2683 lines
120 KiB
TypeScript
2683 lines
120 KiB
TypeScript
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<StringKey, string>`, 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.light": "Light",
|
||
"common.dark": "Dark",
|
||
"common.saveFailed": "Save 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",
|
||
|
||
"chat.expand": "Expand sidebar",
|
||
"chat.openNav": "Open chats",
|
||
"chat.modelAria": "Model",
|
||
"chat.noModels": "No models",
|
||
"chat.selectModel": "Select model…",
|
||
"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.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.",
|
||
|
||
"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.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.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",
|
||
|
||
"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.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",
|
||
} as const;
|
||
|
||
export type StringKey = keyof typeof en;
|
||
type Dict = Record<StringKey, string>;
|
||
|
||
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.light": "Claro",
|
||
"common.dark": "Oscuro",
|
||
"common.saveFailed": "No se pudo guardar",
|
||
"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",
|
||
|
||
"chat.expand": "Mostrar barra lateral",
|
||
"chat.openNav": "Abrir chats",
|
||
"chat.modelAria": "Modelo",
|
||
"chat.noModels": "Sin modelos",
|
||
"chat.selectModel": "Elegir modelo…",
|
||
"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.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.",
|
||
|
||
"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.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.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",
|
||
|
||
"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.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",
|
||
};
|
||
|
||
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.light": "Clair",
|
||
"common.dark": "Sombre",
|
||
"common.saveFailed": "Échec de l'enregistrement",
|
||
"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",
|
||
|
||
"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.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.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.",
|
||
|
||
"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.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.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",
|
||
|
||
"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.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",
|
||
};
|
||
|
||
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.light": "Светлая",
|
||
"common.dark": "Тёмная",
|
||
"common.saveFailed": "Не удалось сохранить",
|
||
"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": "Выйти",
|
||
|
||
"chat.expand": "Показать панель",
|
||
"chat.openNav": "Открыть чаты",
|
||
"chat.modelAria": "Модель",
|
||
"chat.noModels": "Нет моделей",
|
||
"chat.selectModel": "Выбрать модель…",
|
||
"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.email": "Email для восстановления",
|
||
"settings.emailHint": "Необязательно. Только для отправки нового ключа.",
|
||
"settings.accent": "Акцентный цвет",
|
||
"settings.accentHint": "Применяется сразу, хранится на устройстве.",
|
||
"settings.customColor": "Свой цвет",
|
||
"settings.customAria": "Свой акцентный цвет",
|
||
"settings.accentAria": "Акцент: {label}",
|
||
"settings.featHint":
|
||
"Включайте возможности редактора. Сразу, на этом устройстве.",
|
||
|
||
"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.locked": "Только админы",
|
||
"center.lockedSub": "Войдите как админ, чтобы открыть центр.",
|
||
"center.regTitle": "Открытая регистрация",
|
||
"center.regDesc": "Кнопка регистрации на входе. Выкл = только админ создаёт. Включение закрывает лист ожидания ниже.",
|
||
"center.accTitle": "Заявки (лист ожидания)",
|
||
"center.accDescOn": "Недоступно при открытой регистрации — она заменяет.",
|
||
"center.accDescOff":
|
||
"Кнопка доступа на входе. Гости просят имя; разбор во вкладке.",
|
||
"center.ocrTitle": "Инструмент OCR",
|
||
"center.ocrDesc": "Распознавание картинок на сервере. Нужен tesseract.",
|
||
"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": "Не обновилось",
|
||
|
||
"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.sendRequest": "Отправить заявку",
|
||
"review.received": "Заявка получена",
|
||
"review.reserved":
|
||
"@{user} занято вами, пока админ проверяет. Письмо с тикетом уже летит.",
|
||
"review.copyLink": "Копировать ссылку",
|
||
"review.openTicket": "Открыть мой тикет",
|
||
|
||
"admin.editUserTitle": "Править @{user}",
|
||
"admin.avatarUrl": "URL аватара",
|
||
};
|
||
|
||
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.light": "Chiaro",
|
||
"common.dark": "Scuro",
|
||
"common.saveFailed": "Salvataggio non riuscito",
|
||
"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",
|
||
|
||
"chat.expand": "Mostra barra laterale",
|
||
"chat.openNav": "Apri chat",
|
||
"chat.modelAria": "Modello",
|
||
"chat.noModels": "Nessun modello",
|
||
"chat.selectModel": "Scegli modello…",
|
||
"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.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.",
|
||
|
||
"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.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.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",
|
||
|
||
"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.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",
|
||
};
|
||
|
||
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.light": "Claro",
|
||
"common.dark": "Escuro",
|
||
"common.saveFailed": "Falha ao guardar",
|
||
"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",
|
||
|
||
"chat.expand": "Mostrar barra lateral",
|
||
"chat.openNav": "Abrir chats",
|
||
"chat.modelAria": "Modelo",
|
||
"chat.noModels": "Sem modelos",
|
||
"chat.selectModel": "Escolher modelo…",
|
||
"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.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.",
|
||
|
||
"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.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.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",
|
||
|
||
"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.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",
|
||
};
|
||
|
||
const dicts: Record<LangId, Dict> = { 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, string | number>,
|
||
): 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 };
|
||
}
|