import { useSyncExternalStore } from "react"; /** * Platform i18n — TypeScript dictionaries (NOT JSON). * English is the source of truth: `StringKey = keyof typeof en`, and every * other language is typed `Record`, so a missing * translation is a COMPILE error. Fallback chain: current lang -> English. */ export const LANGS = [ { id: "en", label: "English" }, { id: "es", label: "Español" }, { id: "fr", label: "Français" }, { id: "ru", label: "Русский" }, { id: "it", label: "Italiano" }, { id: "pt", label: "Português" }, ] as const; export type LangId = (typeof LANGS)[number]["id"]; const en = { "common.close": "Close", "common.cancel": "Cancel", "common.save": "Save", "common.done": "Done", "common.copy": "Copy", "common.copied": "Copied", "common.create": "Create", "common.delete": "Delete", "common.loading": "Loading…", "common.saving": "Saving…", "common.username": "Username", "common.displayName": "Display name", "common.select": "Select…", "common.theme": "Theme", "common.themeAuto": "Auto", "common.themeAutoFull": "Auto (follows system)", "common.themeSunset": "Sunset", "common.themeSunsetFull": "Sunset (follows local time)", "common.light": "Light", "common.dark": "Dark", "common.saveFailed": "Save failed", "common.loadFailed": "Load failed", "common.deleteFailed": "Delete failed", "common.backToLogin": "Back to login", "loading.tagline": "Preparing your chats", "login.welcome": "Welcome back", "login.subtitle": "Sign in to KisAssistant to continue", "login.usernamePh": "Username", "login.keyPh": "Access key", "login.failed": "Login failed", "login.unlock": "Unlock", "login.newHere": "New here?", "login.register": "Register", "login.regTipOn": "Create an account", "login.regTipOff": "Registration currently disabled on this platform", "login.requestAccess": "Request access", "login.forgot": "Forgot your access key?", "login.perk1": "Private by design — no cookies, no tracking", "login.perk2": "Your own models, streamed in real time", "login.adminNotePre": "Admin key lives in the server ", "login.adminNotePost": "; user keys are issued by the admin.", "login.totpIntro": "Two-factor for @{user} — enter the 6-digit code from your authenticator app.", "login.totpAria": "Authenticator code", "login.totpInvalid": "Invalid code", "login.verify": "Verify", "sidebar.newChat": "New chat", "sidebar.collapse": "Collapse sidebar", "sidebar.closeDrawer": "Close chats", "sidebar.searchPh": "Search chats…", "sidebar.searchAria": "Search conversations", "sidebar.clearSearch": "Clear search", "sidebar.searching": "Searching…", "sidebar.noMatch": "No match for “{q}”.", "sidebar.youPrefix": "you: ", "sidebar.emptyA": "No conversations yet.", "sidebar.emptyB": "Start a new chat above.", "sidebar.rename": "Rename", "sidebar.topic": "Set topic", "sidebar.share": "Share publicly", "sidebar.delete": "Delete", "sidebar.convOptions": "Conversation options", "sidebar.admin": "admin", "sidebar.adminCenter": "Admin Center", "sidebar.adminCenterTip": "Open the Admin Center", "sidebar.settingsTip": "Profile & settings", "sidebar.logout": "Log out", "sidebar.archive": "Archive", "chat.expand": "Expand sidebar", "chat.openNav": "Open chats", "chat.modelAria": "Model", "chat.noModels": "No models", "chat.selectModel": "Select model…", "chat.personalTag": "personal key", "toast.chatDeleted": "Chat “{title}” deleted", "toast.chatArchived": "Chat “{title}” archived", "toast.chatUnarchived": "Chat “{title}” unarchived", "toast.modelChanged": "Model: {label}", "toast.themeChanged": "Theme changed", "toast.accentChanged": "Accent color changed", "toast.unshared": "Share link removed", "toast.settingsSaved": "Settings saved", "toast.convUpdated": "Conversation “{title}” updated", "chat.you": "You", "chat.greet": "How can I help, {name}?", "chat.greetSub": "Pick a model, then type below. Markdown is supported.", "chat.thinking": "Thinking…", "chat.placeholder": "Message KisAssistant…", "chat.send": "Send", "chat.attachTip": "Attach a file", "chat.removeAttach": "Remove {name}", "chat.ocrOpt": "Transcribe image", "chat.unavailable": "(unavailable)", "chat.ocrHint": "JPG, PNG, WEBP — OCR on the server, you review the text first", "chat.textOpt": "Attach text file", "chat.textHint": "TXT or Markdown, max 500KB", "chat.attachNote": "Uploads go through platform tools only. Images are sent to the model as reviewed text — never as raw images.", "chat.wiki": "Wiki & docs", "attach.tooBigImg": "Image too large (max 5MB).", "attach.tooBigText": "Text file too large (max 500KB).", "attach.uploadFailed": "Upload failed", "attach.kindOcr": "image transcription", "attach.kindText": "text file", "attach.tpl": "[attached {kind}: {name}]", "attach.chatError": "**Error:** the model did not respond. Try again.", "dlg.editConv": "Edit conversation", "dlg.fTitle": "Title", "dlg.fTopic": "Topic", "dlg.topicHint": "A short theme tag, e.g. cooking, rust, travel.", "dlg.noTopic": "No topic", "dlg.recoverTitle": "Recover access key", "dlg.recoverIntro": "Enter your username. You'll receive a link that issues a fresh key (the old one stops working).", "dlg.sendLink": "Send reset link", "dlg.reqFailed": "Request failed", "dlg.recoverSent": "If @{user} exists and has a recovery email, a reset link is on its way{fromPart} (valid 60 minutes, single use).", "dlg.recoverSentFrom": " from {from}", "dlg.spamNote": "Check your inbox — and your spam folder, the message may have landed there.", "dlg.noMailNote": "Nothing arrives? Then no recovery email is set on your account and the reset is not possible — please contact the site administrator.", "dlg.registerTitle": "Create an account", "dlg.regWelcome": "Welcome, @{user}! Your access key — copy it now, it won't be shown again.", "dlg.regContinue": "Copy & continue to login", "dlg.regFailed": "Registration failed", "dlg.emailOpt": "Email (optional, for key recovery)", "dlg.shareTitle": "Share publicly", "dlg.shareCreating": "Creating public link…", "dlg.shareIntro": "Anyone with this link can read {title}. No login required.", "dlg.unshare": "Unshare", "dlg.reviewOcr": "Review transcription", "dlg.reviewText": "Review text attachment", "dlg.transcribing": "Transcribing the image on the server (OCR)…", "dlg.uploadingTools": "Uploading through platform tools…", "dlg.ocrExpl": "The image was transcribed to text on the server — the model receives this text, never the image. Fix any reading mistakes below, then attach.", "dlg.textExpl": "The file was uploaded through platform tools. Its text content is attached to your message. Edit below if needed, then attach.", "dlg.truncWarn": "Transcription truncated to the server limit — the end is missing.", "dlg.attachBtn": "Attach to message", "dlg.chars": "{n} chars", "settings.title": "Settings", "settings.searchPh": "Search settings…", "settings.searchAria": "Search settings", "settings.clearSearch": "Clear settings search", "settings.noMatch": "No settings match “{q}”.", "settings.profile": "Profile", "settings.appearance": "Appearance", "settings.features": "Features", "settings.avatarUpload": "Avatar upload", "settings.dropIdle": "Drop an image or click to upload", "settings.dropActive": "Drop it!", "settings.uploading": "Uploading…", "settings.avatarHint": "jpg · png · webp — max 5MB — 5 changes per 2h", "settings.username": "Username", "settings.usernameHint": "Login name. 2–32 chars: lowercase letters, digits, . _ -. Takes effect immediately.", "settings.email": "Recovery email", "settings.emailHint": "Optional. Used only to send you a fresh access key if you lose it.", "settings.accent": "Accent color", "settings.accentHint": "Applied instantly, saved on this device.", "settings.customColor": "Custom color", "settings.customAria": "Custom accent color", "settings.accentAria": "Accent: {label}", "settings.featHint": "Turn composer capabilities on or off. Applied instantly, saved on this device.", "settings.providers": "Providers", "providers.intro": "Connect your own API keys. Models from connected providers show up in the model menu at the top.", "providers.hint": "Keys stay on this server and are never shown again — only the last 4 characters. Usage is billed to your own provider account, and the admin model policy still applies.", "providers.connected": "Connected", "providers.notConnected": "Not connected", "providers.keyPh": "Paste API key…", "providers.saveKey": "Save key", "providers.removeKey": "Remove", "providers.replaceKey": "Replace", "providers.getKey": "Get a key", "providers.keySaved": "Key saved — its models are now in the menu.", "providers.keyRemoved": "Key removed.", "providers.invalidKey": "That key looks invalid (8–256 characters, no spaces).", "providers.loadFailed": "Could not load providers.", "accent.preset.emerald": "Emerald", "accent.preset.rose": "Rose", "accent.preset.blue": "Blue", "accent.preset.violet": "Violet", "accent.preset.amber": "Yellow", "accent.preset.orange": "Orange", "accent.preset.teal": "Teal", "accent.preset.pink": "Pink", "feat.thinking.label": "Thinking", "feat.thinking.hint": "Ask the model to reason step-by-step before answering.", "feat.attachments.label": "Attachments", "feat.attachments.hint": "Attach images (OCR transcription) and text files to messages.", "feat.deepSearch.label": "Deep search", "feat.deepSearch.hint": "Let the assistant search the web for fresh sources. Coming soon.", "feat.soon": "(soon)", "admin.title": "Accounts", "admin.intro": "No-KYC accounts: you create a username, hand the access key to its owner once. Keys are stored hashed — regenerate to rotate.", "admin.keyOnce": "Key for @{user} — shown once, copy it now:", "admin.newAccount": "New account", "admin.email": "Email (recovery)", "admin.createFailed": "Create failed", "admin.regenFailed": "Regenerate failed", "admin.deleteFailed": "Delete failed", "admin.loadFailed": "Load failed", "admin.searchPh": "Search accounts…", "admin.searchAria": "Search accounts", "admin.sortAria": "Sort accounts", "admin.filterAria": "Filter accounts", "admin.sortNewest": "Newest first", "admin.sortOldest": "Oldest first", "admin.sortAz": "A → Z", "admin.sortZa": "Z → A", "admin.filterAll": "All accounts", "admin.filterWith": "With recovery email", "admin.filterNo": "No recovery email", "admin.countMany": "{total} accounts · page {page} of {pages}", "admin.countOne": "{total} account · page {page} of {pages}", "admin.noEmail": "no recovery email", "admin.editProfile": "Edit profile", "admin.regenKey": "Regenerate key (revokes old sessions)", "admin.deleteAccount": "Delete account", "admin.deleteTitle": "Delete @{user}?", "admin.deleteMsg": "This permanently removes the account, all its conversations and its shares.", "admin.prevPage": "Previous page", "admin.nextPage": "Next page", "ollama.models": "Ollama models", "ollama.desc": "Download models from the Ollama library to your Ollama host, or remove local ones. Admin only.", "ollama.pulling": "Pulling {name}", "ollama.customTag": "Custom model tag", "ollama.customHint": "e.g. llama3.2:3b, or a full library name", "ollama.pull": "Pull", "ollama.installed": "Installed ({n})", "ollama.noneYet": "No local Ollama models yet.", "ollama.catalog": "Catalog", "ollama.delTitle": "Delete {name}?", "ollama.delMsg": "The model is removed from the Ollama host. Pull it again anytime.", "ollama.contacting": "Contacting Ollama…", "ollama.pullingSt": "Pulling…", "ollama.doneSt": "Done", "ollama.pullFailed": "Pull failed", "ollama.delTip": "Delete {name}", "ollama.sizeAria": "{name} size", "ollama.cat.llama3_2": "Meta's compact everyday model", "ollama.cat.llama3_1": "Meta's general-purpose workhorse", "ollama.cat.qwen2_5": "Alibaba's multilingual all-rounder", "ollama.cat.gemma2": "Google's efficient open model", "ollama.cat.mistral": "Mistral 7B — fast and capable", "ollama.cat.phi3": "Microsoft's small research model", "ollama.cat.deepseek_r1": "Reasoning model (chain-of-thought)", "ollama.cat.codellama": "Code completion and chat", "ollama.cat.llava": "Vision model (image understanding)", "ollama.cat.nomic_embed_text": "Text embeddings", "center.title": "Admin Center", "center.back": "Back to chat", "center.tabAccounts": "Accounts", "center.tabFeatures": "Features", "center.tabMail": "Mail", "center.tabModels": "Models", "models.title": "Models", "models.desc": "Enable or limit each model. Disabled models disappear from the picker; limits apply per user (admins bypass them).", "models.refresh": "Refresh", "models.refreshAria": "Refresh models", "models.enabled": "Enabled", "models.hourly": "per hour", "models.daily": "per day", "models.unlimited": "Unlimited", "models.ollamaTitle": "Ollama library", "models.ollamaDesc": "Pull or remove local models.", "models.empty": "No models available.", "models.saveFailed": "Could not save", "models.connTitle": "Ollama connectivity", "models.connDesc": "Live status of the Ollama host: reachability, version, latency and installed models.", "models.connOnline": "Online", "models.connOffline": "Offline", "models.connDisabledState": "Disabled", "models.connCheck": "Check now", "models.connCheckAria": "Check Ollama connectivity now", "models.connHost": "Host", "models.connVersion": "Version", "models.connLatency": "Latency", "models.connModels": "Models", "models.connEmpty": "No models on the Ollama host yet — pull one from the library below.", "models.connHint": "Make sure Ollama is running and OLLAMA_HOST points at it.", "center.locked": "Admins only", "center.lockedSub": "Log in with the admin account to open the Admin Center.", "center.regTitle": "Public registration", "center.regDesc": "Show the Register button on the login page. Off = admin-created accounts only. Turning it on retires the wishlist below.", "center.accTitle": "Access requests (wishlist)", "center.accDescOn": "Unavailable while public registration is on — it replaces the wishlist.", "center.accDescOff": "Show Request access on the login page. Visitors reserve a username and plead their case; you triage them in the Access tab.", "center.ocrTitle": "OCR tool", "center.ocrDesc": "Server-side image transcription for attachments. Requires the tesseract binary.", "center.reportsTitle": "Message reports", "center.reportsDesc": "Flag button under assistant responses. Off = the flag turns grey with a “Report disabled” tooltip and new reports are rejected.", "center.usernameChangeTitle": "Username changes", "center.usernameChangeDesc": "Lets users rename themselves in Settings → Profile. Off = the field is locked (admins can still rename accounts).", "settings.usernameDisabled": "Username changes are disabled by the administrator.", "center.mailTitle": "Key recovery emails", "center.mailChecking": "Checking…", "center.mailFrom": "Enabled — sent from {from}", "center.mailDefault": "Enabled — sent from the configured sender", "center.mailOff": "Disabled — set SMTP_HOST on the server to enable", "center.mailNote": "SMTP credentials live in the server environment (see docs/OPERATIONS.md), never in this UI. Users set their recovery address in profile settings or at account creation.", "center.on": "On", "center.off": "Off", "share.fallback": "Shared chat", "share.open": "Open KisAssistant", "share.gone": "This shared chat is gone", "share.goneSub": "The link is invalid, expired, or the author unshared it.", "share.back": "Go back", "share.loading": "Loading shared chat…", "share.by": "by {name}", "reset.checking": "Checking link…", "reset.invalid": "Link invalid", "reset.invalidSub": "This reset link is unknown, already used, or expired. Request a new one from the login page.", "reset.readyTitle": "Reset key for @{user}?", "reset.readySub": "This issues a fresh access key immediately. Your old key stops working.", "reset.issue": "Issue new key", "reset.issuing": "Issuing…", "reset.doneTitle": "New key ready", "reset.doneSub": "Copy it now — it won't be shown again. Then log in with it.", "reset.goLogin": "Go to login", "reset.failed": "Reset failed", "app.deleteConvTitle": "Delete conversation?", "app.deleteConvMsg": "“{title}” and all its messages will be permanently removed.", "settings.language": "Language", "settings.languageHint": "Interface language. Applied instantly, saved on this device.", "settings.security": "Security", "sec.accessKey": "Access key", "sec.accessKeyHint": "Rotate your access key. Old sessions are revoked immediately — you'll log back in with the new key.", "sec.rotate": "Rotate my access key", "sec.rotateConfirm": "Click again to confirm rotation", "sec.rotationFailed": "Rotation failed", "sec.totp": "Two-factor (TOTP)", "sec.totpChecking": "Checking…", "sec.totpAddHint": "Add a 6-digit code from your authenticator app on top of your access key.", "sec.totpEnable": "Enable two-factor", "sec.totpSecretHint": "Add this secret to your authenticator app (or open the otpauth link), then enter a code to confirm:", "sec.totpConfirm": "Confirm", "sec.totpDisableLabel": "Disable with a current code", "sec.totpDisable": "Disable 2FA", "sec.setupFailed": "Setup failed", "sec.passkeys": "Passkeys", "sec.passkeysHint": "WebAuthn / platform authenticators — needs a server RP setup.", "sec.danger": "Danger zone", "sec.dangerHint": "Permanently delete your account with all chats, shares and sessions. Cannot be undone.", "sec.deleteMe": "Delete my account", "sec.deleteFailed": "Delete failed", "sec.newKeyTitle": "New access key", "sec.newKeyHint": "Copy it now — it won't be shown again. Your old sessions are revoked.", "sec.newKeyDone": "Done — log me out", "sec.deleteTitle": "Delete your account?", "sec.deleteMsg": "This permanently removes your account with all conversations, shares and sessions. This cannot be undone.", "sec.deleteForever": "Delete forever", "sec.avatarTooBig": "File too large (max 5MB).", "sec.avatarType": "Only jpg, png or webp images are accepted.", "center.tabAccess": "Access", "center.smtpHost": "Host", "center.smtpPort": "Port", "center.smtpUser": "User", "center.smtpPass": "Password", "center.smtpFrom": "From", "center.smtpAppUrl": "App URL", "center.notSet": "(not set)", "center.noAuth": "(no auth)", "center.tlsImplicit": "implicit TLS (465)", "center.tlsStarttls": "STARTTLS (587)", "center.showPass": "Show SMTP password", "center.hidePass": "Hide SMTP password", "center.hiddenPass": "hidden password", "center.mailTestTitle": "Connectivity test", "center.mailTestDesc": "Verify the connection, then send a real test mail to an address you control.", "center.verifyConn": "Verify connection", "center.verifying": "Verifying…", "center.sendTest": "Send test mail", "center.sendingMail": "Sending…", "center.testToAria": "Test recipient email", "center.connOk": "Connection OK — SMTP host reachable, auth accepted.", "center.verifyFailed": "Verification failed", "center.testSent": "Test mail sent to {to}{tail}.", "center.mailSendFailed": "Send failed", "center.credsNote": "Credentials live in the server environment (see docs/OPERATIONS.md), read-only here. Users set their recovery address in profile settings or at account creation.", "access.status.pending": "Pending", "access.status.reviewing": "Reviewing", "access.status.accepted": "Accepted", "access.status.refused": "Refused", "access.filter.all": "all", "access.filter.pending": "pending", "access.filter.reviewing": "reviewing", "access.filter.accepted": "accepted", "access.filter.refused": "refused", "access.refresh": "Refresh", "access.refreshAria": "Refresh requests", "access.empty": "No access requests. The wishlist is empty.", "access.emptyFiltered": "Nothing with this status.", "access.msgOne": "1 message", "access.msgMany": "{n} messages", "access.accountCreated": "Account created — key shown once, copy it now:", "access.replyPh": "Reply (emailed to the requester)…", "access.replyAria": "Reply to requester", "access.reasonLabel": "Reason (emailed with the decision)", "access.reasonPh": "Optional reason…", "access.btnReviewing": "Reviewing", "access.btnAccept": "Accept", "access.btnRefuse": "Refuse", "access.closedNote": "Ticket closed ({status}).", "access.reasonWord": "Reason", "access.youAdmin": "You (admin)", "access.sendFailed": "Send failed", "access.updateFailed": "Update failed", "live.newRequest": "New access request from @{user}", "live.newMessage": "New message from @{user}", "live.newReport": "New content report ({reason})", "live.adminReply": "Admin replied to your ticket", "live.ticketStatus": "Ticket {status}", "msg.copy": "Copy message", "msg.search": "Search the web", "msg.report": "Report response", "msg.reportDisabled": "Report disabled", "msg.retry": "Retry", "report.title": "Report this response", "report.reason": "Reason", "report.reasonPh": "Select a reason…", "report.reason.copyright": "Copyright", "report.reason.gore": "Gore / CSAM / NSFW", "report.reason.falseinfo": "False information", "report.reason.bug": "Bug", "report.details": "Details (optional)", "report.detailsPh": "What is wrong with this response? (optional)", "report.submit": "Send report", "report.sent": "Report sent — an admin will review it.", "report.failed": "Could not send the report", "center.tabReports": "Reports", "reports.filter.all": "all", "reports.filter.open": "open", "reports.filter.reviewing": "reviewing", "reports.filter.resolved": "resolved", "reports.filter.dismissed": "dismissed", "reports.status.open": "Open", "reports.status.reviewing": "Reviewing", "reports.status.resolved": "Resolved", "reports.status.dismissed": "Dismissed", "reports.refresh": "Refresh", "reports.refreshAria": "Refresh reports", "reports.empty": "No content reports yet.", "reports.emptyFiltered": "Nothing with this status.", "reports.response": "Reported response", "reports.noContent": "No content captured.", "reports.model": "Model", "reports.reporter": "Reporter", "reports.conversation": "Conversation", "reports.detailsGiven": "Reporter details", "reports.noteLabel": "Admin note", "reports.notePh": "Internal note…", "reports.noteWord": "Note", "reports.btnReviewing": "Reviewing", "reports.btnResolve": "Resolve", "reports.btnDismiss": "Dismiss", "reports.updateFailed": "Update failed", "reports.closedNote": "Report {status}.", "report.alreadyReported": "This response was already reported.", "reports.prompt": "User prompt", "reports.unreliable": "Unreliable reporter", "reports.shadowban": "Shadow-ban reporter", "reports.unshadowban": "Lift shadow-ban", "reports.shadowbanTitle": "Shadow-ban this reporter?", "reports.shadowbanMsg": "@{user}'s reports will still be accepted but marked unreliable and hidden from the badge.", "reports.unshadowbanTitle": "Lift the shadow-ban?", "reports.unshadowbanMsg": "@{user}'s reports will count toward the badge again.", "review.title": "Access request", "review.loadingTicket": "Loading ticket…", "review.notFound": "Ticket not found", "review.notFoundSub": "This link is unknown. Check the URL from your email, or file a new request from the login page.", "review.adminNote": "Admin note:", "review.acceptedHint": "Your account was created — check your email for the access key, then log in.", "review.closedHint": "This ticket is closed. Replies by email are not read — if needed, file a new request.", "review.replyPh": "Argue your case…", "review.replyAria": "Reply to your ticket", "review.mailHint": "Email notifications are one-way — always reply here, not by email.", "review.status.pending": "Pending review", "review.status.reviewing": "Under review", "review.status.accepted": "Accepted", "review.status.refused": "Refused", "review.admin": "Admin", "review.you": "You", "review.requestTitle": "Request access", "review.requestIntro": "Reserve a username and tell us why you want in. An admin reviews every request — you follow the discussion on your personal ticket page and by email.", "review.emailHint": "Required — status updates and ticket replies land here.", "review.whyLabel": "Why should you get access?", "review.whyHint": "10+ characters. This starts your ticket thread.", "review.whyPh": "I want to…", "review.emailLabel": "Email", "review.sendRequest": "Send request", "review.received": "Request received", "review.reserved": "@{user} is reserved for you while an admin reviews it. A confirmation email with your personal ticket link is on its way.", "review.copyLink": "Copy link", "review.openTicket": "Open my ticket", "admin.editUserTitle": "Edit @{user}", "admin.avatarUrl": "Avatar URL", "archived.readonly": "This chat is archived and read-only.", "archived.unarchive": "Unarchive", "archived.title": "Archived chats", "archived.empty": "No archived conversations yet.", "archived.view": "View", "archived.deleteTitle": "Delete this archived chat?", "archived.deleteMsg": "This permanently removes the conversation and all its messages.", } as const; export type StringKey = keyof typeof en; type Dict = Record; const es: Dict = { "common.close": "Cerrar", "common.cancel": "Cancelar", "common.save": "Guardar", "common.done": "Listo", "common.copy": "Copiar", "common.copied": "Copiado", "common.create": "Crear", "common.delete": "Eliminar", "common.loading": "Cargando…", "common.saving": "Guardando…", "common.username": "Nombre de usuario", "common.displayName": "Nombre visible", "common.select": "Elegir…", "common.theme": "Tema", "common.themeAuto": "Automático", "common.themeAutoFull": "Automático (según el sistema)", "common.themeSunset": "Puesta de sol", "common.themeSunsetFull": "Puesta de sol (hora local)", "common.light": "Claro", "common.dark": "Oscuro", "common.saveFailed": "No se pudo guardar", "common.loadFailed": "No se pudo cargar", "common.deleteFailed": "No se pudo eliminar", "common.backToLogin": "Volver al inicio de sesión", "loading.tagline": "Preparando tus chats", "login.welcome": "Bienvenido de nuevo", "login.subtitle": "Inicia sesión en KisAssistant para continuar", "login.usernamePh": "Nombre de usuario", "login.keyPh": "Clave de acceso", "login.failed": "Error de inicio de sesión", "login.unlock": "Desbloquear", "login.newHere": "¿Nuevo por aquí?", "login.register": "Registrarse", "login.regTipOn": "Crear una cuenta", "login.regTipOff": "El registro está desactivado en esta plataforma", "login.requestAccess": "Solicitar acceso", "login.forgot": "¿Olvidaste tu clave de acceso?", "login.perk1": "Privado por diseño — sin cookies ni rastreo", "login.perk2": "Tus propios modelos, en tiempo real", "login.adminNotePre": "La clave de admin vive en el .env del servidor", "login.adminNotePost": "; las claves de usuario las emite el admin.", "login.totpIntro": "Doble factor para @{user} — escribe el código de 6 dígitos de tu app.", "login.totpAria": "Código de autenticación", "login.totpInvalid": "Código inválido", "login.verify": "Verificar", "sidebar.newChat": "Nuevo chat", "sidebar.collapse": "Ocultar barra lateral", "sidebar.closeDrawer": "Cerrar chats", "sidebar.searchPh": "Buscar chats…", "sidebar.searchAria": "Buscar conversaciones", "sidebar.clearSearch": "Borrar búsqueda", "sidebar.searching": "Buscando…", "sidebar.noMatch": "Sin resultados para «{q}».", "sidebar.youPrefix": "tú: ", "sidebar.emptyA": "Aún no hay conversaciones.", "sidebar.emptyB": "Empieza un nuevo chat arriba.", "sidebar.rename": "Renombrar", "sidebar.topic": "Definir tema", "sidebar.share": "Compartir públicamente", "sidebar.delete": "Eliminar", "sidebar.convOptions": "Opciones de conversación", "sidebar.admin": "admin", "sidebar.adminCenter": "Centro de admin", "sidebar.adminCenterTip": "Abrir el Centro de admin", "sidebar.settingsTip": "Perfil y ajustes", "sidebar.logout": "Cerrar sesión", "sidebar.archive": "Archivar", "chat.expand": "Mostrar barra lateral", "chat.openNav": "Abrir chats", "chat.modelAria": "Modelo", "chat.noModels": "Sin modelos", "chat.selectModel": "Elegir modelo…", "chat.personalTag": "clave personal", "toast.chatDeleted": "Chat “{title}” eliminado", "toast.chatArchived": "Chat “{title}” archivado", "toast.chatUnarchived": "Chat “{title}” desarchivado", "toast.modelChanged": "Modelo: {label}", "toast.themeChanged": "Tema cambiado", "toast.accentChanged": "Color de acento cambiado", "toast.unshared": "Enlace compartido eliminado", "toast.settingsSaved": "Ajustes guardados", "toast.convUpdated": "Conversación “{title}” actualizada", "chat.you": "Tú", "chat.greet": "¿En qué te ayudo, {name}?", "chat.greetSub": "Elige un modelo y escribe abajo. Markdown soportado.", "chat.thinking": "Pensando…", "chat.placeholder": "Escribe a KisAssistant…", "chat.send": "Enviar", "chat.attachTip": "Adjuntar un archivo", "chat.removeAttach": "Quitar {name}", "chat.ocrOpt": "Transcribir imagen", "chat.unavailable": "(no disponible)", "chat.ocrHint": "JPG, PNG, WEBP — OCR en el servidor, revisas el texto antes", "chat.textOpt": "Adjuntar archivo de texto", "chat.textHint": "TXT o Markdown, máx. 500KB", "chat.attachNote": "Las subidas usan solo herramientas de la plataforma. Las imágenes llegan al modelo como texto revisado — nunca como imágenes.", "chat.wiki": "Wiki y docs", "attach.tooBigImg": "Imagen muy grande (máx. 5MB).", "attach.tooBigText": "Archivo muy grande (máx. 500KB).", "attach.uploadFailed": "Error al subir", "attach.kindOcr": "transcripción de imagen", "attach.kindText": "archivo de texto", "attach.tpl": "[adjunto {kind}: {name}]", "attach.chatError": "**Error:** el modelo no respondió. Inténtalo de nuevo.", "dlg.editConv": "Editar conversación", "dlg.fTitle": "Título", "dlg.fTopic": "Tema", "dlg.topicHint": "Etiqueta corta, p. ej. cocina, rust, viajes.", "dlg.noTopic": "Sin tema", "dlg.recoverTitle": "Recuperar clave de acceso", "dlg.recoverIntro": "Escribe tu usuario. Recibirás un enlace que emite una clave nueva (la anterior deja de funcionar).", "dlg.sendLink": "Enviar enlace", "dlg.reqFailed": "Solicitud fallida", "dlg.recoverSent": "Si @{user} existe y tiene email de recuperación, un enlace va en camino{fromPart} (válido 60 minutos, un solo uso).", "dlg.recoverSentFrom": " desde {from}", "dlg.spamNote": "Revisa tu bandeja — y el spam, puede haber caído allí.", "dlg.noMailNote": "¿No llega nada? Entonces no hay email de recuperación en tu cuenta y no es posible — contacta al administrador del sitio.", "dlg.registerTitle": "Crear una cuenta", "dlg.regWelcome": "¡Bienvenido, @{user}! Tu clave de acceso — cópiala ahora, no se mostrará de nuevo.", "dlg.regContinue": "Copiar e ir al login", "dlg.regFailed": "Registro fallido", "dlg.emailOpt": "Email (opcional, para recuperar la clave)", "dlg.shareTitle": "Compartir públicamente", "dlg.shareCreating": "Creando enlace público…", "dlg.shareIntro": "Cualquiera con este enlace puede leer {title}. Sin login.", "dlg.unshare": "Dejar de compartir", "dlg.reviewOcr": "Revisar transcripción", "dlg.reviewText": "Revisar adjunto de texto", "dlg.transcribing": "Transcribiendo la imagen en el servidor (OCR)…", "dlg.uploadingTools": "Subiendo con herramientas de la plataforma…", "dlg.ocrExpl": "La imagen se transcribió a texto en el servidor — el modelo recibe este texto, nunca la imagen. Corrige errores abajo y adjunta.", "dlg.textExpl": "El archivo se subió con herramientas de la plataforma. Su texto se adjunta a tu mensaje. Edita si quieres y adjunta.", "dlg.truncWarn": "Transcripción recortada al límite del servidor — falta el final.", "dlg.attachBtn": "Adjuntar al mensaje", "dlg.chars": "{n} caracteres", "settings.title": "Ajustes", "settings.searchPh": "Buscar ajustes…", "settings.searchAria": "Buscar ajustes", "settings.clearSearch": "Borrar búsqueda", "settings.noMatch": "Sin ajustes para «{q}».", "settings.profile": "Perfil", "settings.appearance": "Apariencia", "settings.features": "Funciones", "settings.avatarUpload": "Subir avatar", "settings.dropIdle": "Suelta una imagen o haz clic para subir", "settings.dropActive": "¡Suéltala!", "settings.uploading": "Subiendo…", "settings.avatarHint": "jpg · png · webp — máx. 5MB — 5 cambios por 2h", "settings.username": "Nombre de usuario", "settings.usernameHint": "Nombre de acceso. 2–32 caracteres: minúsculas, dígitos, . _ -. Efecto inmediato.", "settings.email": "Email de recuperación", "settings.emailHint": "Opcional. Solo para enviarte una clave nueva si la pierdes.", "settings.accent": "Color de acento", "settings.accentHint": "Se aplica al instante, se guarda en este dispositivo.", "settings.customColor": "Color personalizado", "settings.customAria": "Color de acento personalizado", "settings.accentAria": "Acento: {label}", "settings.featHint": "Activa funciones del editor. Al instante, en este dispositivo.", "settings.providers": "Proveedores", "providers.intro": "Conecta tus propias claves API. Los modelos de los proveedores conectados aparecen en el menú de modelos de arriba.", "providers.hint": "Las claves quedan en este servidor y no se muestran de nuevo — solo los últimos 4 caracteres. El uso se factura a tu propia cuenta y la política de modelos del admin sigue vigente.", "providers.connected": "Conectado", "providers.notConnected": "Sin conectar", "providers.keyPh": "Pega la clave API…", "providers.saveKey": "Guardar clave", "providers.removeKey": "Quitar", "providers.replaceKey": "Reemplazar", "providers.getKey": "Obtener clave", "providers.keySaved": "Clave guardada — sus modelos ya están en el menú.", "providers.keyRemoved": "Clave eliminada.", "providers.invalidKey": "Esa clave no parece válida (8–256 caracteres, sin espacios).", "providers.loadFailed": "No se pudieron cargar los proveedores.", "accent.preset.emerald": "Esmeralda", "accent.preset.rose": "Rosa", "accent.preset.blue": "Azul", "accent.preset.violet": "Violeta", "accent.preset.amber": "Amarillo", "accent.preset.orange": "Naranja", "accent.preset.teal": "Turquesa", "accent.preset.pink": "Rosa fuerte", "feat.thinking.label": "Razonamiento", "feat.thinking.hint": "Pide al modelo razonar paso a paso antes de responder.", "feat.attachments.label": "Adjuntos", "feat.attachments.hint": "Adjunta imágenes (transcripción OCR) y archivos de texto.", "feat.deepSearch.label": "Búsqueda profunda", "feat.deepSearch.hint": "Deja al asistente buscar fuentes frescas. Próximamente.", "feat.soon": "(pronto)", "admin.title": "Cuentas", "admin.intro": "Cuentas sin KYC: creas un usuario, entregas la clave una vez. Se guardan con hash — regenera para rotar.", "admin.keyOnce": "Clave para @{user} — se muestra una vez, cópiala ahora:", "admin.newAccount": "Nueva cuenta", "admin.email": "Email (recuperación)", "admin.createFailed": "No se pudo crear", "admin.regenFailed": "No se pudo regenerar", "admin.deleteFailed": "No se pudo eliminar", "admin.loadFailed": "No se pudo cargar", "admin.searchPh": "Buscar cuentas…", "admin.searchAria": "Buscar cuentas", "admin.sortAria": "Ordenar cuentas", "admin.filterAria": "Filtrar cuentas", "admin.sortNewest": "Más nuevas", "admin.sortOldest": "Más viejas", "admin.sortAz": "A → Z", "admin.sortZa": "Z → A", "admin.filterAll": "Todas las cuentas", "admin.filterWith": "Con email de recuperación", "admin.filterNo": "Sin email de recuperación", "admin.countMany": "{total} cuentas · página {page} de {pages}", "admin.countOne": "{total} cuenta · página {page} de {pages}", "admin.noEmail": "sin email de recuperación", "admin.editProfile": "Editar perfil", "admin.regenKey": "Regenerar clave (revoca sesiones)", "admin.deleteAccount": "Eliminar cuenta", "admin.deleteTitle": "¿Eliminar @{user}?", "admin.deleteMsg": "Esto elimina la cuenta, sus conversaciones y sus shares.", "admin.prevPage": "Página anterior", "admin.nextPage": "Página siguiente", "ollama.models": "Modelos Ollama", "ollama.desc": "Descarga modelos de la librería Ollama a tu host, o elimina los locales. Solo admin.", "ollama.pulling": "Descargando {name}", "ollama.customTag": "Etiqueta personalizada", "ollama.customHint": "p. ej. llama3.2:3b, o un nombre de librería", "ollama.pull": "Descargar", "ollama.installed": "Instalados ({n})", "ollama.noneYet": "Aún no hay modelos Ollama locales.", "ollama.catalog": "Catálogo", "ollama.delTitle": "¿Eliminar {name}?", "ollama.delMsg": "El modelo se elimina del host Ollama. Descárgalo de nuevo cuando quieras.", "ollama.contacting": "Contactando Ollama…", "ollama.pullingSt": "Descargando…", "ollama.doneSt": "Listo", "ollama.pullFailed": "Descarga fallida", "ollama.delTip": "Eliminar {name}", "ollama.sizeAria": "tamaño de {name}", "ollama.cat.llama3_2": "Modelo compacto de Meta para el día a día", "ollama.cat.llama3_1": "El caballo de batalla de Meta", "ollama.cat.qwen2_5": "Multilingüe versátil de Alibaba", "ollama.cat.gemma2": "Modelo abierto y eficiente de Google", "ollama.cat.mistral": "Mistral 7B — rápido y capaz", "ollama.cat.phi3": "Modelo pequeño de investigación de Microsoft", "ollama.cat.deepseek_r1": "Modelo de razonamiento (cadena de pensamiento)", "ollama.cat.codellama": "Autocompletado y chat de código", "ollama.cat.llava": "Modelo de visión (entiende imágenes)", "ollama.cat.nomic_embed_text": "Embeddings de texto", "center.title": "Centro de admin", "center.back": "Volver al chat", "center.tabAccounts": "Cuentas", "center.tabFeatures": "Funciones", "center.tabMail": "Correo", "center.tabModels": "Modelos", "models.title": "Modelos", "models.desc": "Activa o limita cada modelo. Los modelos desactivados desaparecen del selector; los límites se aplican por usuario (los admins no tienen límite).", "models.refresh": "Actualizar", "models.refreshAria": "Actualizar modelos", "models.enabled": "Activado", "models.hourly": "por hora", "models.daily": "por día", "models.unlimited": "Ilimitado", "models.ollamaTitle": "Biblioteca Ollama", "models.ollamaDesc": "Descargar o eliminar modelos locales.", "models.empty": "No hay modelos disponibles.", "models.saveFailed": "No se pudo guardar", "models.connTitle": "Conectividad Ollama", "models.connDesc": "Estado en vivo del servidor Ollama: alcance, versión, latencia y modelos instalados.", "models.connOnline": "En línea", "models.connOffline": "Sin conexión", "models.connDisabledState": "Desactivado", "models.connCheck": "Comprobar ahora", "models.connCheckAria": "Comprobar la conectividad Ollama ahora", "models.connHost": "Host", "models.connVersion": "Versión", "models.connLatency": "Latencia", "models.connModels": "Modelos", "models.connEmpty": "Aún no hay modelos en el servidor Ollama — descarga uno desde la biblioteca.", "models.connHint": "Asegúrate de que Ollama esté en marcha y de que OLLAMA_HOST apunte a él.", "center.locked": "Solo admins", "center.lockedSub": "Inicia sesión como admin para abrir el Centro de admin.", "center.regTitle": "Registro público", "center.regDesc": "Muestra el botón Registrarse en el login. Off = solo cuentas del admin. Activarlo jubila la lista de espera.", "center.accTitle": "Solicitudes de acceso (lista)", "center.accDescOn": "No disponible con registro público — lo reemplaza.", "center.accDescOff": "Muestra Solicitar acceso en el login. Triaje en la pestaña Acceso.", "center.ocrTitle": "Herramienta OCR", "center.ocrDesc": "Transcripción de imágenes en el servidor. Requiere tesseract.", "center.reportsTitle": "Denuncias de mensajes", "center.reportsDesc": "Botón de bandera bajo las respuestas. Off = bandera gris con aviso y se rechazan nuevas denuncias.", "center.usernameChangeTitle": "Cambios de nombre", "center.usernameChangeDesc": "Permite renombrarse en Ajustes → Perfil. Off = campo bloqueado (el admin aún puede renombrar).", "settings.usernameDisabled": "El administrador ha desactivado los cambios de nombre.", "center.mailTitle": "Emails de recuperación", "center.mailChecking": "Verificando…", "center.mailFrom": "Activado — enviados desde {from}", "center.mailDefault": "Activado — enviados desde el remitente configurado", "center.mailOff": "Desactivado — configura SMTP_HOST en el servidor", "center.mailNote": "Las credenciales SMTP viven en el servidor (ver docs/OPERATIONS.md), nunca aquí. El email se define en el perfil o al crear la cuenta.", "center.on": "Sí", "center.off": "No", "share.fallback": "Chat compartido", "share.open": "Abrir KisAssistant", "share.gone": "Este chat compartido ya no existe", "share.goneSub": "El enlace no vale, expiró o el autor lo retiró.", "share.back": "Volver", "share.loading": "Cargando chat compartido…", "share.by": "por {name}", "reset.checking": "Verificando enlace…", "reset.invalid": "Enlace inválido", "reset.invalidSub": "Este enlace no existe, ya se usó o expiró. Pide uno nuevo en el login.", "reset.readyTitle": "¿Nueva clave para @{user}?", "reset.readySub": "Emite una clave nueva al instante. La anterior deja de funcionar.", "reset.issue": "Emitir nueva clave", "reset.issuing": "Emitiendo…", "reset.doneTitle": "Nueva clave lista", "reset.doneSub": "Cópiala ahora — no se mostrará de nuevo. Inicia sesión con ella.", "reset.goLogin": "Ir al login", "reset.failed": "Recuperación fallida", "app.deleteConvTitle": "¿Eliminar conversación?", "app.deleteConvMsg": "«{title}» y sus mensajes se eliminarán.", "settings.language": "Idioma", "settings.languageHint": "Idioma de la interfaz. Al instante, en este dispositivo.", "settings.security": "Seguridad", "sec.accessKey": "Clave de acceso", "sec.accessKeyHint": "Rota tu clave de acceso. Las sesiones antiguas se revocan al instante — volverás con la nueva.", "sec.rotate": "Rotar mi clave", "sec.rotateConfirm": "Pulsa de nuevo para confirmar", "sec.rotationFailed": "No se pudo rotar", "sec.totp": "Doble factor (TOTP)", "sec.totpChecking": "Verificando…", "sec.totpAddHint": "Añade un código de 6 dígitos de tu app además de tu clave.", "sec.totpEnable": "Activar doble factor", "sec.totpSecretHint": "Añade este secreto a tu app (o abre el enlace otpauth) y escribe un código para confirmar:", "sec.totpConfirm": "Confirmar", "sec.totpDisableLabel": "Desactivar con un código actual", "sec.totpDisable": "Desactivar 2FA", "sec.setupFailed": "No se pudo configurar", "sec.passkeys": "Passkeys", "sec.passkeysHint": "WebAuthn / autenticadores del sistema — requiere RP en el servidor.", "sec.danger": "Zona de peligro", "sec.dangerHint": "Elimina tu cuenta con chats, shares y sesiones. Sin vuelta atrás.", "sec.deleteMe": "Eliminar mi cuenta", "sec.deleteFailed": "No se pudo eliminar", "sec.newKeyTitle": "Nueva clave de acceso", "sec.newKeyHint": "Cópiala ahora — no se mostrará de nuevo. Tus sesiones se revocan.", "sec.newKeyDone": "Listo — cerrar sesión", "sec.deleteTitle": "¿Eliminar tu cuenta?", "sec.deleteMsg": "Esto elimina tu cuenta con conversaciones, shares y sesiones. Sin vuelta atrás.", "sec.deleteForever": "Eliminar para siempre", "sec.avatarTooBig": "Archivo muy grande (máx. 5MB).", "sec.avatarType": "Solo se aceptan imágenes jpg, png o webp.", "center.tabAccess": "Acceso", "center.smtpHost": "Servidor", "center.smtpPort": "Puerto", "center.smtpUser": "Usuario", "center.smtpPass": "Contraseña", "center.smtpFrom": "Remitente", "center.smtpAppUrl": "URL de la app", "center.notSet": "(sin definir)", "center.noAuth": "(sin auth)", "center.tlsImplicit": "TLS implícito (465)", "center.tlsStarttls": "STARTTLS (587)", "center.showPass": "Mostrar contraseña SMTP", "center.hidePass": "Ocultar contraseña SMTP", "center.hiddenPass": "contraseña oculta", "center.mailTestTitle": "Prueba de conexión", "center.mailTestDesc": "Verifica la conexión y envía un email real a una dirección tuya.", "center.verifyConn": "Verificar conexión", "center.verifying": "Verificando…", "center.sendTest": "Enviar email de prueba", "center.sendingMail": "Enviando…", "center.testToAria": "Email de prueba", "center.connOk": "Conexión OK — SMTP accesible, auth aceptada.", "center.verifyFailed": "Verificación fallida", "center.testSent": "Email enviado a {to}{tail}.", "center.mailSendFailed": "No se pudo enviar", "center.credsNote": "Las credenciales viven en el servidor (ver docs/OPERATIONS.md), solo lectura aquí. El email se define en el perfil o al crear la cuenta.", "access.status.pending": "Pendiente", "access.status.reviewing": "En revisión", "access.status.accepted": "Aceptada", "access.status.refused": "Rechazada", "access.filter.all": "todas", "access.filter.pending": "pendientes", "access.filter.reviewing": "en revisión", "access.filter.accepted": "aceptadas", "access.filter.refused": "rechazadas", "access.refresh": "Actualizar", "access.refreshAria": "Actualizar solicitudes", "access.empty": "Sin solicitudes. La lista está vacía.", "access.emptyFiltered": "Nada con este estado.", "access.msgOne": "1 mensaje", "access.msgMany": "{n} mensajes", "access.accountCreated": "Cuenta creada — clave única, cópiala ahora:", "access.replyPh": "Responder (llega por email)…", "access.replyAria": "Responder al solicitante", "access.reasonLabel": "Motivo (se envía con la decisión)", "access.reasonPh": "Motivo opcional…", "access.btnReviewing": "Revisar", "access.btnAccept": "Aceptar", "access.btnRefuse": "Rechazar", "access.closedNote": "Ticket cerrado ({status}).", "access.reasonWord": "Motivo", "access.youAdmin": "Tú (admin)", "access.sendFailed": "No se pudo enviar", "access.updateFailed": "No se pudo actualizar", "live.newRequest": "New access request from @{user}", "live.newMessage": "New message from @{user}", "live.newReport": "New content report ({reason})", "live.adminReply": "Admin replied to your ticket", "live.ticketStatus": "Ticket {status}", "msg.copy": "Copiar mensaje", "msg.search": "Buscar en la web", "msg.report": "Denunciar respuesta", "msg.reportDisabled": "Denuncias desactivadas", "msg.retry": "Reintentar", "report.title": "Denunciar esta respuesta", "report.reason": "Motivo", "report.reasonPh": "Selecciona un motivo…", "report.reason.copyright": "Derechos de autor", "report.reason.gore": "Gore / CSAM / NSFW", "report.reason.falseinfo": "Información falsa", "report.reason.bug": "Error", "report.details": "Detalles (opcional)", "report.detailsPh": "¿Qué tiene de malo esta respuesta? (opcional)", "report.submit": "Enviar denuncia", "report.sent": "Denuncia enviada — un administrador la revisará.", "report.failed": "No se pudo enviar la denuncia", "center.tabReports": "Denuncias", "reports.filter.all": "todas", "reports.filter.open": "abiertas", "reports.filter.reviewing": "en revisión", "reports.filter.resolved": "resueltas", "reports.filter.dismissed": "descartadas", "reports.status.open": "Abierta", "reports.status.reviewing": "En revisión", "reports.status.resolved": "Resuelta", "reports.status.dismissed": "Descartada", "reports.refresh": "Actualizar", "reports.refreshAria": "Actualizar denuncias", "reports.empty": "Aún no hay denuncias de contenido.", "reports.emptyFiltered": "Nada con este estado.", "reports.response": "Respuesta denunciada", "reports.noContent": "Sin contenido capturado.", "reports.model": "Modelo", "reports.reporter": "Denunciante", "reports.conversation": "Conversación", "reports.detailsGiven": "Detalles del denunciante", "reports.noteLabel": "Nota del administrador", "reports.notePh": "Nota interna…", "reports.noteWord": "Nota", "reports.btnReviewing": "En revisión", "reports.btnResolve": "Resolver", "reports.btnDismiss": "Descartar", "reports.updateFailed": "No se pudo actualizar", "reports.closedNote": "Denuncia {status}.", "report.alreadyReported": "Esta respuesta ya fue denunciada.", "reports.prompt": "Pregunta del usuario", "reports.unreliable": "Denunciante no fiable", "reports.shadowban": "Silenciar denunciante", "reports.unshadowban": "Quitar silencio", "reports.shadowbanTitle": "¿Silenciar a este denunciante?", "reports.shadowbanMsg": "Las denuncias de @{user} se seguirán aceptando, pero se marcarán como no fiables y no contarán en el contador.", "reports.unshadowbanTitle": "¿Quitar el silencio?", "reports.unshadowbanMsg": "Las denuncias de @{user} volverán a contar.", "review.title": "Solicitud de acceso", "review.loadingTicket": "Cargando ticket…", "review.notFound": "Ticket no encontrado", "review.notFoundSub": "Este enlace no existe. Revisa la URL de tu email o pide acceso desde el login.", "review.adminNote": "Nota del admin:", "review.acceptedHint": "Tu cuenta fue creada — revisa tu email con la clave e inicia sesión.", "review.closedHint": "Ticket cerrado. Los emails no se leen — si hace falta, pide acceso de nuevo.", "review.replyPh": "Defiende tu caso…", "review.replyAria": "Responder a tu ticket", "review.mailHint": "Los emails son unidireccionales — responde aquí, no por email.", "review.status.pending": "Pendiente de revisión", "review.status.reviewing": "En revisión", "review.status.accepted": "Aceptada", "review.status.refused": "Rechazada", "review.admin": "Admin", "review.you": "Tú", "review.requestTitle": "Solicitar acceso", "review.requestIntro": "Reserva un usuario y cuéntanos por qué quieres entrar. Un admin revisa cada solicitud — sigues el hilo en tu ticket y por email.", "review.emailHint": "Obligatorio — avisos y respuestas llegan aquí.", "review.whyLabel": "¿Por qué darte acceso?", "review.whyHint": "10+ caracteres. Esto abre tu ticket.", "review.whyPh": "Quiero…", "review.emailLabel": "Email", "review.sendRequest": "Enviar solicitud", "review.received": "Solicitud recibida", "review.reserved": "@{user} queda reservado mientras un admin lo revisa. Un email con tu ticket va en camino.", "review.copyLink": "Copiar enlace", "review.openTicket": "Abrir mi ticket", "admin.editUserTitle": "Editar @{user}", "admin.avatarUrl": "URL del avatar", "archived.readonly": "Este chat está archivado y es de solo lectura.", "archived.unarchive": "Desarchivar", "archived.title": "Chats archivados", "archived.empty": "No hay conversaciones archivadas.", "archived.view": "Ver", "archived.deleteTitle": "¿Eliminar este chat archivado?", "archived.deleteMsg": "Esto elimina permanentemente la conversación y todos sus mensajes.", }; const fr: Dict = { "common.close": "Fermer", "common.cancel": "Annuler", "common.save": "Enregistrer", "common.done": "Terminé", "common.copy": "Copier", "common.copied": "Copié", "common.create": "Créer", "common.delete": "Supprimer", "common.loading": "Chargement…", "common.saving": "Enregistrement…", "common.username": "Nom d'utilisateur", "common.displayName": "Nom affiché", "common.select": "Choisir…", "common.theme": "Thème", "common.themeAuto": "Auto", "common.themeAutoFull": "Auto (suit le système)", "common.themeSunset": "Coucher de soleil", "common.themeSunsetFull": "Coucher de soleil (heure locale)", "common.light": "Clair", "common.dark": "Sombre", "common.saveFailed": "Échec de l'enregistrement", "common.loadFailed": "Chargement impossible", "common.deleteFailed": "Suppression impossible", "common.backToLogin": "Retour à la connexion", "loading.tagline": "Préparation de vos chats", "login.welcome": "Bon retour", "login.subtitle": "Connectez-vous à KisAssistant pour continuer", "login.usernamePh": "Nom d'utilisateur", "login.keyPh": "Clé d'accès", "login.failed": "Échec de connexion", "login.unlock": "Déverrouiller", "login.newHere": "Nouveau ici ?", "login.register": "S'inscrire", "login.regTipOn": "Créer un compte", "login.regTipOff": "Inscription désactivée sur cette plateforme", "login.requestAccess": "Demander l'accès", "login.forgot": "Clé d'accès oubliée ?", "login.perk1": "Privé par conception — sans cookies ni suivi", "login.perk2": "Vos propres modèles, en temps réel", "login.adminNotePre": "La clé admin vit dans le .env du serveur", "login.adminNotePost": " ; les clés utilisateur sont émises par l'admin.", "login.totpIntro": "Double facteur pour @{user} — entrez le code à 6 chiffres de votre app.", "login.totpAria": "Code d'authentification", "login.totpInvalid": "Code invalide", "login.verify": "Vérifier", "sidebar.newChat": "Nouveau chat", "sidebar.collapse": "Masquer la barre latérale", "sidebar.closeDrawer": "Fermer les chats", "sidebar.searchPh": "Rechercher…", "sidebar.searchAria": "Rechercher des conversations", "sidebar.clearSearch": "Effacer la recherche", "sidebar.searching": "Recherche…", "sidebar.noMatch": "Aucun résultat pour « {q} ».", "sidebar.youPrefix": "vous : ", "sidebar.emptyA": "Aucune conversation.", "sidebar.emptyB": "Démarrez un nouveau chat ci-dessus.", "sidebar.rename": "Renommer", "sidebar.topic": "Définir le sujet", "sidebar.share": "Partager publiquement", "sidebar.delete": "Supprimer", "sidebar.convOptions": "Options de conversation", "sidebar.admin": "admin", "sidebar.adminCenter": "Centre admin", "sidebar.adminCenterTip": "Ouvrir le Centre admin", "sidebar.settingsTip": "Profil et réglages", "sidebar.logout": "Déconnexion", "sidebar.archive": "Archiver", "chat.expand": "Afficher la barre latérale", "chat.openNav": "Ouvrir les chats", "chat.modelAria": "Modèle", "chat.noModels": "Aucun modèle", "chat.selectModel": "Choisir un modèle…", "chat.personalTag": "clé personnelle", "toast.chatDeleted": "Chat “{title}” supprimé", "toast.chatArchived": "Chat “{title}” archivé", "toast.chatUnarchived": "Chat “{title}” désarchivé", "toast.modelChanged": "Modèle : {label}", "toast.themeChanged": "Thème modifié", "toast.accentChanged": "Couleur d’accent modifiée", "toast.unshared": "Lien de partage retiré", "toast.settingsSaved": "Réglages enregistrés", "toast.convUpdated": "Conversation “{title}” mise à jour", "chat.you": "Vous", "chat.greet": "Comment puis-je aider, {name} ?", "chat.greetSub": "Choisissez un modèle, puis écrivez ci-dessous. Markdown pris en charge.", "chat.thinking": "Réflexion…", "chat.placeholder": "Écrivez à KisAssistant…", "chat.send": "Envoyer", "chat.attachTip": "Joindre un fichier", "chat.removeAttach": "Retirer {name}", "chat.ocrOpt": "Transcrire une image", "chat.unavailable": "(indisponible)", "chat.ocrHint": "JPG, PNG, WEBP — OCR sur le serveur, vous relisez le texte avant", "chat.textOpt": "Joindre un fichier texte", "chat.textHint": "TXT ou Markdown, 500 Ko max", "chat.attachNote": "Les envois passent uniquement par les outils de la plateforme. Les images arrivent au modèle comme texte relu — jamais brutes.", "chat.wiki": "Wiki et docs", "attach.tooBigImg": "Image trop lourde (5 Mo max).", "attach.tooBigText": "Fichier trop lourd (500 Ko max).", "attach.uploadFailed": "Échec de l'envoi", "attach.kindOcr": "transcription d'image", "attach.kindText": "fichier texte", "attach.tpl": "[pièce jointe {kind} : {name}]", "attach.chatError": "**Erreur :** le modèle n'a pas répondu. Réessayez.", "dlg.editConv": "Modifier la conversation", "dlg.fTitle": "Titre", "dlg.fTopic": "Sujet", "dlg.topicHint": "Courte étiquette, p. ex. cuisine, rust, voyages.", "dlg.noTopic": "Sans sujet", "dlg.recoverTitle": "Récupérer la clé d'accès", "dlg.recoverIntro": "Entrez votre nom d'utilisateur. Vous recevrez un lien qui émet une clé neuve (l'ancienne cesse de fonctionner).", "dlg.sendLink": "Envoyer le lien", "dlg.reqFailed": "Requête échouée", "dlg.recoverSent": "Si @{user} existe avec un email de récupération, un lien est en route{fromPart} (valide 60 minutes, usage unique).", "dlg.recoverSentFrom": " depuis {from}", "dlg.spamNote": "Vérifiez votre boîte — et les spams, le message a pu y atterrir.", "dlg.noMailNote": "Rien ne vient ? Alors aucun email de récupération n'est défini et c'est impossible — contactez l'administrateur du site.", "dlg.registerTitle": "Créer un compte", "dlg.regWelcome": "Bienvenue, @{user} ! Votre clé d'accès — copiez-la vite, elle ne sera plus affichée.", "dlg.regContinue": "Copier et aller au login", "dlg.regFailed": "Inscription échouée", "dlg.emailOpt": "Email (optionnel, pour récupérer la clé)", "dlg.shareTitle": "Partager publiquement", "dlg.shareCreating": "Création du lien public…", "dlg.shareIntro": "Quiconque a ce lien peut lire {title}. Sans connexion.", "dlg.unshare": "Ne plus partager", "dlg.reviewOcr": "Relire la transcription", "dlg.reviewText": "Relire la pièce jointe", "dlg.transcribing": "Transcription de l'image sur le serveur (OCR)…", "dlg.uploadingTools": "Envoi via les outils de la plateforme…", "dlg.ocrExpl": "L'image a été transcrite en texte sur le serveur — le modèle reçoit ce texte, jamais l'image. Corrigez ci-dessous puis joignez.", "dlg.textExpl": "Le fichier a été envoyé via les outils de la plateforme. Son texte est joint à votre message. Modifiez si besoin puis joignez.", "dlg.truncWarn": "Transcription tronquée à la limite serveur — la fin manque.", "dlg.attachBtn": "Joindre au message", "dlg.chars": "{n} caractères", "settings.title": "Réglages", "settings.searchPh": "Rechercher…", "settings.searchAria": "Rechercher dans les réglages", "settings.clearSearch": "Effacer la recherche", "settings.noMatch": "Aucun réglage pour « {q} ».", "settings.profile": "Profil", "settings.appearance": "Apparence", "settings.features": "Fonctions", "settings.avatarUpload": "Envoyer un avatar", "settings.dropIdle": "Déposez une image ou cliquez pour envoyer", "settings.dropActive": "Lâchez !", "settings.uploading": "Envoi…", "settings.avatarHint": "jpg · png · webp — 5 Mo max — 5 changements par 2 h", "settings.username": "Nom d'utilisateur", "settings.usernameHint": "Identifiant de connexion. 2–32 car. : minuscules, chiffres, . _ -. Effet immédiat.", "settings.email": "Email de récupération", "settings.emailHint": "Optionnel. Sert uniquement à vous renvoyer une clé.", "settings.accent": "Couleur d'accent", "settings.accentHint": "Appliquée aussitôt, gardée sur cet appareil.", "settings.customColor": "Couleur perso", "settings.customAria": "Couleur d'accent personnalisée", "settings.accentAria": "Accent : {label}", "settings.featHint": "Activez les fonctions du composer. Aussitôt, sur cet appareil.", "settings.providers": "Fournisseurs", "providers.intro": "Connectez vos propres clés API. Les modèles des fournisseurs connectés apparaissent dans le menu des modèles en haut.", "providers.hint": "Les clés restent sur ce serveur et ne sont plus jamais affichées — seuls les 4 derniers caractères. L'usage est facturé sur votre propre compte, et la politique de modèles de l'admin reste en vigueur.", "providers.connected": "Connecté", "providers.notConnected": "Non connecté", "providers.keyPh": "Collez la clé API…", "providers.saveKey": "Enregistrer", "providers.removeKey": "Retirer", "providers.replaceKey": "Remplacer", "providers.getKey": "Obtenir une clé", "providers.keySaved": "Clé enregistrée — ses modèles sont dans le menu.", "providers.keyRemoved": "Clé retirée.", "providers.invalidKey": "Cette clé semble invalide (8–256 caractères, sans espaces).", "providers.loadFailed": "Impossible de charger les fournisseurs.", "accent.preset.emerald": "Émeraude", "accent.preset.rose": "Rose", "accent.preset.blue": "Bleu", "accent.preset.violet": "Violet", "accent.preset.amber": "Jaune", "accent.preset.orange": "Orange", "accent.preset.teal": "Sarcelle", "accent.preset.pink": "Rose vif", "feat.thinking.label": "Réflexion", "feat.thinking.hint": "Demande au modèle de raisonner étape par étape.", "feat.attachments.label": "Pièces jointes", "feat.attachments.hint": "Joindre images (transcription OCR) et fichiers texte.", "feat.deepSearch.label": "Recherche profonde", "feat.deepSearch.hint": "Laisse l'assistant chercher des sources fraîches. Bientôt.", "feat.soon": "(bientôt)", "admin.title": "Comptes", "admin.intro": "Comptes sans KYC : vous créez un pseudo, remettez la clé une fois. Stockées hachées — régénérez pour tourner.", "admin.keyOnce": "Clé pour @{user} — affichée une fois, copiez-la :", "admin.newAccount": "Nouveau compte", "admin.email": "Email (récupération)", "admin.createFailed": "Création impossible", "admin.regenFailed": "Régénération impossible", "admin.deleteFailed": "Suppression impossible", "admin.loadFailed": "Chargement impossible", "admin.searchPh": "Rechercher des comptes…", "admin.searchAria": "Rechercher des comptes", "admin.sortAria": "Trier les comptes", "admin.filterAria": "Filtrer les comptes", "admin.sortNewest": "Plus récents", "admin.sortOldest": "Plus anciens", "admin.sortAz": "A → Z", "admin.sortZa": "Z → A", "admin.filterAll": "Tous les comptes", "admin.filterWith": "Avec email de récupération", "admin.filterNo": "Sans email de récupération", "admin.countMany": "{total} comptes · page {page} sur {pages}", "admin.countOne": "{total} compte · page {page} sur {pages}", "admin.noEmail": "sans email de récupération", "admin.editProfile": "Modifier le profil", "admin.regenKey": "Régénérer la clé (révoque les sessions)", "admin.deleteAccount": "Supprimer le compte", "admin.deleteTitle": "Supprimer @{user} ?", "admin.deleteMsg": "Supprime définitivement le compte, ses conversations et ses partages.", "admin.prevPage": "Page précédente", "admin.nextPage": "Page suivante", "ollama.models": "Modèles Ollama", "ollama.desc": "Téléchargez des modèles Ollama sur votre hôte, ou retirez les locaux. Admin uniquement.", "ollama.pulling": "Téléchargement de {name}", "ollama.customTag": "Tag personnalisé", "ollama.customHint": "p. ex. llama3.2:3b, ou un nom de bibliothèque", "ollama.pull": "Télécharger", "ollama.installed": "Installés ({n})", "ollama.noneYet": "Aucun modèle Ollama local.", "ollama.catalog": "Catalogue", "ollama.delTitle": "Supprimer {name} ?", "ollama.delMsg": "Le modèle est retiré de l'hôte Ollama. Retéléchargez-le à tout moment.", "ollama.contacting": "Contact d'Ollama…", "ollama.pullingSt": "Téléchargement…", "ollama.doneSt": "Terminé", "ollama.pullFailed": "Échec du téléchargement", "ollama.delTip": "Supprimer {name}", "ollama.sizeAria": "taille de {name}", "ollama.cat.llama3_2": "Le compact de Meta pour tous les jours", "ollama.cat.llama3_1": "Le bourreau de travail de Meta", "ollama.cat.qwen2_5": "Polyglotte polyvalent d'Alibaba", "ollama.cat.gemma2": "Le modèle ouvert et efficace de Google", "ollama.cat.mistral": "Mistral 7B — rapide et capable", "ollama.cat.phi3": "Le petit modèle de recherche Microsoft", "ollama.cat.deepseek_r1": "Modèle de raisonnement (chaîne de pensée)", "ollama.cat.codellama": "Complétion et chat de code", "ollama.cat.llava": "Modèle de vision (comprend les images)", "ollama.cat.nomic_embed_text": "Embeddings de texte", "center.title": "Centre admin", "center.back": "Retour au chat", "center.tabAccounts": "Comptes", "center.tabFeatures": "Fonctions", "center.tabMail": "Mail", "center.tabModels": "Modèles", "models.title": "Modèles", "models.desc": "Activez ou limitez chaque modèle. Les modèles désactivés disparaissent du sélecteur ; les limites s'appliquent par utilisateur (les admins ne sont pas limités).", "models.refresh": "Actualiser", "models.refreshAria": "Actualiser les modèles", "models.enabled": "Activé", "models.hourly": "par heure", "models.daily": "par jour", "models.unlimited": "Illimité", "models.ollamaTitle": "Bibliothèque Ollama", "models.ollamaDesc": "Télécharger ou supprimer des modèles locaux.", "models.empty": "Aucun modèle disponible.", "models.saveFailed": "Enregistrement impossible", "models.connTitle": "Connectivité Ollama", "models.connDesc": "État en direct de l'hôte Ollama : joignabilité, version, latence et modèles installés.", "models.connOnline": "En ligne", "models.connOffline": "Hors ligne", "models.connDisabledState": "Désactivé", "models.connCheck": "Vérifier", "models.connCheckAria": "Vérifier la connectivité Ollama", "models.connHost": "Hôte", "models.connVersion": "Version", "models.connLatency": "Latence", "models.connModels": "Modèles", "models.connEmpty": "Aucun modèle sur l'hôte Ollama — téléchargez-en un depuis la bibliothèque ci-dessous.", "models.connHint": "Vérifiez qu'Ollama tourne et que OLLAMA_HOST pointe vers lui.", "center.locked": "Admins uniquement", "center.lockedSub": "Connectez-vous en admin pour ouvrir le Centre admin.", "center.regTitle": "Inscription publique", "center.regDesc": "Affiche le bouton S'inscrire au login. Off = comptes admin uniquement. L'activer retire la liste d'attente ci-dessous.", "center.accTitle": "Demandes d'accès (liste)", "center.accDescOn": "Indispo avec l'inscription publique — elle la remplace.", "center.accDescOff": "Affiche Demander l'accès au login. Réservez un pseudo, plaidez votre cas ; tri dans l'onglet Accès.", "center.ocrTitle": "Outil OCR", "center.ocrDesc": "Transcription d'images côté serveur. Requiert tesseract.", "center.reportsTitle": "Signalements de messages", "center.reportsDesc": "Bouton drapeau sous les réponses. Off = drapeau grisé avec infobulle et nouveaux signalements refusés.", "center.usernameChangeTitle": "Changements de pseudo", "center.usernameChangeDesc": "Permet de se renommer dans Réglages → Profil. Off = champ verrouillé (l'admin peut toujours renommer).", "settings.usernameDisabled": "Changements de pseudo désactivés par l'administrateur.", "center.mailTitle": "Emails de récupération", "center.mailChecking": "Vérification…", "center.mailFrom": "Activé — envoyés depuis {from}", "center.mailDefault": "Activé — envoyés depuis l'expéditeur configuré", "center.mailOff": "Désactivé — définissez SMTP_HOST sur le serveur", "center.mailNote": "Les identifiants SMTP vivent sur le serveur (voir docs/OPERATIONS.md), jamais ici. L'email se règle dans le profil ou à la création.", "center.on": "Oui", "center.off": "Non", "share.fallback": "Chat partagé", "share.open": "Ouvrir KisAssistant", "share.gone": "Ce chat partagé n'existe plus", "share.goneSub": "Le lien est invalide, expiré, ou l'auteur l'a retiré.", "share.back": "Retour", "share.loading": "Chargement du chat partagé…", "share.by": "par {name}", "reset.checking": "Vérification du lien…", "reset.invalid": "Lien invalide", "reset.invalidSub": "Ce lien est inconnu, déjà utilisé ou expiré. Redemandez-en un au login.", "reset.readyTitle": "Nouvelle clé pour @{user} ?", "reset.readySub": "Émet aussitôt une clé neuve. L'ancienne cesse de fonctionner.", "reset.issue": "Émettre une clé", "reset.issuing": "Émission…", "reset.doneTitle": "Nouvelle clé prête", "reset.doneSub": "Copiez-la vite — plus affichée ensuite. Connectez-vous avec.", "reset.goLogin": "Aller au login", "reset.failed": "Récupération échouée", "app.deleteConvTitle": "Supprimer la conversation ?", "app.deleteConvMsg": "« {title} » et ses messages seront supprimés.", "settings.language": "Langue", "settings.languageHint": "Langue de l'interface. Aussitôt, sur cet appareil.", "settings.security": "Sécurité", "sec.accessKey": "Clé d'accès", "sec.accessKeyHint": "Faites tourner votre clé. Les anciennes sessions sont révoquées aussitôt — reconnectez-vous avec la neuve.", "sec.rotate": "Faire tourner ma clé", "sec.rotateConfirm": "Recliquez pour confirmer", "sec.rotationFailed": "Rotation impossible", "sec.totp": "Double facteur (TOTP)", "sec.totpChecking": "Vérification…", "sec.totpAddHint": "Ajoutez un code à 6 chiffres de votre app en plus de votre clé.", "sec.totpEnable": "Activer le double facteur", "sec.totpSecretHint": "Ajoutez ce secret à votre app (ou ouvrez le lien otpauth), puis entrez un code :", "sec.totpConfirm": "Confirmer", "sec.totpDisableLabel": "Désactiver avec un code actuel", "sec.totpDisable": "Désactiver 2FA", "sec.setupFailed": "Configuration impossible", "sec.passkeys": "Passkeys", "sec.passkeysHint": "WebAuthn / authentificateurs système — requiert un RP serveur.", "sec.danger": "Zone dangereuse", "sec.dangerHint": "Supprime votre compte avec chats, partages et sessions. Irréversible.", "sec.deleteMe": "Supprimer mon compte", "sec.deleteFailed": "Suppression impossible", "sec.newKeyTitle": "Nouvelle clé d'accès", "sec.newKeyHint": "Copiez-la vite — plus affichée ensuite. Vos sessions sont révoquées.", "sec.newKeyDone": "OK — me déconnecter", "sec.deleteTitle": "Supprimer votre compte ?", "sec.deleteMsg": "Supprime définitivement votre compte avec conversations, partages et sessions. Irréversible.", "sec.deleteForever": "Supprimer pour toujours", "sec.avatarTooBig": "Fichier trop lourd (5 Mo max).", "sec.avatarType": "Seules les images jpg, png ou webp sont acceptées.", "center.tabAccess": "Accès", "center.smtpHost": "Serveur", "center.smtpPort": "Port", "center.smtpUser": "Utilisateur", "center.smtpPass": "Mot de passe", "center.smtpFrom": "Expéditeur", "center.smtpAppUrl": "URL de l'app", "center.notSet": "(non défini)", "center.noAuth": "(sans auth)", "center.tlsImplicit": "TLS implicite (465)", "center.tlsStarttls": "STARTTLS (587)", "center.showPass": "Afficher le mot de passe SMTP", "center.hidePass": "Masquer le mot de passe SMTP", "center.hiddenPass": "mot de passe masqué", "center.mailTestTitle": "Test de connexion", "center.mailTestDesc": "Vérifiez la connexion, puis envoyez un vrai mail de test à une adresse à vous.", "center.verifyConn": "Vérifier la connexion", "center.verifying": "Vérification…", "center.sendTest": "Envoyer un mail de test", "center.sendingMail": "Envoi…", "center.testToAria": "Email de test", "center.connOk": "Connexion OK — hôte SMTP joignable, auth acceptée.", "center.verifyFailed": "Vérification échouée", "center.testSent": "Mail de test envoyé à {to}{tail}.", "center.mailSendFailed": "Envoi impossible", "center.credsNote": "Les identifiants vivent sur le serveur (voir docs/OPERATIONS.md), lecture seule ici. L'email se règle dans le profil ou à la création.", "access.status.pending": "En attente", "access.status.reviewing": "En examen", "access.status.accepted": "Acceptée", "access.status.refused": "Refusée", "access.filter.all": "tous", "access.filter.pending": "en attente", "access.filter.reviewing": "en examen", "access.filter.accepted": "acceptés", "access.filter.refused": "refusés", "access.refresh": "Actualiser", "access.refreshAria": "Actualiser les demandes", "access.empty": "Aucune demande. La liste est vide.", "access.emptyFiltered": "Rien avec ce statut.", "access.msgOne": "1 message", "access.msgMany": "{n} messages", "access.accountCreated": "Compte créé — clé unique, copiez-la :", "access.replyPh": "Répondre (envoyé par email)…", "access.replyAria": "Répondre au demandeur", "access.reasonLabel": "Motif (envoyé avec la décision)", "access.reasonPh": "Motif optionnel…", "access.btnReviewing": "Examiner", "access.btnAccept": "Accepter", "access.btnRefuse": "Refuser", "access.closedNote": "Ticket fermé ({status}).", "access.reasonWord": "Motif", "access.youAdmin": "Vous (admin)", "access.sendFailed": "Envoi impossible", "access.updateFailed": "Mise à jour impossible", "live.newRequest": "New access request from @{user}", "live.newMessage": "New message from @{user}", "live.newReport": "New content report ({reason})", "live.adminReply": "Admin replied to your ticket", "live.ticketStatus": "Ticket {status}", "msg.copy": "Copier le message", "msg.search": "Rechercher sur le web", "msg.report": "Signaler la réponse", "msg.reportDisabled": "Signalements désactivés", "msg.retry": "Réessayer", "report.title": "Signaler cette réponse", "report.reason": "Motif", "report.reasonPh": "Choisir un motif…", "report.reason.copyright": "Droit d'auteur", "report.reason.gore": "Gore / CSAM / NSFW", "report.reason.falseinfo": "Fausse information", "report.reason.bug": "Bug", "report.details": "Détails (facultatif)", "report.detailsPh": "Qu'est-ce qui ne va pas avec cette réponse ? (facultatif)", "report.submit": "Envoyer le signalement", "report.sent": "Signalement envoyé — un admin va l'examiner.", "report.failed": "Envoi du signalement impossible", "center.tabReports": "Signalements", "reports.filter.all": "tous", "reports.filter.open": "ouverts", "reports.filter.reviewing": "en cours", "reports.filter.resolved": "résolus", "reports.filter.dismissed": "rejetés", "reports.status.open": "Ouvert", "reports.status.reviewing": "En cours", "reports.status.resolved": "Résolu", "reports.status.dismissed": "Rejeté", "reports.refresh": "Actualiser", "reports.refreshAria": "Actualiser les signalements", "reports.empty": "Aucun signalement pour l'instant.", "reports.emptyFiltered": "Rien avec ce statut.", "reports.response": "Réponse signalée", "reports.noContent": "Aucun contenu capturé.", "reports.model": "Modèle", "reports.reporter": "Auteur", "reports.conversation": "Conversation", "reports.detailsGiven": "Détails de l'auteur", "reports.noteLabel": "Note admin", "reports.notePh": "Note interne…", "reports.noteWord": "Note", "reports.btnReviewing": "En cours", "reports.btnResolve": "Résoudre", "reports.btnDismiss": "Rejeter", "reports.updateFailed": "Mise à jour impossible", "reports.closedNote": "Signalement {status}.", "report.alreadyReported": "Cette réponse a déjà été signalée.", "reports.prompt": "Demande de l'utilisateur", "reports.unreliable": "Signaleur non fiable", "reports.shadowban": "Shadow-ban le signaleur", "reports.unshadowban": "Lever le shadow-ban", "reports.shadowbanTitle": "Shadow-ban ce signaleur ?", "reports.shadowbanMsg": "Les signalements de @{user} seront toujours acceptés mais marqués non fiables et exclus du compteur.", "reports.unshadowbanTitle": "Lever le shadow-ban ?", "reports.unshadowbanMsg": "Les signalements de @{user} compteront à nouveau.", "review.title": "Demande d'accès", "review.loadingTicket": "Chargement du ticket…", "review.notFound": "Ticket introuvable", "review.notFoundSub": "Ce lien est inconnu. Vérifiez l'URL de votre email, ou refaites une demande depuis le login.", "review.adminNote": "Note admin :", "review.acceptedHint": "Votre compte est créé — vérifiez votre email avec la clé, puis connectez-vous.", "review.closedHint": "Ticket fermé. Les emails ne sont pas lus — si besoin, refaites une demande.", "review.replyPh": "Plaidez votre cas…", "review.replyAria": "Répondre à votre ticket", "review.mailHint": "Les emails sont à sens unique — répondez ici, pas par email.", "review.status.pending": "En attente d'examen", "review.status.reviewing": "En cours d'examen", "review.status.accepted": "Acceptée", "review.status.refused": "Refusée", "review.admin": "Admin", "review.you": "Vous", "review.requestTitle": "Demander l'accès", "review.requestIntro": "Réservez un pseudo et dites pourquoi vous voulez entrer. Un admin examine chaque demande — suivez le fil sur votre ticket et par email.", "review.emailHint": "Requis — avis et réponses arrivent ici.", "review.whyLabel": "Pourquoi vous donner l'accès ?", "review.whyHint": "10+ caractères. Ceci ouvre votre ticket.", "review.whyPh": "Je veux…", "review.emailLabel": "Email", "review.sendRequest": "Envoyer la demande", "review.received": "Demande reçue", "review.reserved": "@{user} est réservé pendant l'examen admin. Un email avec votre ticket est en route.", "review.copyLink": "Copier le lien", "review.openTicket": "Ouvrir mon ticket", "admin.editUserTitle": "Modifier @{user}", "admin.avatarUrl": "URL de l'avatar", "archived.readonly": "Ce chat est archivé en lecture seule.", "archived.unarchive": "Désarchiver", "archived.title": "Conversations archivées", "archived.empty": "Aucune conversation archivée.", "archived.view": "Voir", "archived.deleteTitle": "Supprimer cette conversation archivée ?", "archived.deleteMsg": "Cela supprime définitivement la conversation et tous ses messages.", }; const ru: Dict = { "common.close": "Закрыть", "common.cancel": "Отмена", "common.save": "Сохранить", "common.done": "Готово", "common.copy": "Копировать", "common.copied": "Скопировано", "common.create": "Создать", "common.delete": "Удалить", "common.loading": "Загрузка…", "common.saving": "Сохранение…", "common.username": "Имя пользователя", "common.displayName": "Отображаемое имя", "common.select": "Выбрать…", "common.theme": "Тема", "common.themeAuto": "Авто", "common.themeAutoFull": "Авто (как в системе)", "common.themeSunset": "Закат", "common.themeSunsetFull": "Закат (местное время)", "common.light": "Светлая", "common.dark": "Тёмная", "common.saveFailed": "Не удалось сохранить", "common.loadFailed": "Не загрузилось", "common.deleteFailed": "Не удалилось", "common.backToLogin": "Назад ко входу", "loading.tagline": "Готовим ваши чаты", "login.welcome": "С возвращением", "login.subtitle": "Войдите в KisAssistant, чтобы продолжить", "login.usernamePh": "Имя пользователя", "login.keyPh": "Ключ доступа", "login.failed": "Ошибка входа", "login.unlock": "Разблокировать", "login.newHere": "Впервые здесь?", "login.register": "Регистрация", "login.regTipOn": "Создать аккаунт", "login.regTipOff": "Регистрация на этой платформе отключена", "login.requestAccess": "Запросить доступ", "login.forgot": "Забыли ключ доступа?", "login.perk1": "Приватность по умолчанию — без cookies и трекинга", "login.perk2": "Ваши модели, в реальном времени", "login.adminNotePre": "Ключ админа живёт в .env сервера", "login.adminNotePost": "; ключи пользователей выдаёт админ.", "login.totpIntro": "Двухфакторка для @{user} — введите 6-значный код из приложения.", "login.totpAria": "Код аутентификатора", "login.totpInvalid": "Неверный код", "login.verify": "Проверить", "sidebar.newChat": "Новый чат", "sidebar.collapse": "Скрыть панель", "sidebar.closeDrawer": "Закрыть чаты", "sidebar.searchPh": "Поиск чатов…", "sidebar.searchAria": "Поиск разговоров", "sidebar.clearSearch": "Очистить поиск", "sidebar.searching": "Поиск…", "sidebar.noMatch": "По запросу «{q}» ничего нет.", "sidebar.youPrefix": "вы: ", "sidebar.emptyA": "Пока нет разговоров.", "sidebar.emptyB": "Начните новый чат выше.", "sidebar.rename": "Переименовать", "sidebar.topic": "Задать тему", "sidebar.share": "Опубликовать ссылку", "sidebar.delete": "Удалить", "sidebar.convOptions": "Действия с разговором", "sidebar.admin": "админ", "sidebar.adminCenter": "Админ-центр", "sidebar.adminCenterTip": "Открыть Админ-центр", "sidebar.settingsTip": "Профиль и настройки", "sidebar.logout": "Выйти", "sidebar.archive": "В архив", "chat.expand": "Показать панель", "chat.openNav": "Открыть чаты", "chat.modelAria": "Модель", "chat.noModels": "Нет моделей", "chat.selectModel": "Выбрать модель…", "chat.personalTag": "свой ключ", "toast.chatDeleted": "Чат “{title}” удалён", "toast.chatArchived": "Чат “{title}” архивирован", "toast.chatUnarchived": "Чат “{title}” разархивирован", "toast.modelChanged": "Модель: {label}", "toast.themeChanged": "Тема изменена", "toast.accentChanged": "Акцентный цвет изменён", "toast.unshared": "Публичная ссылка удалена", "toast.settingsSaved": "Настройки сохранены", "toast.convUpdated": "Чат “{title}” обновлён", "chat.you": "Вы", "chat.greet": "Чем помочь, {name}?", "chat.greetSub": "Выберите модель и пишите ниже. Поддерживается Markdown.", "chat.thinking": "Думаю…", "chat.placeholder": "Сообщение KisAssistant…", "chat.send": "Отправить", "chat.attachTip": "Прикрепить файл", "chat.removeAttach": "Убрать {name}", "chat.ocrOpt": "Распознать изображение", "chat.unavailable": "(недоступно)", "chat.ocrHint": "JPG, PNG, WEBP — OCR на сервере, текст вы проверите", "chat.textOpt": "Прикрепить текстовый файл", "chat.textHint": "TXT или Markdown, до 500 КБ", "chat.attachNote": "Загрузка только через инструменты платформы. Изображения уходят модели проверенным текстом — не картинками.", "chat.wiki": "Вики и доки", "attach.tooBigImg": "Изображение слишком большое (макс. 5 МБ).", "attach.tooBigText": "Файл слишком большой (макс. 500 КБ).", "attach.uploadFailed": "Ошибка загрузки", "attach.kindOcr": "распознанное изображение", "attach.kindText": "текстовый файл", "attach.tpl": "[приложено {kind}: {name}]", "attach.chatError": "**Ошибка:** модель не ответила. Попробуйте ещё.", "dlg.editConv": "Изменить разговор", "dlg.fTitle": "Название", "dlg.fTopic": "Тема", "dlg.topicHint": "Короткий тег, напр. кухня, rust, поездки.", "dlg.noTopic": "Без темы", "dlg.recoverTitle": "Восстановить ключ", "dlg.recoverIntro": "Введите имя пользователя. Вы получите ссылку на новый ключ (старый перестанет работать).", "dlg.sendLink": "Отправить ссылку", "dlg.reqFailed": "Запрос не удался", "dlg.recoverSent": "Если @{user} существует и у него есть email, ссылка уже летит{fromPart} (действует 60 минут, одноразовая).", "dlg.recoverSentFrom": " от {from}", "dlg.spamNote": "Проверьте входящие — и спам, письмо могло попасть туда.", "dlg.noMailNote": "Ничего нет? Значит, email не указан и сброс невозможен — обратитесь к администратору сайта.", "dlg.registerTitle": "Создать аккаунт", "dlg.regWelcome": "Добро пожаловать, @{user}! Ваш ключ — скопируйте сейчас, больше не покажем.", "dlg.regContinue": "Скопировать и ко входу", "dlg.regFailed": "Ошибка регистрации", "dlg.emailOpt": "Email (необязательно, для восстановления)", "dlg.shareTitle": "Опубликовать ссылку", "dlg.shareCreating": "Создаём публичную ссылку…", "dlg.shareIntro": "Каждый с этой ссылкой прочитает {title}. Вход не нужен.", "dlg.unshare": "Закрыть доступ", "dlg.reviewOcr": "Проверить распознавание", "dlg.reviewText": "Проверить вложение", "dlg.transcribing": "Распознаём изображение на сервере (OCR)…", "dlg.uploadingTools": "Загружаем через инструменты платформы…", "dlg.ocrExpl": "Изображение распознано в текст на сервере — модель получит текст, не картинку. Исправьте ошибки ниже и прикрепите.", "dlg.textExpl": "Файл загружен через инструменты платформы. Его текст прикрепится к сообщению. Правьте и прикрепляйте.", "dlg.truncWarn": "Распознавание обрезано лимитом сервера — конца нет.", "dlg.attachBtn": "Прикрепить к сообщению", "dlg.chars": "{n} символов", "settings.title": "Настройки", "settings.searchPh": "Поиск настроек…", "settings.searchAria": "Поиск настроек", "settings.clearSearch": "Очистить поиск", "settings.noMatch": "Настроек по «{q}» нет.", "settings.profile": "Профиль", "settings.appearance": "Внешний вид", "settings.features": "Функции", "settings.avatarUpload": "Загрузить аватар", "settings.dropIdle": "Перетащите картинку или нажмите для загрузки", "settings.dropActive": "Отпускайте!", "settings.uploading": "Загрузка…", "settings.avatarHint": "jpg · png · webp — до 5 МБ — 5 смен за 2 ч", "settings.username": "Имя пользователя", "settings.usernameHint": "Логин. 2–32 символа: строчные буквы, цифры, . _ -. Вступает в силу сразу.", "settings.email": "Email для восстановления", "settings.emailHint": "Необязательно. Только для отправки нового ключа.", "settings.accent": "Акцентный цвет", "settings.accentHint": "Применяется сразу, хранится на устройстве.", "settings.customColor": "Свой цвет", "settings.customAria": "Свой акцентный цвет", "settings.accentAria": "Акцент: {label}", "settings.featHint": "Включайте возможности редактора. Сразу, на этом устройстве.", "settings.providers": "Провайдеры", "providers.intro": "Подключите свои API-ключи. Модели подключённых провайдеров появятся в меню моделей сверху.", "providers.hint": "Ключи хранятся на этом сервере и больше не показываются — только последние 4 символа. Оплата идёт на ваш счёт у провайдера, политика моделей админа действует.", "providers.connected": "Подключено", "providers.notConnected": "Не подключено", "providers.keyPh": "Вставьте API-ключ…", "providers.saveKey": "Сохранить", "providers.removeKey": "Удалить", "providers.replaceKey": "Заменить", "providers.getKey": "Получить ключ", "providers.keySaved": "Ключ сохранён — модели уже в меню.", "providers.keyRemoved": "Ключ удалён.", "providers.invalidKey": "Похоже, ключ неверный (8–256 символов, без пробелов).", "providers.loadFailed": "Не удалось загрузить провайдеров.", "accent.preset.emerald": "Изумруд", "accent.preset.rose": "Роза", "accent.preset.blue": "Синий", "accent.preset.violet": "Фиолет", "accent.preset.amber": "Жёлтый", "accent.preset.orange": "Оранжевый", "accent.preset.teal": "Бирюза", "accent.preset.pink": "Розовый", "feat.thinking.label": "Размышление", "feat.thinking.hint": "Просить модель рассуждать шаг за шагом.", "feat.attachments.label": "Вложения", "feat.attachments.hint": "Картинки (OCR) и текстовые файлы к сообщениям.", "feat.deepSearch.label": "Глубокий поиск", "feat.deepSearch.hint": "Поиск свежих источников. Скоро.", "feat.soon": "(скоро)", "admin.title": "Аккаунты", "admin.intro": "Аккаунты без KYC: создаёте имя, ключ отдаёте один раз. Хранится хэш — смена ротирует.", "admin.keyOnce": "Ключ @{user} — показан раз, копируйте:", "admin.newAccount": "Новый аккаунт", "admin.email": "Email (восстановление)", "admin.createFailed": "Не создалось", "admin.regenFailed": "Не сменилось", "admin.deleteFailed": "Не удалилось", "admin.loadFailed": "Не загрузилось", "admin.searchPh": "Поиск аккаунтов…", "admin.searchAria": "Поиск аккаунтов", "admin.sortAria": "Сортировка", "admin.filterAria": "Фильтр", "admin.sortNewest": "Сначала новые", "admin.sortOldest": "Сначала старые", "admin.sortAz": "А → Я", "admin.sortZa": "Я → А", "admin.filterAll": "Все аккаунты", "admin.filterWith": "С email", "admin.filterNo": "Без email", "admin.countMany": "{total} аккаунтов · стр. {page} из {pages}", "admin.countOne": "{total} аккаунт · стр. {page} из {pages}", "admin.noEmail": "без email", "admin.editProfile": "Изменить профиль", "admin.regenKey": "Сменить ключ (убьёт сессии)", "admin.deleteAccount": "Удалить аккаунт", "admin.deleteTitle": "Удалить @{user}?", "admin.deleteMsg": "Аккаунт, разговоры и ссылки удалятся навсегда.", "admin.prevPage": "Назад", "admin.nextPage": "Вперёд", "ollama.models": "Модели Ollama", "ollama.desc": "Качайте модели на Ollama-хост или удаляйте локальные. Только админ.", "ollama.pulling": "Качаем {name}", "ollama.customTag": "Свой тег", "ollama.customHint": "напр. llama3.2:3b или имя из библиотеки", "ollama.pull": "Скачать", "ollama.installed": "Стоит ({n})", "ollama.noneYet": "Локальных моделей пока нет.", "ollama.catalog": "Каталог", "ollama.delTitle": "Удалить {name}?", "ollama.delMsg": "Модель уйдёт с хоста. Скачайте заново когда угодно.", "ollama.contacting": "Связь с Ollama…", "ollama.pullingSt": "Качаем…", "ollama.doneSt": "Готово", "ollama.pullFailed": "Не скачалось", "ollama.delTip": "Удалить {name}", "ollama.sizeAria": "размер {name}", "ollama.cat.llama3_2": "Компактная повседневная модель Meta", "ollama.cat.llama3_1": "Рабочая лошадка Meta", "ollama.cat.qwen2_5": "Многоязычный универсал Alibaba", "ollama.cat.gemma2": "Эффективная открытая модель Google", "ollama.cat.mistral": "Mistral 7B — быстрый и способный", "ollama.cat.phi3": "Маленькая исследовательская модель Microsoft", "ollama.cat.deepseek_r1": "Рассуждающая модель (цепочка мыслей)", "ollama.cat.codellama": "Код и чат про код", "ollama.cat.llava": "Зрение (понимает картинки)", "ollama.cat.nomic_embed_text": "Текстовые эмбеддинги", "center.title": "Админ-центр", "center.back": "Назад в чат", "center.tabAccounts": "Аккаунты", "center.tabFeatures": "Функции", "center.tabMail": "Почта", "center.tabModels": "Модели", "models.title": "Модели", "models.desc": "Включайте или ограничивайте каждую модель. Отключённые модели исчезают из выбора; лимиты действуют на пользователя (админов не касаются).", "models.refresh": "Обновить", "models.refreshAria": "Обновить модели", "models.enabled": "Включена", "models.hourly": "в час", "models.daily": "в день", "models.unlimited": "Без лимита", "models.ollamaTitle": "Библиотека Ollama", "models.ollamaDesc": "Скачивание и удаление локальных моделей.", "models.empty": "Нет доступных моделей.", "models.saveFailed": "Не удалось сохранить", "models.connTitle": "Подключение Ollama", "models.connDesc": "Состояние хоста Ollama: доступность, версия, задержка и модели.", "models.connOnline": "В сети", "models.connOffline": "Нет связи", "models.connDisabledState": "Отключено", "models.connCheck": "Проверить", "models.connCheckAria": "Проверить подключение Ollama", "models.connHost": "Хост", "models.connVersion": "Версия", "models.connLatency": "Задержка", "models.connModels": "Модели", "models.connEmpty": "На хосте Ollama пока нет моделей — скачайте из библиотеки ниже.", "models.connHint": "Убедитесь, что Ollama запущена и OLLAMA_HOST указывает на неё.", "center.locked": "Только админы", "center.lockedSub": "Войдите как админ, чтобы открыть центр.", "center.regTitle": "Открытая регистрация", "center.regDesc": "Кнопка регистрации на входе. Выкл = только админ создаёт. Включение закрывает лист ожидания ниже.", "center.accTitle": "Заявки (лист ожидания)", "center.accDescOn": "Недоступно при открытой регистрации — она заменяет.", "center.accDescOff": "Кнопка доступа на входе. Гости просят имя; разбор во вкладке.", "center.ocrTitle": "Инструмент OCR", "center.ocrDesc": "Распознавание картинок на сервере. Нужен tesseract.", "center.reportsTitle": "Жалобы на сообщения", "center.reportsDesc": "Кнопка-флажок под ответами. Выкл = серый флажок с подсказкой, новые жалобы отклоняются.", "center.usernameChangeTitle": "Смена имени", "center.usernameChangeDesc": "Позволяет переименоваться в Настройки → Профиль. Выкл = поле закрыто (админ всё равно может).", "settings.usernameDisabled": "Смена имени отключена администратором.", "center.mailTitle": "Письма восстановления", "center.mailChecking": "Проверяем…", "center.mailFrom": "Включено — шлём от {from}", "center.mailDefault": "Включено — шлём с настроенного адреса", "center.mailOff": "Выключено — задайте SMTP_HOST", "center.mailNote": "SMTP-доступ живёт в окружении сервера (см. docs/OPERATIONS.md), не здесь. Email задаётся в профиле или при создании.", "center.on": "Вкл", "center.off": "Выкл", "share.fallback": "Общий чат", "share.open": "Открыть KisAssistant", "share.gone": "Этот общий чат исчез", "share.goneSub": "Ссылка неверна, истекла или автор закрыл её.", "share.back": "Назад", "share.loading": "Загружаем общий чат…", "share.by": "от {name}", "reset.checking": "Проверяем ссылку…", "reset.invalid": "Ссылка неверна", "reset.invalidSub": "Ссылка неизвестна, использована или истекла. Запросите новую на входе.", "reset.readyTitle": "Новый ключ для @{user}?", "reset.readySub": "Сразу выдадим свежий ключ. Старый перестанет работать.", "reset.issue": "Выдать ключ", "reset.issuing": "Выдаём…", "reset.doneTitle": "Ключ готов", "reset.doneSub": "Скопируйте сейчас — больше не покажем. Войдите с ним.", "reset.goLogin": "Ко входу", "reset.failed": "Не вышло", "app.deleteConvTitle": "Удалить разговор?", "app.deleteConvMsg": "«{title}» и сообщения удалятся.", "settings.language": "Язык", "settings.languageHint": "Язык интерфейса. Сразу, на этом устройстве.", "settings.security": "Безопасность", "sec.accessKey": "Ключ доступа", "sec.accessKeyHint": "Смените ключ. Старые сессии отзовутся сразу — войдёте с новым.", "sec.rotate": "Сменить мой ключ", "sec.rotateConfirm": "Нажмите ещё раз для подтверждения", "sec.rotationFailed": "Не сменилось", "sec.totp": "Двухфакторка (TOTP)", "sec.totpChecking": "Проверяем…", "sec.totpAddHint": "Добавьте 6-значный код из приложения поверх ключа.", "sec.totpEnable": "Включить двухфакторку", "sec.totpSecretHint": "Добавьте секрет в приложение (или откройте otpauth-ссылку) и введите код:", "sec.totpConfirm": "Подтвердить", "sec.totpDisableLabel": "Отключить текущим кодом", "sec.totpDisable": "Выключить 2FA", "sec.setupFailed": "Не настроилось", "sec.passkeys": "Пасскеи", "sec.passkeysHint": "WebAuthn / системные ключи — нужен RP на сервере.", "sec.danger": "Опасная зона", "sec.dangerHint": "Навсегда удалит аккаунт с чатами, ссылками и сессиями. Без возврата.", "sec.deleteMe": "Удалить мой аккаунт", "sec.deleteFailed": "Не удалилось", "sec.newKeyTitle": "Новый ключ доступа", "sec.newKeyHint": "Скопируйте сейчас — больше не покажем. Старые сессии отозваны.", "sec.newKeyDone": "Готово — выйти", "sec.deleteTitle": "Удалить ваш аккаунт?", "sec.deleteMsg": "Навсегда удалит аккаунт с разговорами, ссылками и сессиями. Без возврата.", "sec.deleteForever": "Удалить навсегда", "sec.avatarTooBig": "Файл слишком большой (макс. 5 МБ).", "sec.avatarType": "Только картинки jpg, png или webp.", "center.tabAccess": "Доступ", "center.smtpHost": "Хост", "center.smtpPort": "Порт", "center.smtpUser": "Пользователь", "center.smtpPass": "Пароль", "center.smtpFrom": "Отправитель", "center.smtpAppUrl": "URL приложения", "center.notSet": "(не задано)", "center.noAuth": "(без auth)", "center.tlsImplicit": "неявный TLS (465)", "center.tlsStarttls": "STARTTLS (587)", "center.showPass": "Показать пароль SMTP", "center.hidePass": "Скрыть пароль SMTP", "center.hiddenPass": "пароль скрыт", "center.mailTestTitle": "Проверка связи", "center.mailTestDesc": "Проверьте соединение и отправьте тестовое письмо на свой адрес.", "center.verifyConn": "Проверить соединение", "center.verifying": "Проверяем…", "center.sendTest": "Отправить тест", "center.sendingMail": "Отправляем…", "center.testToAria": "Адрес для теста", "center.connOk": "Связь OK — SMTP доступен, auth принят.", "center.verifyFailed": "Проверка не удалась", "center.testSent": "Тест отправлен на {to}{tail}.", "center.mailSendFailed": "Не отправилось", "center.credsNote": "Доступы живут в окружении сервера (см. docs/OPERATIONS.md), здесь только чтение. Email задаётся в профиле или при создании.", "access.status.pending": "Ожидает", "access.status.reviewing": "На проверке", "access.status.accepted": "Принята", "access.status.refused": "Отклонена", "access.filter.all": "все", "access.filter.pending": "ожидают", "access.filter.reviewing": "на проверке", "access.filter.accepted": "приняты", "access.filter.refused": "отклонены", "access.refresh": "Обновить", "access.refreshAria": "Обновить заявки", "access.empty": "Заявок нет. Лист ожидания пуст.", "access.emptyFiltered": "С таким статусом ничего нет.", "access.msgOne": "1 сообщение", "access.msgMany": "{n} сообщений", "access.accountCreated": "Аккаунт создан — ключ показан раз, копируйте:", "access.replyPh": "Ответить (уйдёт по email)…", "access.replyAria": "Ответить заявителю", "access.reasonLabel": "Причина (уйдёт с решением)", "access.reasonPh": "Необязательная причина…", "access.btnReviewing": "На проверку", "access.btnAccept": "Принять", "access.btnRefuse": "Отклонить", "access.closedNote": "Тикет закрыт ({status}).", "access.reasonWord": "Причина", "access.youAdmin": "Вы (админ)", "access.sendFailed": "Не отправилось", "access.updateFailed": "Не обновилось", "live.newRequest": "New access request from @{user}", "live.newMessage": "New message from @{user}", "live.newReport": "New content report ({reason})", "live.adminReply": "Admin replied to your ticket", "live.ticketStatus": "Ticket {status}", "msg.copy": "Скопировать сообщение", "msg.search": "Искать в интернете", "msg.report": "Пожаловаться на ответ", "msg.reportDisabled": "Жалобы отключены", "msg.retry": "Повторить", "report.title": "Пожаловаться на этот ответ", "report.reason": "Причина", "report.reasonPh": "Выберите причину…", "report.reason.copyright": "Авторские права", "report.reason.gore": "Gore / CSAM / NSFW", "report.reason.falseinfo": "Ложная информация", "report.reason.bug": "Ошибка", "report.details": "Подробности (необязательно)", "report.detailsPh": "Что не так с этим ответом? (необязательно)", "report.submit": "Отправить жалобу", "report.sent": "Жалоба отправлена — администратор её рассмотрит.", "report.failed": "Не удалось отправить жалобу", "center.tabReports": "Жалобы", "reports.filter.all": "все", "reports.filter.open": "открытые", "reports.filter.reviewing": "на рассмотрении", "reports.filter.resolved": "решённые", "reports.filter.dismissed": "отклонённые", "reports.status.open": "Открыта", "reports.status.reviewing": "На рассмотрении", "reports.status.resolved": "Решена", "reports.status.dismissed": "Отклонена", "reports.refresh": "Обновить", "reports.refreshAria": "Обновить жалобы", "reports.empty": "Жалоб на контент пока нет.", "reports.emptyFiltered": "Ничего с этим статусом.", "reports.response": "Ответ", "reports.noContent": "Содержимое отсутствует.", "reports.model": "Модель", "reports.reporter": "Автор жалобы", "reports.conversation": "Беседа", "reports.detailsGiven": "Комментарий автора", "reports.noteLabel": "Заметка админа", "reports.notePh": "Внутренняя заметка…", "reports.noteWord": "Заметка", "reports.btnReviewing": "На рассмотрение", "reports.btnResolve": "Решить", "reports.btnDismiss": "Отклонить", "reports.updateFailed": "Не обновилось", "reports.closedNote": "Жалоба {status}.", "report.alreadyReported": "На этот ответ уже пожаловались.", "reports.prompt": "Запрос пользователя", "reports.unreliable": "Ненадёжный автор", "reports.shadowban": "Теневой бан", "reports.unshadowban": "Снять теневой бан", "reports.shadowbanTitle": "Теневой бан для этого автора?", "reports.shadowbanMsg": "Жалобы @{user} будут приниматься, но помечаться как ненадёжные и не учитываться в счётчике.", "reports.unshadowbanTitle": "Снять теневой бан?", "reports.unshadowbanMsg": "Жалобы @{user} снова будут учитываться.", "review.title": "Заявка на доступ", "review.loadingTicket": "Загружаем тикет…", "review.notFound": "Тикет не найден", "review.notFoundSub": "Ссылка неизвестна. Проверьте URL из письма или подайте заявку со входа.", "review.adminNote": "Заметка админа:", "review.acceptedHint": "Аккаунт создан — проверьте письмо с ключом и входите.", "review.closedHint": "Тикет закрыт. Письма не читаются — при нужде подайте новую заявку.", "review.replyPh": "Изложите свой случай…", "review.replyAria": "Ответить в тикет", "review.mailHint": "Письма — в одну сторону: отвечайте здесь, не по email.", "review.status.pending": "Ждёт проверки", "review.status.reviewing": "На проверке", "review.status.accepted": "Принята", "review.status.refused": "Отклонена", "review.admin": "Админ", "review.you": "Вы", "review.requestTitle": "Запросить доступ", "review.requestIntro": "Займите имя и расскажите, зачем вам сюда. Админ разберёт каждую заявку — следите за тикетом и почтой.", "review.emailHint": "Обязательно — статусы и ответы придут сюда.", "review.whyLabel": "Почему вам дать доступ?", "review.whyHint": "10+ символов. Это откроет тикет.", "review.whyPh": "Я хочу…", "review.emailLabel": "Email", "review.sendRequest": "Отправить заявку", "review.received": "Заявка получена", "review.reserved": "@{user} занято вами, пока админ проверяет. Письмо с тикетом уже летит.", "review.copyLink": "Копировать ссылку", "review.openTicket": "Открыть мой тикет", "admin.editUserTitle": "Править @{user}", "admin.avatarUrl": "URL аватара", "archived.readonly": "Чат в архиве, только чтение.", "archived.unarchive": "Вернуть", "archived.title": "Архив чатов", "archived.empty": "Нет архивных чатов.", "archived.view": "Открыть", "archived.deleteTitle": "Удалить этот архивный чат?", "archived.deleteMsg": "Это навсегда удалит разговор и все сообщения.", }; const it: Dict = { "common.close": "Chiudi", "common.cancel": "Annulla", "common.save": "Salva", "common.done": "Fatto", "common.copy": "Copia", "common.copied": "Copiato", "common.create": "Crea", "common.delete": "Elimina", "common.loading": "Caricamento…", "common.saving": "Salvataggio…", "common.username": "Nome utente", "common.displayName": "Nome visualizzato", "common.select": "Scegli…", "common.theme": "Tema", "common.themeAuto": "Auto", "common.themeAutoFull": "Auto (segue il sistema)", "common.themeSunset": "Tramonto", "common.themeSunsetFull": "Tramonto (ora locale)", "common.light": "Chiaro", "common.dark": "Scuro", "common.saveFailed": "Salvataggio non riuscito", "common.loadFailed": "Caricamento fallito", "common.deleteFailed": "Eliminazione fallita", "common.backToLogin": "Torna al login", "loading.tagline": "Preparo le tue chat", "login.welcome": "Bentornato", "login.subtitle": "Accedi a KisAssistant per continuare", "login.usernamePh": "Nome utente", "login.keyPh": "Chiave di accesso", "login.failed": "Accesso non riuscito", "login.unlock": "Sblocca", "login.newHere": "Nuovo qui?", "login.register": "Registrati", "login.regTipOn": "Crea un account", "login.regTipOff": "Registrazione disattivata su questa piattaforma", "login.requestAccess": "Richiedi accesso", "login.forgot": "Chiave dimenticata?", "login.perk1": "Privato per progettazione — niente cookie né traccianti", "login.perk2": "I tuoi modelli, in tempo reale", "login.adminNotePre": "La chiave admin vive nel .env del server", "login.adminNotePost": "; le chiavi utente le emette l'admin.", "login.totpIntro": "Doppio fattore per @{user} — inserisci il codice a 6 cifre dall'app.", "login.totpAria": "Codice autenticatore", "login.totpInvalid": "Codice non valido", "login.verify": "Verifica", "sidebar.newChat": "Nuova chat", "sidebar.collapse": "Nascondi barra laterale", "sidebar.closeDrawer": "Chiudi chat", "sidebar.searchPh": "Cerca chat…", "sidebar.searchAria": "Cerca conversazioni", "sidebar.clearSearch": "Cancella ricerca", "sidebar.searching": "Ricerca…", "sidebar.noMatch": "Nessun risultato per «{q}».", "sidebar.youPrefix": "tu: ", "sidebar.emptyA": "Nessuna conversazione.", "sidebar.emptyB": "Inizia una nuova chat qui sopra.", "sidebar.rename": "Rinomina", "sidebar.topic": "Imposta argomento", "sidebar.share": "Condividi pubblicamente", "sidebar.delete": "Elimina", "sidebar.convOptions": "Opzioni conversazione", "sidebar.admin": "admin", "sidebar.adminCenter": "Centro admin", "sidebar.adminCenterTip": "Apri il Centro admin", "sidebar.settingsTip": "Profilo e impostazioni", "sidebar.logout": "Esci", "sidebar.archive": "Archivia", "chat.expand": "Mostra barra laterale", "chat.openNav": "Apri chat", "chat.modelAria": "Modello", "chat.noModels": "Nessun modello", "chat.selectModel": "Scegli modello…", "chat.personalTag": "chiave personale", "toast.chatDeleted": "Chat “{title}” eliminata", "toast.chatArchived": "Chat “{title}” archiviata", "toast.chatUnarchived": "Chat “{title}” disarchiviata", "toast.modelChanged": "Modello: {label}", "toast.themeChanged": "Tema cambiato", "toast.accentChanged": "Colore accento cambiato", "toast.unshared": "Link condiviso rimosso", "toast.settingsSaved": "Impostazioni salvate", "toast.convUpdated": "Conversazione “{title}” aggiornata", "chat.you": "Tu", "chat.greet": "Come posso aiutare, {name}?", "chat.greetSub": "Scegli un modello e scrivi qui sotto. Markdown supportato.", "chat.thinking": "Penso…", "chat.placeholder": "Scrivi a KisAssistant…", "chat.send": "Invia", "chat.attachTip": "Allega un file", "chat.removeAttach": "Rimuovi {name}", "chat.ocrOpt": "Trascrivi immagine", "chat.unavailable": "(non disponibile)", "chat.ocrHint": "JPG, PNG, WEBP — OCR sul server, rivedi il testo prima", "chat.textOpt": "Allega file di testo", "chat.textHint": "TXT o Markdown, max 500KB", "chat.attachNote": "Upload solo con gli strumenti della piattaforma. Le immagini arrivano al modello come testo rivisto — mai grezze.", "chat.wiki": "Wiki e docs", "attach.tooBigImg": "Immagine troppo grande (max 5MB).", "attach.tooBigText": "File troppo grande (max 500KB).", "attach.uploadFailed": "Upload non riuscito", "attach.kindOcr": "trascrizione immagine", "attach.kindText": "file di testo", "attach.tpl": "[allegato {kind}: {name}]", "attach.chatError": "**Errore:** il modello non ha risposto. Riprova.", "dlg.editConv": "Modifica conversazione", "dlg.fTitle": "Titolo", "dlg.fTopic": "Argomento", "dlg.topicHint": "Breve etichetta, es. cucina, rust, viaggi.", "dlg.noTopic": "Nessun argomento", "dlg.recoverTitle": "Recupera chiave di accesso", "dlg.recoverIntro": "Inserisci il tuo nome utente. Riceverai un link che emette una chiave nuova (la vecchia smette di funzionare).", "dlg.sendLink": "Invia link", "dlg.reqFailed": "Richiesta fallita", "dlg.recoverSent": "Se @{user} esiste con email di recupero, un link è in viaggio{fromPart} (valido 60 minuti, monouso).", "dlg.recoverSentFrom": " da {from}", "dlg.spamNote": "Controlla la posta — e lo spam, potrebbe essere finito lì.", "dlg.noMailNote": "Nulla in arrivo? Allora nessuna email è impostata e non si può — contatta l'amministratore del sito.", "dlg.registerTitle": "Crea un account", "dlg.regWelcome": "Benvenuto, @{user}! La tua chiave — copiala ora, non sarà più mostrata.", "dlg.regContinue": "Copia e vai al login", "dlg.regFailed": "Registrazione fallita", "dlg.emailOpt": "Email (opzionale, per recupero chiave)", "dlg.shareTitle": "Condividi pubblicamente", "dlg.shareCreating": "Creazione link pubblico…", "dlg.shareIntro": "Chiunque abbia questo link può leggere {title}. Senza login.", "dlg.unshare": "Rimuovi condivisione", "dlg.reviewOcr": "Rivedi trascrizione", "dlg.reviewText": "Rivedi allegato", "dlg.transcribing": "Trascrizione immagine sul server (OCR)…", "dlg.uploadingTools": "Upload con gli strumenti…", "dlg.ocrExpl": "L'immagine è stata trascritta in testo sul server — il modello riceve questo testo, mai l'immagine. Correggi sotto e allega.", "dlg.textExpl": "Il file è stato caricato con gli strumenti. Il suo testo sarà allegato al messaggio. Modifica e allega.", "dlg.truncWarn": "Trascrizione troncata al limite server — manca la fine.", "dlg.attachBtn": "Allega al messaggio", "dlg.chars": "{n} caratteri", "settings.title": "Impostazioni", "settings.searchPh": "Cerca impostazioni…", "settings.searchAria": "Cerca impostazioni", "settings.clearSearch": "Cancella ricerca", "settings.noMatch": "Nessuna impostazione per «{q}».", "settings.profile": "Profilo", "settings.appearance": "Aspetto", "settings.features": "Funzioni", "settings.avatarUpload": "Carica avatar", "settings.dropIdle": "Trascina un'immagine o clicca per caricare", "settings.dropActive": "Lascia!", "settings.uploading": "Caricamento…", "settings.avatarHint": "jpg · png · webp — max 5MB — 5 cambi per 2h", "settings.username": "Nome utente", "settings.usernameHint": "Nome di accesso. 2–32 caratteri: minuscole, cifre, . _ -. Effetto immediato.", "settings.email": "Email di recupero", "settings.emailHint": "Opzionale. Solo per inviarti una chiave nuova.", "settings.accent": "Colore accento", "settings.accentHint": "Subito attivo, salvato su questo dispositivo.", "settings.customColor": "Colore custom", "settings.customAria": "Colore accento personalizzato", "settings.accentAria": "Accento: {label}", "settings.featHint": "Attiva funzioni del composer. Subito, su questo dispositivo.", "settings.providers": "Provider", "providers.intro": "Collega le tue chiavi API. I modelli dei provider collegati appaiono nel menu dei modelli in alto.", "providers.hint": "Le chiavi restano su questo server e non vengono più mostrate — solo gli ultimi 4 caratteri. L'uso è addebitato al tuo account e resta valida la policy modelli dell'admin.", "providers.connected": "Collegato", "providers.notConnected": "Non collegato", "providers.keyPh": "Incolla la chiave API…", "providers.saveKey": "Salva", "providers.removeKey": "Rimuovi", "providers.replaceKey": "Sostituisci", "providers.getKey": "Ottieni chiave", "providers.keySaved": "Chiave salvata — i modelli sono nel menu.", "providers.keyRemoved": "Chiave rimossa.", "providers.invalidKey": "Chiave non valida (8–256 caratteri, senza spazi).", "providers.loadFailed": "Impossibile caricare i provider.", "accent.preset.emerald": "Smeraldo", "accent.preset.rose": "Rosa", "accent.preset.blue": "Blu", "accent.preset.violet": "Viola", "accent.preset.amber": "Giallo", "accent.preset.orange": "Arancione", "accent.preset.teal": "Teal", "accent.preset.pink": "Rosa shocking", "feat.thinking.label": "Ragionamento", "feat.thinking.hint": "Chiedi al modello di ragionare passo passo.", "feat.attachments.label": "Allegati", "feat.attachments.hint": "Allega immagini (trascrizione OCR) e file di testo.", "feat.deepSearch.label": "Ricerca profonda", "feat.deepSearch.hint": "Cerca fonti fresche sul web. Prossimamente.", "feat.soon": "(presto)", "admin.title": "Account", "admin.intro": "Account senza KYC: crei un nome, consegni la chiave una volta. Hash salvati — rigenera per ruotare.", "admin.keyOnce": "Chiave per @{user} — mostrata una volta, copiala ora:", "admin.newAccount": "Nuovo account", "admin.email": "Email (recupero)", "admin.createFailed": "Creazione fallita", "admin.regenFailed": "Rigenerazione fallita", "admin.deleteFailed": "Eliminazione fallita", "admin.loadFailed": "Caricamento fallito", "admin.searchPh": "Cerca account…", "admin.searchAria": "Cerca account", "admin.sortAria": "Ordina account", "admin.filterAria": "Filtra account", "admin.sortNewest": "Più recenti", "admin.sortOldest": "Meno recenti", "admin.sortAz": "A → Z", "admin.sortZa": "Z → A", "admin.filterAll": "Tutti gli account", "admin.filterWith": "Con email di recupero", "admin.filterNo": "Senza email di recupero", "admin.countMany": "{total} account · pagina {page} di {pages}", "admin.countOne": "{total} account · pagina {page} di {pages}", "admin.noEmail": "senza email di recupero", "admin.editProfile": "Modifica profilo", "admin.regenKey": "Rigenera chiave (revoca sessioni)", "admin.deleteAccount": "Elimina account", "admin.deleteTitle": "Eliminare @{user}?", "admin.deleteMsg": "Rimuove per sempre account, conversazioni e condivisioni.", "admin.prevPage": "Pagina precedente", "admin.nextPage": "Pagina successiva", "ollama.models": "Modelli Ollama", "ollama.desc": "Scarica modelli sull'host Ollama o rimuovi i locali. Solo admin.", "ollama.pulling": "Scarico {name}", "ollama.customTag": "Tag personalizzato", "ollama.customHint": "es. llama3.2:3b, o un nome di libreria", "ollama.pull": "Scarica", "ollama.installed": "Installati ({n})", "ollama.noneYet": "Ancora nessun modello locale.", "ollama.catalog": "Catalogo", "ollama.delTitle": "Eliminare {name}?", "ollama.delMsg": "Il modello lascia l'host Ollama. Riscaricalo quando vuoi.", "ollama.contacting": "Contatto Ollama…", "ollama.pullingSt": "Scarico…", "ollama.doneSt": "Fatto", "ollama.pullFailed": "Download fallito", "ollama.delTip": "Elimina {name}", "ollama.sizeAria": "taglia di {name}", "ollama.cat.llama3_2": "Il compatto Meta per tutti i giorni", "ollama.cat.llama3_1": "Il cavallo di battaglia Meta", "ollama.cat.qwen2_5": "Multilingua Alibaba tuttofare", "ollama.cat.gemma2": "L'open efficiente di Google", "ollama.cat.mistral": "Mistral 7B — veloce e capace", "ollama.cat.phi3": "Il piccolo modello Microsoft", "ollama.cat.deepseek_r1": "Modello di ragionamento (catena di pensiero)", "ollama.cat.codellama": "Completamento e chat di codice", "ollama.cat.llava": "Visione (capisce le immagini)", "ollama.cat.nomic_embed_text": "Embedding di testo", "center.title": "Centro admin", "center.back": "Torna alla chat", "center.tabAccounts": "Account", "center.tabFeatures": "Funzioni", "center.tabMail": "Mail", "center.tabModels": "Modelli", "models.title": "Modelli", "models.desc": "Abilita o limita ogni modello. I modelli disabilitati spariscono dal selettore; i limiti valgono per utente (gli admin sono esenti).", "models.refresh": "Aggiorna", "models.refreshAria": "Aggiorna modelli", "models.enabled": "Abilitato", "models.hourly": "all'ora", "models.daily": "al giorno", "models.unlimited": "Illimitato", "models.ollamaTitle": "Libreria Ollama", "models.ollamaDesc": "Scarica o rimuovi modelli locali.", "models.empty": "Nessun modello disponibile.", "models.saveFailed": "Salvataggio fallito", "models.connTitle": "Connettività Ollama", "models.connDesc": "Stato live dell'host Ollama: raggiungibilità, versione, latenza e modelli installati.", "models.connOnline": "Online", "models.connOffline": "Offline", "models.connDisabledState": "Disabilitato", "models.connCheck": "Verifica ora", "models.connCheckAria": "Verifica la connettività Ollama", "models.connHost": "Host", "models.connVersion": "Versione", "models.connLatency": "Latenza", "models.connModels": "Modelli", "models.connEmpty": "Nessun modello sull'host Ollama — scaricane uno dalla libreria qui sotto.", "models.connHint": "Assicurati che Ollama sia in esecuzione e che OLLAMA_HOST punti a esso.", "center.locked": "Solo admin", "center.lockedSub": "Accedi come admin per aprire il Centro.", "center.regTitle": "Registrazione pubblica", "center.regDesc": "Mostra Registrati al login. Off = solo account admin. Attivarlo ritira la wishlist qui sotto.", "center.accTitle": "Richieste di accesso (lista)", "center.accDescOn": "Non disponibile con registrazione pubblica — la sostituisce.", "center.accDescOff": "Mostra Richiedi accesso al login. Valuta nella scheda Accesso.", "center.ocrTitle": "Strumento OCR", "center.ocrDesc": "Trascrizione immagini sul server. Serve tesseract.", "center.reportsTitle": "Segnalazioni messaggi", "center.reportsDesc": "Bandierina sotto le risposte. Off = bandierina grigia con tooltip e nuove segnalazioni rifiutate.", "center.usernameChangeTitle": "Cambi di nome", "center.usernameChangeDesc": "Permette di rinominarsi in Impostazioni → Profilo. Off = campo bloccato (l'admin può ancora rinominare).", "settings.usernameDisabled": "Cambi di nome disabilitati dall'amministratore.", "center.mailTitle": "Email di recupero", "center.mailChecking": "Verifica…", "center.mailFrom": "Attivo — inviate da {from}", "center.mailDefault": "Attivo — inviate dal mittente configurato", "center.mailOff": "Disattivo — imposta SMTP_HOST sul server", "center.mailNote": "Le credenziali SMTP vivono sul server (vedi docs/OPERATIONS.md), mai qui. L'email si imposta nel profilo o alla creazione.", "center.on": "Sì", "center.off": "No", "share.fallback": "Chat condivisa", "share.open": "Apri KisAssistant", "share.gone": "Questa chat condivisa non c'è più", "share.goneSub": "Il link non vale, è scaduto o l'autore l'ha tolto.", "share.back": "Indietro", "share.loading": "Carico chat condivisa…", "share.by": "di {name}", "reset.checking": "Verifica link…", "reset.invalid": "Link non valido", "reset.invalidSub": "Link sconosciuto, usato o scaduto. Richiedine uno al login.", "reset.readyTitle": "Nuova chiave per @{user}?", "reset.readySub": "Emette subito una chiave nuova. La vecchia smette di funzionare.", "reset.issue": "Emetti nuova chiave", "reset.issuing": "Emissione…", "reset.doneTitle": "Nuova chiave pronta", "reset.doneSub": "Copiala ora — non sarà più mostrata. Accedi con essa.", "reset.goLogin": "Vai al login", "reset.failed": "Recupero fallito", "app.deleteConvTitle": "Eliminare conversazione?", "app.deleteConvMsg": "«{title}» e i suoi messaggi saranno eliminati.", "settings.language": "Lingua", "settings.languageHint": "Lingua dell'interfaccia. Subito, su questo dispositivo.", "settings.security": "Sicurezza", "sec.accessKey": "Chiave di accesso", "sec.accessKeyHint": "Ruota la tua chiave. Le vecchie sessioni sono revocate subito — rientri con la nuova.", "sec.rotate": "Ruota la mia chiave", "sec.rotateConfirm": "Clicca di nuovo per confermare", "sec.rotationFailed": "Rotazione fallita", "sec.totp": "Doppio fattore (TOTP)", "sec.totpChecking": "Verifica…", "sec.totpAddHint": "Aggiungi un codice a 6 cifre dall'app oltre alla chiave.", "sec.totpEnable": "Attiva doppio fattore", "sec.totpSecretHint": "Aggiungi questo segreto all'app (o apri il link otpauth) e inserisci un codice:", "sec.totpConfirm": "Conferma", "sec.totpDisableLabel": "Disattiva con un codice attuale", "sec.totpDisable": "Disattiva 2FA", "sec.setupFailed": "Configurazione fallita", "sec.passkeys": "Passkey", "sec.passkeysHint": "WebAuthn / autenticatori di sistema — serve RP sul server.", "sec.danger": "Zona pericolosa", "sec.dangerHint": "Elimina account con chat, condivisioni e sessioni. Irreversibile.", "sec.deleteMe": "Elimina il mio account", "sec.deleteFailed": "Eliminazione fallita", "sec.newKeyTitle": "Nuova chiave di accesso", "sec.newKeyHint": "Copiala ora — non sarà più mostrata. Le vecchie sessioni sono revocate.", "sec.newKeyDone": "Fatto — esci", "sec.deleteTitle": "Eliminare il tuo account?", "sec.deleteMsg": "Rimuove per sempre account con conversazioni, condivisioni e sessioni. Irreversibile.", "sec.deleteForever": "Elimina per sempre", "sec.avatarTooBig": "File troppo grande (max 5MB).", "sec.avatarType": "Solo immagini jpg, png o webp.", "center.tabAccess": "Accesso", "center.smtpHost": "Host", "center.smtpPort": "Porta", "center.smtpUser": "Utente", "center.smtpPass": "Password", "center.smtpFrom": "Mittente", "center.smtpAppUrl": "URL dell'app", "center.notSet": "(non impostato)", "center.noAuth": "(senza auth)", "center.tlsImplicit": "TLS implicito (465)", "center.tlsStarttls": "STARTTLS (587)", "center.showPass": "Mostra password SMTP", "center.hidePass": "Nascondi password SMTP", "center.hiddenPass": "password nascosta", "center.mailTestTitle": "Test di connessione", "center.mailTestDesc": "Verifica la connessione e invia una vera mail di test a un tuo indirizzo.", "center.verifyConn": "Verifica connessione", "center.verifying": "Verifica…", "center.sendTest": "Invia mail di test", "center.sendingMail": "Invio…", "center.testToAria": "Email di test", "center.connOk": "Connessione OK — host SMTP raggiungibile, auth accettata.", "center.verifyFailed": "Verifica fallita", "center.testSent": "Mail di test inviata a {to}{tail}.", "center.mailSendFailed": "Invio fallito", "center.credsNote": "Le credenziali vivono sul server (vedi docs/OPERATIONS.md), qui sola lettura. L'email si imposta nel profilo o alla creazione.", "access.status.pending": "In attesa", "access.status.reviewing": "In revisione", "access.status.accepted": "Accettata", "access.status.refused": "Rifiutata", "access.filter.all": "tutti", "access.filter.pending": "in attesa", "access.filter.reviewing": "in revisione", "access.filter.accepted": "accettati", "access.filter.refused": "rifiutati", "access.refresh": "Aggiorna", "access.refreshAria": "Aggiorna richieste", "access.empty": "Nessuna richiesta. La lista è vuota.", "access.emptyFiltered": "Nulla con questo stato.", "access.msgOne": "1 messaggio", "access.msgMany": "{n} messaggi", "access.accountCreated": "Account creato — chiave unica, copiala ora:", "access.replyPh": "Rispondi (arriva via email)…", "access.replyAria": "Rispondi al richiedente", "access.reasonLabel": "Motivo (inviato con la decisione)", "access.reasonPh": "Motivo opzionale…", "access.btnReviewing": "In revisione", "access.btnAccept": "Accetta", "access.btnRefuse": "Rifiuta", "access.closedNote": "Ticket chiuso ({status}).", "access.reasonWord": "Motivo", "access.youAdmin": "Tu (admin)", "access.sendFailed": "Invio fallito", "access.updateFailed": "Aggiornamento fallito", "live.newRequest": "New access request from @{user}", "live.newMessage": "New message from @{user}", "live.newReport": "New content report ({reason})", "live.adminReply": "Admin replied to your ticket", "live.ticketStatus": "Ticket {status}", "msg.copy": "Copia messaggio", "msg.search": "Cerca nel web", "msg.report": "Segnala risposta", "msg.reportDisabled": "Segnalazioni disabilitate", "msg.retry": "Riprova", "report.title": "Segnala questa risposta", "report.reason": "Motivo", "report.reasonPh": "Seleziona un motivo…", "report.reason.copyright": "Diritto d'autore", "report.reason.gore": "Gore / CSAM / NSFW", "report.reason.falseinfo": "Informazione falsa", "report.reason.bug": "Bug", "report.details": "Dettagli (facoltativo)", "report.detailsPh": "Cosa c'è di sbagliato in questa risposta? (facoltativo)", "report.submit": "Invia segnalazione", "report.sent": "Segnalazione inviata — un amministratore la esaminerà.", "report.failed": "Impossibile inviare la segnalazione", "center.tabReports": "Segnalazioni", "reports.filter.all": "tutte", "reports.filter.open": "aperte", "reports.filter.reviewing": "in revisione", "reports.filter.resolved": "risolte", "reports.filter.dismissed": "respinte", "reports.status.open": "Aperta", "reports.status.reviewing": "In revisione", "reports.status.resolved": "Risolta", "reports.status.dismissed": "Respinta", "reports.refresh": "Aggiorna", "reports.refreshAria": "Aggiorna segnalazioni", "reports.empty": "Nessuna segnalazione ancora.", "reports.emptyFiltered": "Niente con questo stato.", "reports.response": "Risposta segnalata", "reports.noContent": "Nessun contenuto acquisito.", "reports.model": "Modello", "reports.reporter": "Autore", "reports.conversation": "Conversazione", "reports.detailsGiven": "Dettagli dell'autore", "reports.noteLabel": "Nota admin", "reports.notePh": "Nota interna…", "reports.noteWord": "Nota", "reports.btnReviewing": "In revisione", "reports.btnResolve": "Risolvi", "reports.btnDismiss": "Respingi", "reports.updateFailed": "Aggiornamento fallito", "reports.closedNote": "Segnalazione {status}.", "report.alreadyReported": "Questa risposta è già stata segnalata.", "reports.prompt": "Richiesta dell'utente", "reports.unreliable": "Autore inaffidabile", "reports.shadowban": "Silenzia autore", "reports.unshadowban": "Togli silenzio", "reports.shadowbanTitle": "Silenziare questo autore?", "reports.shadowbanMsg": "Le segnalazioni di @{user} saranno comunque accettate ma marcate come inaffidabili ed escluse dal conteggio.", "reports.unshadowbanTitle": "Togliere il silenzio?", "reports.unshadowbanMsg": "Le segnalazioni di @{user} conteranno di nuovo.", "review.title": "Richiesta di accesso", "review.loadingTicket": "Carico ticket…", "review.notFound": "Ticket non trovato", "review.notFoundSub": "Link sconosciuto. Controlla l'URL della tua email o richiedi accesso dal login.", "review.adminNote": "Nota admin:", "review.acceptedHint": "Account creato — controlla l'email con la chiave e accedi.", "review.closedHint": "Ticket chiuso. Le email non si leggono — se serve, richiedi di nuovo.", "review.replyPh": "Perora la tua causa…", "review.replyAria": "Rispondi al tuo ticket", "review.mailHint": "Le email sono a senso unico — rispondi qui, non via email.", "review.status.pending": "In attesa di revisione", "review.status.reviewing": "In revisione", "review.status.accepted": "Accettata", "review.status.refused": "Rifiutata", "review.admin": "Admin", "review.you": "Tu", "review.requestTitle": "Richiedi accesso", "review.requestIntro": "Riserva un nome e spiega perché vuoi entrare. Un admin valuta ogni richiesta — segui il filo sul tuo ticket e via email.", "review.emailHint": "Obbligatoria — avvisi e risposte arrivano qui.", "review.whyLabel": "Perché darti accesso?", "review.whyHint": "10+ caratteri. Questo apre il tuo ticket.", "review.whyPh": "Voglio…", "review.emailLabel": "Email", "review.sendRequest": "Invia richiesta", "review.received": "Richiesta ricevuta", "review.reserved": "@{user} è riservato mentre un admin valuta. Un'email col tuo ticket è in viaggio.", "review.copyLink": "Copia link", "review.openTicket": "Apri il mio ticket", "admin.editUserTitle": "Modifica @{user}", "admin.avatarUrl": "URL avatar", "archived.readonly": "Questa chat è archiviata e in sola lettura.", "archived.unarchive": "Ripristina", "archived.title": "Chat archiviate", "archived.empty": "Nessuna conversazione archiviata.", "archived.view": "Vedi", "archived.deleteTitle": "Eliminare questa chat archiviata?", "archived.deleteMsg": "Questo rimuove definitivamente la conversazione e tutti i suoi messaggi.", }; const pt: Dict = { "common.close": "Fechar", "common.cancel": "Cancelar", "common.save": "Guardar", "common.done": "Pronto", "common.copy": "Copiar", "common.copied": "Copiado", "common.create": "Criar", "common.delete": "Eliminar", "common.loading": "A carregar…", "common.saving": "A guardar…", "common.username": "Nome de utilizador", "common.displayName": "Nome visível", "common.select": "Escolher…", "common.theme": "Tema", "common.themeAuto": "Auto", "common.themeAutoFull": "Auto (segue o sistema)", "common.themeSunset": "Pôr do sol", "common.themeSunsetFull": "Pôr do sol (hora local)", "common.light": "Claro", "common.dark": "Escuro", "common.saveFailed": "Falha ao guardar", "common.loadFailed": "Falha ao carregar", "common.deleteFailed": "Falha ao eliminar", "common.backToLogin": "Voltar ao login", "loading.tagline": "A preparar os teus chats", "login.welcome": "Bem-vindo de volta", "login.subtitle": "Inicia sessão no KisAssistant para continuar", "login.usernamePh": "Nome de utilizador", "login.keyPh": "Chave de acesso", "login.failed": "Falha no login", "login.unlock": "Desbloquear", "login.newHere": "Novo por aqui?", "login.register": "Registar", "login.regTipOn": "Criar uma conta", "login.regTipOff": "Registo desativado nesta plataforma", "login.requestAccess": "Pedir acesso", "login.forgot": "Esqueceste a chave?", "login.perk1": "Privado por design — sem cookies nem rastreio", "login.perk2": "Os teus modelos, em tempo real", "login.adminNotePre": "A chave admin vive no .env do servidor", "login.adminNotePost": "; as chaves são emitidas pelo admin.", "login.totpIntro": "Dois fatores para @{user} — escreve o código de 6 dígitos da app.", "login.totpAria": "Código do autenticador", "login.totpInvalid": "Código inválido", "login.verify": "Verificar", "sidebar.newChat": "Novo chat", "sidebar.collapse": "Ocultar barra lateral", "sidebar.closeDrawer": "Fechar chats", "sidebar.searchPh": "Pesquisar chats…", "sidebar.searchAria": "Pesquisar conversas", "sidebar.clearSearch": "Limpar pesquisa", "sidebar.searching": "A pesquisar…", "sidebar.noMatch": "Sem resultados para «{q}».", "sidebar.youPrefix": "tu: ", "sidebar.emptyA": "Ainda sem conversas.", "sidebar.emptyB": "Começa um novo chat acima.", "sidebar.rename": "Renomear", "sidebar.topic": "Definir tópico", "sidebar.share": "Partilhar publicamente", "sidebar.delete": "Eliminar", "sidebar.convOptions": "Opções da conversa", "sidebar.admin": "admin", "sidebar.adminCenter": "Centro admin", "sidebar.adminCenterTip": "Abrir o Centro admin", "sidebar.settingsTip": "Perfil e definições", "sidebar.logout": "Sair", "sidebar.archive": "Arquivar", "chat.expand": "Mostrar barra lateral", "chat.openNav": "Abrir chats", "chat.modelAria": "Modelo", "chat.noModels": "Sem modelos", "chat.selectModel": "Escolher modelo…", "chat.personalTag": "chave pessoal", "toast.chatDeleted": "Chat “{title}” eliminado", "toast.chatArchived": "Chat “{title}” arquivado", "toast.chatUnarchived": "Chat “{title}” desarquivado", "toast.modelChanged": "Modelo: {label}", "toast.themeChanged": "Tema alterado", "toast.accentChanged": "Cor de destaque alterada", "toast.unshared": "Link partilhado removido", "toast.settingsSaved": "Definições guardadas", "toast.convUpdated": "Conversa “{title}” atualizada", "chat.you": "Tu", "chat.greet": "Como posso ajudar, {name}?", "chat.greetSub": "Escolhe um modelo e escreve abaixo. Markdown suportado.", "chat.thinking": "A pensar…", "chat.placeholder": "Escreve ao KisAssistant…", "chat.send": "Enviar", "chat.attachTip": "Anexar ficheiro", "chat.removeAttach": "Remover {name}", "chat.ocrOpt": "Transcrever imagem", "chat.unavailable": "(indisponível)", "chat.ocrHint": "JPG, PNG, WEBP — OCR no servidor, revês o texto antes", "chat.textOpt": "Anexar ficheiro de texto", "chat.textHint": "TXT ou Markdown, máx. 500KB", "chat.attachNote": "Uploads só via ferramentas da plataforma. Imagens chegam ao modelo como texto revisto — nunca brutas.", "chat.wiki": "Wiki e docs", "attach.tooBigImg": "Imagem muito grande (máx. 5MB).", "attach.tooBigText": "Ficheiro muito grande (máx. 500KB).", "attach.uploadFailed": "Falha no upload", "attach.kindOcr": "transcrição de imagem", "attach.kindText": "ficheiro de texto", "attach.tpl": "[anexo {kind}: {name}]", "attach.chatError": "**Erro:** o modelo não respondeu. Tenta de novo.", "dlg.editConv": "Editar conversa", "dlg.fTitle": "Título", "dlg.fTopic": "Tópico", "dlg.topicHint": "Etiqueta curta, ex. cozinha, rust, viagens.", "dlg.noTopic": "Sem tópico", "dlg.recoverTitle": "Recuperar chave", "dlg.recoverIntro": "Escreve o teu username. Receberás um link que emite uma chave nova (a antiga deixa de funcionar).", "dlg.sendLink": "Enviar link", "dlg.reqFailed": "Pedido falhou", "dlg.recoverSent": "Se @{user} existe com email de recuperação, um link vai a caminho{fromPart} (válido 60 minutos, uso único).", "dlg.recoverSentFrom": " de {from}", "dlg.spamNote": "Vê o inbox — e o spam, pode ter caído lá.", "dlg.noMailNote": "Nada chega? Então não há email definido e não é possível — contacta o admin do site.", "dlg.registerTitle": "Criar uma conta", "dlg.regWelcome": "Bem-vindo, @{user}! A tua chave — copia agora, não será mostrada de novo.", "dlg.regContinue": "Copiar e ir ao login", "dlg.regFailed": "Registo falhou", "dlg.emailOpt": "Email (opcional, para recuperar a chave)", "dlg.shareTitle": "Partilhar publicamente", "dlg.shareCreating": "A criar link público…", "dlg.shareIntro": "Quem tiver este link pode ler {title}. Sem login.", "dlg.unshare": "Remover partilha", "dlg.reviewOcr": "Rever transcrição", "dlg.reviewText": "Rever anexo", "dlg.transcribing": "A transcrever imagem no servidor (OCR)…", "dlg.uploadingTools": "Upload via ferramentas…", "dlg.ocrExpl": "A imagem foi transcrita em texto no servidor — o modelo recebe este texto, nunca a imagem. Corrige abaixo e anexa.", "dlg.textExpl": "O ficheiro foi enviado via ferramentas. O texto será anexado à mensagem. Edita e anexa.", "dlg.truncWarn": "Transcrição cortada no limite — falta o fim.", "dlg.attachBtn": "Anexar à mensagem", "dlg.chars": "{n} caracteres", "settings.title": "Definições", "settings.searchPh": "Pesquisar definições…", "settings.searchAria": "Pesquisar definições", "settings.clearSearch": "Limpar pesquisa", "settings.noMatch": "Sem definições para «{q}».", "settings.profile": "Perfil", "settings.appearance": "Aparência", "settings.features": "Funções", "settings.avatarUpload": "Enviar avatar", "settings.dropIdle": "Larga uma imagem ou clica para enviar", "settings.dropActive": "Larga!", "settings.uploading": "A enviar…", "settings.avatarHint": "jpg · png · webp — máx. 5MB — 5 mudanças por 2h", "settings.username": "Nome de utilizador", "settings.usernameHint": "Nome de acesso. 2–32 caracteres: minúsculas, dígitos, . _ -. Efeito imediato.", "settings.email": "Email de recuperação", "settings.emailHint": "Opcional. Só para te enviar uma chave nova.", "settings.accent": "Cor de destaque", "settings.accentHint": "Aplicada na hora, guardada neste dispositivo.", "settings.customColor": "Cor custom", "settings.customAria": "Cor de destaque personalizada", "settings.accentAria": "Destaque: {label}", "settings.featHint": "Liga funções do editor. Na hora, neste dispositivo.", "settings.providers": "Provedores", "providers.intro": "Ligue as suas chaves API. Os modelos dos provedores ligados aparecem no menu de modelos em cima.", "providers.hint": "As chaves ficam neste servidor e nunca mais são mostradas — só os últimos 4 caracteres. O uso é cobrado na sua conta e a política de modelos do admin continua válida.", "providers.connected": "Ligado", "providers.notConnected": "Desligado", "providers.keyPh": "Cole a chave API…", "providers.saveKey": "Guardar", "providers.removeKey": "Remover", "providers.replaceKey": "Substituir", "providers.getKey": "Obter chave", "providers.keySaved": "Chave guardada — os modelos já estão no menu.", "providers.keyRemoved": "Chave removida.", "providers.invalidKey": "Essa chave parece inválida (8–256 caracteres, sem espaços).", "providers.loadFailed": "Não foi possível carregar os provedores.", "accent.preset.emerald": "Esmeralda", "accent.preset.rose": "Rosa", "accent.preset.blue": "Azul", "accent.preset.violet": "Violeta", "accent.preset.amber": "Amarelo", "accent.preset.orange": "Laranja", "accent.preset.teal": "Turquesa", "accent.preset.pink": "Rosa choque", "feat.thinking.label": "Raciocínio", "feat.thinking.hint": "Pede ao modelo para raciocinar passo a passo.", "feat.attachments.label": "Anexos", "feat.attachments.hint": "Anexa imagens (transcrição OCR) e textos.", "feat.deepSearch.label": "Pesquisa profunda", "feat.deepSearch.hint": "Pesquisa fontes frescas na web. Em breve.", "feat.soon": "(breve)", "admin.title": "Contas", "admin.intro": "Contas sem KYC: crias um nome, entregas a chave uma vez. Hash guardado — regenera para rodar.", "admin.keyOnce": "Chave para @{user} — mostrada uma vez, copia agora:", "admin.newAccount": "Nova conta", "admin.email": "Email (recuperação)", "admin.createFailed": "Falha ao criar", "admin.regenFailed": "Falha ao regenerar", "admin.deleteFailed": "Falha ao eliminar", "admin.loadFailed": "Falha ao carregar", "admin.searchPh": "Pesquisar contas…", "admin.searchAria": "Pesquisar contas", "admin.sortAria": "Ordenar contas", "admin.filterAria": "Filtrar contas", "admin.sortNewest": "Mais novas", "admin.sortOldest": "Mais antigas", "admin.sortAz": "A → Z", "admin.sortZa": "Z → A", "admin.filterAll": "Todas as contas", "admin.filterWith": "Com email", "admin.filterNo": "Sem email", "admin.countMany": "{total} contas · página {page} de {pages}", "admin.countOne": "{total} conta · página {page} de {pages}", "admin.noEmail": "sem email", "admin.editProfile": "Editar perfil", "admin.regenKey": "Regenerar chave (revoga sessões)", "admin.deleteAccount": "Eliminar conta", "admin.deleteTitle": "Eliminar @{user}?", "admin.deleteMsg": "Remove para sempre conta, conversas e partilhas.", "admin.prevPage": "Página anterior", "admin.nextPage": "Página seguinte", "ollama.models": "Modelos Ollama", "ollama.desc": "Saca modelos para o host Ollama ou remove locais. Só admin.", "ollama.pulling": "A sacar {name}", "ollama.customTag": "Tag custom", "ollama.customHint": "ex. llama3.2:3b, ou nome da biblioteca", "ollama.pull": "Sacar", "ollama.installed": "Instalados ({n})", "ollama.noneYet": "Sem modelos locais ainda.", "ollama.catalog": "Catálogo", "ollama.delTitle": "Eliminar {name}?", "ollama.delMsg": "O modelo sai do host. Saca de novo quando quiseres.", "ollama.contacting": "A contactar Ollama…", "ollama.pullingSt": "A sacar…", "ollama.doneSt": "Pronto", "ollama.pullFailed": "Falha no download", "ollama.delTip": "Eliminar {name}", "ollama.sizeAria": "tamanho de {name}", "ollama.cat.llama3_2": "Compacto Meta para o dia a dia", "ollama.cat.llama3_1": "O workhorse da Meta", "ollama.cat.qwen2_5": "Multilingue Alibaba versátil", "ollama.cat.gemma2": "Aberto e eficiente do Google", "ollama.cat.mistral": "Mistral 7B — rápido e capaz", "ollama.cat.phi3": "Pequeno modelo Microsoft", "ollama.cat.deepseek_r1": "Modelo de raciocínio (cadeia de pensamento)", "ollama.cat.codellama": "Código e chat de código", "ollama.cat.llava": "Visão (percebe imagens)", "ollama.cat.nomic_embed_text": "Embeddings de texto", "center.title": "Centro admin", "center.back": "Voltar ao chat", "center.tabAccounts": "Contas", "center.tabFeatures": "Funções", "center.tabMail": "Mail", "center.tabModels": "Modelos", "models.title": "Modelos", "models.desc": "Ative ou limite cada modelo. Os desativados somem do seletor; os limites valem por utilizador (admins isentos).", "models.refresh": "Atualizar", "models.refreshAria": "Atualizar modelos", "models.enabled": "Ativo", "models.hourly": "por hora", "models.daily": "por dia", "models.unlimited": "Ilimitado", "models.ollamaTitle": "Biblioteca Ollama", "models.ollamaDesc": "Baixar ou remover modelos locais.", "models.empty": "Nenhum modelo disponível.", "models.saveFailed": "Falha ao guardar", "models.connTitle": "Conectividade Ollama", "models.connDesc": "Estado ao vivo do servidor Ollama: alcance, versão, latência e modelos instalados.", "models.connOnline": "Online", "models.connOffline": "Offline", "models.connDisabledState": "Desativado", "models.connCheck": "Verificar agora", "models.connCheckAria": "Verificar a conectividade Ollama agora", "models.connHost": "Servidor", "models.connVersion": "Versão", "models.connLatency": "Latência", "models.connModels": "Modelos", "models.connEmpty": "Nenhum modelo no servidor Ollama — baixe um da biblioteca abaixo.", "models.connHint": "Certifique-se de que o Ollama está a correr e de que OLLAMA_HOST aponta para ele.", "center.locked": "Só admins", "center.lockedSub": "Entra como admin para abrir o Centro.", "center.regTitle": "Registo público", "center.regDesc": "Mostra Registar no login. Off = só contas do admin. Ligar aposenta a lista abaixo.", "center.accTitle": "Pedidos de acesso (lista)", "center.accDescOn": "Indisponível com registo público — substitui.", "center.accDescOff": "Mostra Pedir acesso no login. Triagem na aba Acesso.", "center.ocrTitle": "Ferramenta OCR", "center.ocrDesc": "Transcrição no servidor. Precisa tesseract.", "center.reportsTitle": "Denúncias de mensagens", "center.reportsDesc": "Bandeira sob as respostas. Off = bandeira cinzenta com aviso e novas denúncias recusadas.", "center.usernameChangeTitle": "Mudanças de nome", "center.usernameChangeDesc": "Permite renomear-se em Definições → Perfil. Off = campo bloqueado (o admin continua a poder renomear).", "settings.usernameDisabled": "Mudanças de nome desativadas pelo administrador.", "center.mailTitle": "Emails de recuperação", "center.mailChecking": "A verificar…", "center.mailFrom": "Ativo — enviados de {from}", "center.mailDefault": "Ativo — enviados do remetente", "center.mailOff": "Desligado — define SMTP_HOST", "center.mailNote": "Credenciais SMTP vivem no servidor (ver docs/OPERATIONS.md), nunca aqui. Email define-se no perfil ou criação.", "center.on": "Sim", "center.off": "Não", "share.fallback": "Chat partilhado", "share.open": "Abrir KisAssistant", "share.gone": "Este chat partilhado sumiu", "share.goneSub": "Link inválido, expirado ou removido pelo autor.", "share.back": "Voltar", "share.loading": "A carregar chat…", "share.by": "por {name}", "reset.checking": "A verificar link…", "reset.invalid": "Link inválido", "reset.invalidSub": "Link desconhecido, usado ou expirado. Pede outro no login.", "reset.readyTitle": "Nova chave para @{user}?", "reset.readySub": "Emite já uma chave nova. A antiga morre.", "reset.issue": "Emitir nova chave", "reset.issuing": "A emitir…", "reset.doneTitle": "Nova chave pronta", "reset.doneSub": "Copia agora — não será mostrada de novo. Entra com ela.", "reset.goLogin": "Ir ao login", "reset.failed": "Falha na recuperação", "app.deleteConvTitle": "Eliminar conversa?", "app.deleteConvMsg": "«{title}» e mensagens serão eliminados.", "settings.language": "Idioma", "settings.languageHint": "Idioma da interface. Na hora, neste dispositivo.", "settings.security": "Segurança", "sec.accessKey": "Chave de acesso", "sec.accessKeyHint": "Roda a tua chave. Sessões antigas morrem na hora — entras com a nova.", "sec.rotate": "Rodar a minha chave", "sec.rotateConfirm": "Clica de novo para confirmar", "sec.rotationFailed": "Falha ao rodar", "sec.totp": "Dois fatores (TOTP)", "sec.totpChecking": "A verificar…", "sec.totpAddHint": "Adiciona um código de 6 dígitos da app além da chave.", "sec.totpEnable": "Ativar dois fatores", "sec.totpSecretHint": "Adiciona este segredo à app (ou abre o link otpauth) e escreve um código:", "sec.totpConfirm": "Confirmar", "sec.totpDisableLabel": "Desligar com um código atual", "sec.totpDisable": "Desligar 2FA", "sec.setupFailed": "Falha ao configurar", "sec.passkeys": "Passkeys", "sec.passkeysHint": "WebAuthn / autenticadores do sistema — precisa RP no servidor.", "sec.danger": "Zona perigosa", "sec.dangerHint": "Elimina a conta com chats, partilhas e sessões. Sem volta.", "sec.deleteMe": "Eliminar a minha conta", "sec.deleteFailed": "Falha ao eliminar", "sec.newKeyTitle": "Nova chave de acesso", "sec.newKeyHint": "Copia agora — não será mostrada de novo. Sessões antigas morrem.", "sec.newKeyDone": "Pronto — sair", "sec.deleteTitle": "Eliminar a tua conta?", "sec.deleteMsg": "Remove para sempre a conta com conversas, partilhas e sessões. Sem volta.", "sec.deleteForever": "Eliminar para sempre", "sec.avatarTooBig": "Ficheiro muito grande (máx. 5MB).", "sec.avatarType": "Só imagens jpg, png ou webp.", "center.tabAccess": "Acesso", "center.smtpHost": "Servidor", "center.smtpPort": "Porta", "center.smtpUser": "Utilizador", "center.smtpPass": "Palavra-passe", "center.smtpFrom": "Remetente", "center.smtpAppUrl": "URL da app", "center.notSet": "(não definido)", "center.noAuth": "(sem auth)", "center.tlsImplicit": "TLS implícito (465)", "center.tlsStarttls": "STARTTLS (587)", "center.showPass": "Mostrar palavra-passe SMTP", "center.hidePass": "Ocultar palavra-passe SMTP", "center.hiddenPass": "palavra-passe oculta", "center.mailTestTitle": "Teste de ligação", "center.mailTestDesc": "Verifica a ligação e envia um mail real para um endereço teu.", "center.verifyConn": "Verificar ligação", "center.verifying": "A verificar…", "center.sendTest": "Enviar mail de teste", "center.sendingMail": "A enviar…", "center.testToAria": "Email de teste", "center.connOk": "Ligação OK — SMTP acessível, auth aceite.", "center.verifyFailed": "Verificação falhou", "center.testSent": "Mail de teste enviado para {to}{tail}.", "center.mailSendFailed": "Falha ao enviar", "center.credsNote": "Credenciais vivem no servidor (ver docs/OPERATIONS.md), só leitura aqui. Email define-se no perfil ou criação.", "access.status.pending": "Pendente", "access.status.reviewing": "Em revisão", "access.status.accepted": "Aceite", "access.status.refused": "Recusado", "access.filter.all": "todos", "access.filter.pending": "pendentes", "access.filter.reviewing": "em revisão", "access.filter.accepted": "aceites", "access.filter.refused": "recusados", "access.refresh": "Atualizar", "access.refreshAria": "Atualizar pedidos", "access.empty": "Sem pedidos. A lista está vazia.", "access.emptyFiltered": "Nada com este estado.", "access.msgOne": "1 mensagem", "access.msgMany": "{n} mensagens", "access.accountCreated": "Conta criada — chave única, copia agora:", "access.replyPh": "Responder (chega por email)…", "access.replyAria": "Responder ao requerente", "access.reasonLabel": "Motivo (enviado com a decisão)", "access.reasonPh": "Motivo opcional…", "access.btnReviewing": "Em revisão", "access.btnAccept": "Aceitar", "access.btnRefuse": "Recusar", "access.closedNote": "Ticket fechado ({status}).", "access.reasonWord": "Motivo", "access.youAdmin": "Tu (admin)", "access.sendFailed": "Falha ao enviar", "access.updateFailed": "Falha ao atualizar", "live.newRequest": "New access request from @{user}", "live.newMessage": "New message from @{user}", "live.newReport": "New content report ({reason})", "live.adminReply": "Admin replied to your ticket", "live.ticketStatus": "Ticket {status}", "msg.copy": "Copiar mensagem", "msg.search": "Pesquisar na web", "msg.report": "Denunciar resposta", "msg.reportDisabled": "Denúncias desativadas", "msg.retry": "Tentar novamente", "report.title": "Denunciar esta resposta", "report.reason": "Motivo", "report.reasonPh": "Selecione um motivo…", "report.reason.copyright": "Direitos autorais", "report.reason.gore": "Gore / CSAM / NSFW", "report.reason.falseinfo": "Informação falsa", "report.reason.bug": "Bug", "report.details": "Detalhes (opcional)", "report.detailsPh": "O que há de errado nesta resposta? (opcional)", "report.submit": "Enviar denúncia", "report.sent": "Denúncia enviada — um administrador irá analisá-la.", "report.failed": "Não foi possível enviar a denúncia", "center.tabReports": "Denúncias", "reports.filter.all": "todas", "reports.filter.open": "abertas", "reports.filter.reviewing": "em análise", "reports.filter.resolved": "resolvidas", "reports.filter.dismissed": "rejeitadas", "reports.status.open": "Aberta", "reports.status.reviewing": "Em análise", "reports.status.resolved": "Resolvida", "reports.status.dismissed": "Rejeitada", "reports.refresh": "Atualizar", "reports.refreshAria": "Atualizar denúncias", "reports.empty": "Nenhuma denúncia de conteúdo ainda.", "reports.emptyFiltered": "Nada com este estado.", "reports.response": "Resposta denunciada", "reports.noContent": "Nenhum conteúdo capturado.", "reports.model": "Modelo", "reports.reporter": "Autor", "reports.conversation": "Conversa", "reports.detailsGiven": "Detalhes do autor", "reports.noteLabel": "Nota do admin", "reports.notePh": "Nota interna…", "reports.noteWord": "Nota", "reports.btnReviewing": "Em análise", "reports.btnResolve": "Resolver", "reports.btnDismiss": "Rejeitar", "reports.updateFailed": "Falha ao atualizar", "reports.closedNote": "Denúncia {status}.", "report.alreadyReported": "Esta resposta já foi denunciada.", "reports.prompt": "Pergunta do utilizador", "reports.unreliable": "Autor pouco fiável", "reports.shadowban": "Silenciar autor", "reports.unshadowban": "Tirar silêncio", "reports.shadowbanTitle": "Silenciar este autor?", "reports.shadowbanMsg": "As denúncias de @{user} continuarão a ser aceites, mas marcadas como pouco fiáveis e excluídas da contagem.", "reports.unshadowbanTitle": "Tirar o silêncio?", "reports.unshadowbanMsg": "As denúncias de @{user} voltarão a contar.", "review.title": "Pedido de acesso", "review.loadingTicket": "A carregar ticket…", "review.notFound": "Ticket não encontrado", "review.notFoundSub": "Link desconhecido. Vê o URL do teu email ou pede acesso no login.", "review.adminNote": "Nota do admin:", "review.acceptedHint": "Conta criada — vê o email com a chave e entra.", "review.closedHint": "Ticket fechado. Emails não são lidos — se preciso, pede de novo.", "review.replyPh": "Defende o teu caso…", "review.replyAria": "Responder ao teu ticket", "review.mailHint": "Emails são de sentido único — responde aqui, não por email.", "review.status.pending": "Pendente de revisão", "review.status.reviewing": "Em revisão", "review.status.accepted": "Aceite", "review.status.refused": "Recusado", "review.admin": "Admin", "review.you": "Tu", "review.requestTitle": "Pedir acesso", "review.requestIntro": "Reserva um nome e diz porque queres entrar. Um admin revê cada pedido — segues o fio no teu ticket e por email.", "review.emailHint": "Obrigatório — avisos e respostas chegam aqui.", "review.whyLabel": "Porque dar-te acesso?", "review.whyHint": "10+ caracteres. Isto abre o teu ticket.", "review.whyPh": "Quero…", "review.emailLabel": "Email", "review.sendRequest": "Enviar pedido", "review.received": "Pedido recebido", "review.reserved": "@{user} fica reservado enquanto um admin revê. Um email com o teu ticket vai a caminho.", "review.copyLink": "Copiar link", "review.openTicket": "Abrir o meu ticket", "admin.editUserTitle": "Editar @{user}", "admin.avatarUrl": "URL do avatar", "archived.readonly": "Este chat está arquivado e é só de leitura.", "archived.unarchive": "Desarquivar", "archived.title": "Conversas arquivadas", "archived.empty": "Nenhuma conversa arquivada.", "archived.view": "Ver", "archived.deleteTitle": "Eliminar esta conversa arquivada?", "archived.deleteMsg": "Isto remove permanentemente a conversa e todas as mensagens.", }; const dicts: Record = { en, es, fr, ru, it, pt }; const STORE_KEY = "kisassistant_lang"; function detectLang(): LangId { try { const saved = localStorage.getItem(STORE_KEY); if ( saved === "en" || saved === "es" || saved === "fr" || saved === "ru" || saved === "it" || saved === "pt" ) { return saved; } } catch { // storage unavailable — fall through to browser detection } const nav = (navigator.language || "en").toLowerCase().split("-")[0]; if ( nav === "es" || nav === "fr" || nav === "ru" || nav === "it" || nav === "pt" ) return nav; return "en"; } let current: LangId = detectLang(); if (typeof document !== "undefined") document.documentElement.lang = current; const listeners = new Set<() => void>(); export function getLang(): LangId { return current; } /** Persist the language (per browser) and re-render every subscriber. */ export function setLang(lang: LangId): void { if (current === lang) return; current = lang; try { localStorage.setItem(STORE_KEY, lang); } catch { // ignore } if (typeof document !== "undefined") document.documentElement.lang = lang; listeners.forEach((fn) => fn()); } /** Global translate function: t("login.welcome"), t("chat.greet", { name }). */ export function t( key: StringKey, vars?: Record, ): string { let s: string = dicts[current][key] ?? dicts.en[key] ?? key; if (vars) { for (const [k, v] of Object.entries(vars)) s = s.replaceAll(`{${k}}`, String(v)); } return s; } /** Reactive hook — components using translated strings must call it so they re-render on language switch. */ export function useT(): { t: typeof t; lang: LangId } { useSyncExternalStore( (fn) => { listeners.add(fn); return () => { listeners.delete(fn); }; }, () => current, ); return { t, lang: current }; }