Users, file manager, image generation, budgets and agent workflow

- User management: local accounts (Argon2) and OpenID Connect, admin/user
  roles, per-user folders plus admin-shared folders, read-only session
  sharing, event filtering per user
- File manager with CodeMirror editor and git client (status, diff,
  stage, commit, branches, fetch/pull/push, log, clone/init)
- Image generation via fal.ai: generate_image tool and image page
- Budgets: monthly limits per provider, provider balances/costs
  (OpenRouter, Anthropic admin cost report, fal.ai), price discovery
- Subagents on other models, background tasks, concurrency limits per
  host/model/subagent
- MCP tools loaded on demand (mcp_search/mcp_call), read-only hints
- Permission mode "auto", safe read-only commands always allowed
- Persistent message queue with attachments, deletable
- Live reasoning incl. <think> tags, activity animations, CSS themes,
  mobile layout, statistics per user, upload size handling
- Docker: named volume for state, localhost-bound port

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Opus 5.5 committed 2026-11-08 16:54:08 +01:00
1 parent f3a2fe3816
commit 49262c612c
70 files changed
+8495 -714

No files matched your search

+223 -8
View File
@@ -14,8 +14,116 @@ export const de: Messages = {
"nav.settings": "Einstellungen",
"nav.stats": "Statistik",
"login.prompt": "Gib das auf dem Server konfigurierte Zugriffstoken ein.",
"login.token": "Zugriffstoken",
"nav.images": "Bilder",
"images.disabled": "Bildgenerierung ist nicht eingerichtet: Ein Admin aktiviert sie unter Einstellungen → Allgemein (fal.ai-Key in FAL_KEY).",
"images.promptPlaceholder": "Beschreibe das Bild: Motiv, Stil, Komposition, Farben, Licht… (Strg+Enter)",
"images.model": "Modell",
"images.size": "Format",
"images.count": "Anzahl",
"images.countN": "{n}×",
"images.folder": "Ordner",
"images.generate": "Erzeugen",
"images.generating": "Erzeuge…",
"images.empty": "Noch keine Bilder.",
"images.reuse": "Prompt wiederverwenden",
"images.copyPath": "Dateipfad kopieren",
"images.deleteConfirm": "Dieses Bild löschen (inklusive Datei)?",
"images.size.square_hd": "Quadrat HD",
"images.size.square": "Quadrat",
"images.size.landscape_4_3": "Quer 4:3",
"images.size.landscape_16_9": "Quer 16:9",
"images.size.portrait_4_3": "Hoch 3:4",
"images.size.portrait_16_9": "Hoch 9:16",
"settings.images": "Bildgenerierung (fal.ai)",
"settings.imagesEnabled": "Aktiv",
"settings.imagesKey": "API-Key",
"settings.imagesDefault": "Standardmodell",
"settings.imagesDir": "Ordner im Projekt",
"settings.imagesHint": "Modelle und Preise pro Bild werden in der Config-Datei bearbeitet ([images]).",
"budget.title": "Budgets in diesem Monat",
"budget.spent": "Verbraucht",
"budget.budget": "Budget",
"budget.remaining": "Verbleibend",
"budget.provider": "Laut Anbieter",
"budget.balance": "Guthaben {v}",
"budget.providerSpent": "Abgerechnet {v}",
"budget.orgWide": "Organisationsweiter Kostenbericht des Anbieters",
"budget.remoteError": "nicht verfügbar",
"budget.hint": "Verbraucht = von CodeCraft mit deinen Preisen erfasste Kosten. Werte des Anbieters kommen aus deren APIs (OpenRouter, Anthropic mit Admin-Key, fal.ai mit Admin-Key).",
"budget.chip": "Budget {name}: {p}",
"budget.chipHint": "Ein Monatsbudget ist fast aufgebraucht – siehe Statistik.",
"settings.monthlyBudget": "Monatsbudget",
"settings.monthlyBudgetTotal": "Monatsbudget (gesamt)",
"settings.monthlyBudgetHint": "In der eingestellten Währung; 0 = keins. Warnungen erscheinen im Chat-Kopf.",
"settings.budgetWarning": "Warnen ab",
"settings.adminKey": "Admin-Key (optional)",
"settings.adminKeyHint": "Anthropic Admin-API-Key (sk-ant-admin01-…), um die echten, organisationsweiten Monatskosten anzuzeigen.",
"settings.fetchPrices": "Preise abrufen",
"settings.pricesFetched": "Preise für {n} Modelle aktualisiert – zum Übernehmen speichern.",
"stats.perMillion": "pro 1 Mio. Tok.",
"stats.perMillionHint": "Effektive Kosten pro Million Tokens (Input inkl. Cache + Output)",
"queue.pending": "{n} Nachricht(en) in der Warteschlange – werden beim nächsten Schritt übergeben",
"queue.stopped": "{n} Nachricht(en) in der Warteschlange",
"queue.sendNow": "Jetzt senden",
"queue.remove": "Aus der Warteschlange entfernen",
"queue.backgroundResult": "Ergebnis einer Hintergrundaufgabe",
"permissionMode.auto": "Automatisch (fragt einmal, Sicheres läuft)",
"nav.files": "Dateien",
"files.search": "Dateien suchen…",
"files.newFile": "Neue Datei",
"files.newFolder": "Neuer Ordner",
"files.upload": "Hochladen",
"files.showHidden": "Versteckte Dateien zeigen",
"files.newFileName": "Dateiname:",
"files.newFolderName": "Ordnername:",
"files.renameTo": "Neuer Name:",
"files.copyTo": "Name der Kopie:",
"files.rename": "Umbenennen",
"files.duplicate": "Duplizieren",
"files.download": "Herunterladen",
"files.actions": "Aktionen",
"files.deleteConfirm": "„{name}“ löschen?",
"files.deleteFolderConfirm": "Den Ordner „{name}“ mit allem Inhalt löschen?",
"files.noHits": "Keine Treffer",
"files.emptyFolder": "Leerer Ordner",
"files.dropHint": "Dateien hierher ziehen zum Hochladen",
"files.selectFile": "Datei auswählen",
"files.edit": "Bearbeiten",
"files.preview": "Vorschau",
"files.binary": "Binärdatei – zum Öffnen herunterladen.",
"files.tooLarge": "Datei zu groß für den Editor – bitte herunterladen.",
"files.discardChanges": "Ungespeicherte Änderungen verwerfen?",
"files.conflict": "Die Datei wurde seit dem Öffnen geändert (z. B. von einem Agenten). Trotzdem überschreiben?",
"files.tabFiles": "Dateien",
"git.init": "Git-Repository anlegen",
"git.clone": "Repository klonen",
"git.cloneUrl": "Repository-URL (https://… oder git@…):",
"git.cloneFolder": "Ordnername:",
"git.branch": "Branch",
"git.newBranch": "Neuer Branch",
"git.newBranchName": "Name des neuen Branches:",
"git.fetch": "Fetch",
"git.pull": "Pull",
"git.push": "Push",
"git.done": "Erledigt.",
"git.changes": "Änderungen",
"git.history": "Verlauf",
"git.staged": "Vorgemerkt",
"git.unstaged": "Änderungen",
"git.stage": "Vormerken",
"git.unstage": "Nicht mehr vormerken",
"git.stageAll": "Alle vormerken",
"git.unstageAll": "Alle zurücknehmen",
"git.discard": "Änderungen verwerfen",
"git.discardConfirm": "Alle Änderungen an {file} verwerfen?",
"git.openFile": "Datei öffnen",
"git.clean": "Keine Änderungen.",
"git.message": "Commit-Nachricht",
"git.amend": "Letzten Commit ändern",
"git.commit": "Commit",
"git.noCommits": "Noch keine Commits.",
"git.noDiff": "Keine Unterschiede.",
"login.prompt": "Melde dich an, um fortzufahren.",
"login.invalid": "Ungültiges Token.",
"login.submit": "Anmelden",
@@ -40,9 +148,23 @@ export const de: Messages = {
"new.firstPrompt": "Was soll erledigt werden? (optional)",
"new.start": "Starten in {dir}",
"activity.thinking": "Denkt nach…",
"activity.writing": "Schreibt…",
"activity.tool": "Führt {tool} aus…",
"activity.subagent": "Subagent arbeitet: {name}",
"activity.waitingForYou": "Pausiert – wartet auf deine Antwort",
"activity.working": "Arbeitet",
"activity.titleBusy": "{n} aktiv",
"activity.titleInput": "Eingabe nötig",
"chat.reasoningLive": "Denkt nach…",
"chat.reasoningDone": "Gedacht · {n} Wörter",
"settings.reasoningDisplay": "Reasoning des Modells",
"settings.reasoningCollapsed": "Eingeklappt (per Klick öffnen)",
"settings.reasoningExpanded": "Immer aufgeklappt",
"settings.reasoningHidden": "Aus",
"settings.reasoningHint": "Die Überlegungen des Modells (sofern Modell und Provider sie liefern).",
"settings.reasoningLive": "Beim Denken offen, danach einklappen",
"chat.empty": "Schreib eine Nachricht, um zu starten.",
"chat.reasoning": "Überlegungen",
"chat.thinking": "Arbeitet…",
"chat.compacted": "Unterhaltung komprimiert – Zusammenfassung",
"chat.context": "Kontext",
"chat.contextHint": "{tokens} Tokens in der letzten Anfrage",
@@ -66,7 +188,7 @@ export const de: Messages = {
"command.compact": "Unterhaltung zusammenfassen, um Kontext freizugeben",
"command.new": "Neue Session starten",
"permissionMode.default": "Vor Änderungen fragen",
"permissionMode.default": "Fragen (außer Lesebefehle)",
"permissionMode.accept_edits": "Änderungen automatisch annehmen",
"permissionMode.bypass": "Alles erlauben",
@@ -75,6 +197,7 @@ export const de: Messages = {
"tool.input": "Eingabe",
"tool.output": "Ausgabe",
"tool.todosUpdated": "Aufgaben aktualisiert ({done}/{total} erledigt)",
"tool.todos": "Aufgaben",
"tool.openSubagent": "Subagent-Session öffnen",
@@ -107,8 +230,6 @@ export const de: Messages = {
"settings.language": "Sprache",
"settings.theme": "Design",
"settings.themeSystem": "System",
"settings.themeDark": "Dunkel",
"settings.themeLight": "Hell",
"settings.notifications": "Benachrichtigungen",
"settings.notificationsEnable": "Aktivieren",
"settings.notificationsOn": "Aktiv",
@@ -159,6 +280,11 @@ export const de: Messages = {
"settings.rawHint": "Die komplette Konfigurationsdatei inklusive Kommentaren. Sie wird vor dem Speichern geprüft.",
"settings.revert": "Zurücksetzen",
"mcp.load": "Tools für das Modell",
"mcp.loadAuto": "Automatisch (bei vielen Tools bei Bedarf)",
"mcp.loadAlways": "Immer alle Tools mitsenden",
"mcp.loadOnDemand": "Bei Bedarf (mcp_search / mcp_call)",
"mcp.loadHint": "Jedes direkt mitgesendete Tool kostet bei jeder Anfrage Kontext. Große Server besser bei Bedarf laden.",
"mcp.reconnect": "Alle neu verbinden",
"mcp.none": "Keine MCP-Server konfiguriert.",
"mcp.tools": "{n} Tools",
@@ -183,7 +309,6 @@ export const de: Messages = {
"permissions.ask": "Fragen",
"permissions.deny": "Verbieten",
"agents.intro": "Modi und Subagenten. Eigene in der Config-Datei ([agents.*]) oder als Markdown in .codecraft/agents bzw. .claude/agents anlegen.",
"agents.title": "Agenten",
"agents.readOnly": "nur lesen",
"agents.commands": "Slash-Befehle",
@@ -225,4 +350,94 @@ export const de: Messages = {
"stats.total": "Gesamt",
"stats.other": "Sonstige",
"stats.noData": "Keine Anfragen in diesem Zeitraum.",
"common.cancel": "Abbrechen",
"nav.logout": "Abmelden",
"login.username": "Benutzername",
"login.password": "Passwort",
"login.passwordRepeat": "Passwort wiederholen",
"login.setupPrompt": "Noch keine Benutzer. Gib den Setup-Code aus dem Server-Log ein und lege den ersten Admin an.",
"login.setupCode": "Setup-Code",
"login.createAdmin": "Admin anlegen",
"login.invalidCode": "Ungültiger Setup-Code.",
"login.mismatch": "Die Passwörter stimmen nicht überein.",
"login.or": "oder",
"sidebar.shared": "Mit dir geteilt",
"chat.readOnly": "nur lesen",
"chat.readOnlyHint": "Diese Session wurde mit dir geteilt – du kannst mitlesen, aber nicht schreiben.",
"chat.backgroundDone": "Hintergrundaufgabe fertig",
"chat.waitingModel": "Warte auf freien Slot bei {model}…",
"chat.waitingSubagent": "Warte auf freien Subagent-Slot ({agent})…",
"chat.anySubagent": "Limit für alle Subagenten",
"tool.background": "Hintergrund",
"share.title": "Session teilen",
"share.hint": "Ausgewählte Benutzer können diese Session lesend verfolgen.",
"share.noUsers": "Keine weiteren Benutzer.",
"settings.tab.users": "Benutzer",
"settings.tab.folders": "Freigegebene Ordner",
"settings.tab.account": "Konto",
"settings.maxParallelSubagents": "Parallele Subagenten",
"settings.maxParallelSubagentsHint": "Max. gleichzeitig laufende Subagenten über alle Sessions (0 = unbegrenzt).",
"settings.maxConcurrency": "Parallele Anfragen",
"settings.maxConcurrencyHint": "Max. gleichzeitige Anfragen an diesen Host/dieses Modell; weitere warten in einer Warteschlange. 1 für Server, die nur eine Anfrage gleichzeitig verarbeiten.",
"settings.slots": "Slots",
"account.title": "Konto",
"account.current": "Aktuelles Passwort",
"account.new": "Neues Passwort",
"account.change": "Passwort ändern",
"account.changed": "Passwort geändert. Andere Geräte wurden abgemeldet.",
"account.hint": "Mindestens 8 Zeichen.",
"users.intro": "Benutzer arbeiten in ihrem eigenen Ordner <workspace>/<benutzername> und in den für sie freigegebenen Ordnern. Admins können den ganzen Workspace nutzen und den Server verwalten.",
"users.username": "Benutzername",
"users.usernameHint": "Buchstaben, Ziffern, - _ . – gleichzeitig der Name des Benutzerordners.",
"users.displayName": "Anzeigename",
"users.role": "Rolle",
"users.roleUser": "Benutzer",
"users.roleAdmin": "Admin",
"users.login": "Anmeldung",
"users.password": "Passwort",
"users.passwordHint": "Leer lassen für reine Single-Sign-on-Anmeldung.",
"users.created": "Angelegt",
"users.you": "du",
"users.setPassword": "Passwort setzen",
"users.newPasswordFor": "Neues Passwort für {name} (mind. 8 Zeichen):",
"users.enable": "Aktivieren",
"users.disable": "Deaktivieren",
"users.deleteConfirm": "Benutzer „{name}“ löschen? Seine Sessions bleiben für Admins sichtbar.",
"users.add": "Benutzer hinzufügen",
"folders.intro": "Ordner im Workspace, in denen ausgewählte Benutzer zusätzlich zu ihrem eigenen Ordner arbeiten dürfen.",
"folders.none": "Noch keine freigegebenen Ordner.",
"folders.noUsers": "Keine Benutzer ohne Admin-Rolle.",
"folders.add": "Ordner freigeben",
"folders.addHint": "Der Ordner wird angelegt, falls er nicht existiert. Bestehende Projektordner lassen sich ebenso freigeben.",
"folders.remove": "Freigabe aufheben",
"folders.removeConfirm": "Freigabe dieses Ordners aufheben? Die Dateien bleiben erhalten.",
"agents.intro": "Modi und Subagenten mit ihrem Modell. Ein Subagent kann auf einem anderen Modell oder Host laufen – z. B. ein kleines Modell, das im Hintergrund Doku oder Übersetzungen schreibt.",
"agents.primaryTitle": "Modi",
"agents.subagentTitle": "Subagenten",
"agents.modePrimary": "Modus",
"agents.modeSubagent": "Subagent",
"agents.source.builtin": "eingebaut",
"agents.source.config": "Config",
"agents.source.file": "Datei",
"agents.model": "Modell",
"agents.sessionModel": "Session-Modell",
"agents.modelHint": "Festes Modell für diesen Agenten; leer = das im Chat gewählte Modell.",
"agents.maxParallel": "Max. parallel",
"agents.maxParallelShort": "max. {n} parallel",
"agents.description": "Beschreibung",
"agents.descriptionHint": "Anhand dieser Beschreibung entscheidet der Hauptagent, wann er delegiert.",
"agents.mode": "Art",
"agents.tools": "Tools",
"agents.toolsAll": "alle Tools (z. B. read, glob, grep)",
"agents.prompt": "Anweisungen",
"agents.reset": "Zurücksetzen",
"agents.resetConfirm": "„{name}“ auf die eingebauten Standardwerte zurücksetzen?",
"agents.deleteConfirm": "Agent „{name}“ löschen?",
"agents.add": "Agent hinzufügen",
"agents.addWriter": "Writer (Doku, Zusammenfassungen, Übersetzungen)",
"agents.addBlank": "Leerer Agent",
"stats.user": "Benutzer",
"stats.allUsers": "Alle Benutzer",
"stats.byUser": "Nach Benutzer",
};
+223 -8
View File
@@ -13,8 +13,116 @@ export const en = {
"nav.settings": "Settings",
"nav.stats": "Statistics",
"login.prompt": "Enter the access token configured on the server.",
"login.token": "Access token",
"nav.images": "Images",
"images.disabled": "Image generation is not set up: an admin enables it under Settings → General (fal.ai key in FAL_KEY).",
"images.promptPlaceholder": "Describe the image: subject, style, composition, colours, lighting… (Ctrl+Enter)",
"images.model": "Model",
"images.size": "Format",
"images.count": "Number",
"images.countN": "{n}×",
"images.folder": "Folder",
"images.generate": "Generate",
"images.generating": "Generating…",
"images.empty": "No images yet.",
"images.reuse": "Reuse prompt",
"images.copyPath": "Copy file path",
"images.deleteConfirm": "Delete this image (including the file)?",
"images.size.square_hd": "Square HD",
"images.size.square": "Square",
"images.size.landscape_4_3": "Landscape 4:3",
"images.size.landscape_16_9": "Landscape 16:9",
"images.size.portrait_4_3": "Portrait 3:4",
"images.size.portrait_16_9": "Portrait 9:16",
"settings.images": "Image generation (fal.ai)",
"settings.imagesEnabled": "Enabled",
"settings.imagesKey": "API key",
"settings.imagesDefault": "Default model",
"settings.imagesDir": "Folder in the project",
"settings.imagesHint": "Models and prices per image are edited in the config file ([images]).",
"budget.title": "Budgets this month",
"budget.spent": "Spent",
"budget.budget": "Budget",
"budget.remaining": "Remaining",
"budget.provider": "According to provider",
"budget.balance": "Balance {v}",
"budget.providerSpent": "Billed {v}",
"budget.orgWide": "Organisation-wide cost report of the provider",
"budget.remoteError": "not available",
"budget.hint": "Spent = costs recorded by CodeCraft with your configured prices. Provider values come from their APIs (OpenRouter, Anthropic with admin key, fal.ai with admin key).",
"budget.chip": "Budget {name}: {p}",
"budget.chipHint": "A monthly budget is nearly used up – see Statistics.",
"settings.monthlyBudget": "Monthly budget",
"settings.monthlyBudgetTotal": "Monthly budget (total)",
"settings.monthlyBudgetHint": "In the configured currency; 0 = none. Warnings appear in the chat header.",
"settings.budgetWarning": "Warn at",
"settings.adminKey": "Admin key (optional)",
"settings.adminKeyHint": "Anthropic Admin API key (sk-ant-admin01-…) to show the real, organisation-wide monthly cost.",
"settings.fetchPrices": "Fetch prices",
"settings.pricesFetched": "Prices updated for {n} models – save to keep them.",
"stats.perMillion": "per 1M tok",
"stats.perMillionHint": "Effective cost per million tokens (input incl. cache + output)",
"queue.pending": "{n} message(s) waiting – delivered at the agent's next step",
"queue.stopped": "{n} message(s) waiting",
"queue.sendNow": "Send now",
"queue.remove": "Remove from queue",
"queue.backgroundResult": "Result of a background task",
"permissionMode.auto": "Automatic (asks once, safe steps run)",
"nav.files": "Files",
"files.search": "Search files…",
"files.newFile": "New file",
"files.newFolder": "New folder",
"files.upload": "Upload",
"files.showHidden": "Show hidden files",
"files.newFileName": "File name:",
"files.newFolderName": "Folder name:",
"files.renameTo": "New name:",
"files.copyTo": "Name of the copy:",
"files.rename": "Rename",
"files.duplicate": "Duplicate",
"files.download": "Download",
"files.actions": "Actions",
"files.deleteConfirm": "Delete \"{name}\"?",
"files.deleteFolderConfirm": "Delete the folder \"{name}\" with everything in it?",
"files.noHits": "No matches",
"files.emptyFolder": "Empty folder",
"files.dropHint": "Drop files here to upload",
"files.selectFile": "Select a file",
"files.edit": "Edit",
"files.preview": "Preview",
"files.binary": "Binary file – download it to open.",
"files.tooLarge": "File too large for the editor – download it.",
"files.discardChanges": "Discard unsaved changes?",
"files.conflict": "The file was changed on disk (e.g. by an agent) since you opened it. Overwrite it anyway?",
"files.tabFiles": "Files",
"git.init": "Initialise git repository",
"git.clone": "Clone repository",
"git.cloneUrl": "Repository URL (https://… or git@…):",
"git.cloneFolder": "Folder name:",
"git.branch": "Branch",
"git.newBranch": "New branch",
"git.newBranchName": "Name of the new branch:",
"git.fetch": "Fetch",
"git.pull": "Pull",
"git.push": "Push",
"git.done": "Done.",
"git.changes": "Changes",
"git.history": "History",
"git.staged": "Staged",
"git.unstaged": "Changes",
"git.stage": "Stage",
"git.unstage": "Unstage",
"git.stageAll": "Stage all",
"git.unstageAll": "Unstage all",
"git.discard": "Discard changes",
"git.discardConfirm": "Discard all changes to {file}?",
"git.openFile": "Open file",
"git.clean": "No changes.",
"git.message": "Commit message",
"git.amend": "Amend last commit",
"git.commit": "Commit",
"git.noCommits": "No commits yet.",
"git.noDiff": "No differences.",
"login.prompt": "Sign in to continue.",
"login.invalid": "Invalid token.",
"login.submit": "Sign in",
@@ -39,9 +147,23 @@ export const en = {
"new.firstPrompt": "What should be done? (optional)",
"new.start": "Start in {dir}",
"activity.thinking": "Thinking…",
"activity.writing": "Writing…",
"activity.tool": "Running {tool}…",
"activity.subagent": "Subagent working: {name}",
"activity.waitingForYou": "Paused – waiting for your answer",
"activity.working": "Working",
"activity.titleBusy": "{n} working",
"activity.titleInput": "Input needed",
"chat.reasoningLive": "Thinking…",
"chat.reasoningDone": "Thought · {n} words",
"settings.reasoningDisplay": "Model reasoning",
"settings.reasoningCollapsed": "Collapsed (click to open)",
"settings.reasoningExpanded": "Always expanded",
"settings.reasoningHidden": "Off",
"settings.reasoningHint": "Thinking of the model (if the model and provider return it).",
"settings.reasoningLive": "Open while thinking, then collapse",
"chat.empty": "Send a message to start.",
"chat.reasoning": "Reasoning",
"chat.thinking": "Working…",
"chat.compacted": "Conversation compacted – summary",
"chat.context": "Context",
"chat.contextHint": "{tokens} tokens in the last request",
@@ -65,7 +187,7 @@ export const en = {
"command.compact": "Summarize the conversation to free up context",
"command.new": "Start a new session",
"permissionMode.default": "Ask before changes",
"permissionMode.default": "Ask (except read-only commands)",
"permissionMode.accept_edits": "Auto-accept edits",
"permissionMode.bypass": "Allow everything",
@@ -74,6 +196,7 @@ export const en = {
"tool.input": "Input",
"tool.output": "Output",
"tool.todosUpdated": "Tasks updated ({done}/{total} done)",
"tool.todos": "Tasks",
"tool.openSubagent": "Open subagent session",
@@ -106,8 +229,6 @@ export const en = {
"settings.language": "Language",
"settings.theme": "Theme",
"settings.themeSystem": "System",
"settings.themeDark": "Dark",
"settings.themeLight": "Light",
"settings.notifications": "Notifications",
"settings.notificationsEnable": "Enable",
"settings.notificationsOn": "Enabled",
@@ -158,6 +279,11 @@ export const en = {
"settings.rawHint": "The complete configuration file including comments. It is validated before saving.",
"settings.revert": "Revert",
"mcp.load": "Tools for the model",
"mcp.loadAuto": "Automatic (on demand when many tools)",
"mcp.loadAlways": "Always send all tools",
"mcp.loadOnDemand": "On demand (mcp_search / mcp_call)",
"mcp.loadHint": "Every tool sent directly costs context on every request. Large servers are better loaded on demand.",
"mcp.reconnect": "Reconnect all",
"mcp.none": "No MCP servers configured.",
"mcp.tools": "{n} tools",
@@ -182,7 +308,6 @@ export const en = {
"permissions.ask": "Ask",
"permissions.deny": "Deny",
"agents.intro": "Modes and subagents. Add your own in the config file ([agents.*]) or as Markdown in .codecraft/agents or .claude/agents.",
"agents.title": "Agents",
"agents.readOnly": "read-only",
"agents.commands": "Slash commands",
@@ -224,6 +349,96 @@ export const en = {
"stats.total": "Total",
"stats.other": "Other",
"stats.noData": "No requests in this period.",
"common.cancel": "Cancel",
"nav.logout": "Sign out",
"login.username": "Username",
"login.password": "Password",
"login.passwordRepeat": "Repeat password",
"login.setupPrompt": "No users yet. Enter the setup code from the server log and create the first admin.",
"login.setupCode": "Setup code",
"login.createAdmin": "Create admin",
"login.invalidCode": "Invalid setup code.",
"login.mismatch": "The passwords do not match.",
"login.or": "or",
"sidebar.shared": "Shared with you",
"chat.readOnly": "read-only",
"chat.readOnlyHint": "This session was shared with you – you can follow it, but not write.",
"chat.backgroundDone": "Background task finished",
"chat.waitingModel": "Waiting for a free slot on {model}…",
"chat.waitingSubagent": "Waiting for a free subagent slot ({agent})…",
"chat.anySubagent": "limit for all subagents",
"tool.background": "background",
"share.title": "Share session",
"share.hint": "Selected users can follow this session read-only.",
"share.noUsers": "No other users.",
"settings.tab.users": "Users",
"settings.tab.folders": "Shared folders",
"settings.tab.account": "Account",
"settings.maxParallelSubagents": "Parallel subagents",
"settings.maxParallelSubagentsHint": "Max. subagents running at the same time across all sessions (0 = unlimited).",
"settings.maxConcurrency": "Parallel requests",
"settings.maxConcurrencyHint": "Max. simultaneous requests to this host/model; more wait in a queue. 1 for servers that process one request at a time.",
"settings.slots": "Slots",
"account.title": "Account",
"account.current": "Current password",
"account.new": "New password",
"account.change": "Change password",
"account.changed": "Password changed. Other devices were signed out.",
"account.hint": "At least 8 characters.",
"users.intro": "Users work in their own folder <workspace>/<username> plus the folders shared with them. Admins can use the whole workspace and manage the server.",
"users.username": "Username",
"users.usernameHint": "Letters, digits, - _ . – also the name of the user's folder.",
"users.displayName": "Display name",
"users.role": "Role",
"users.roleUser": "User",
"users.roleAdmin": "Admin",
"users.login": "Sign-in",
"users.password": "Password",
"users.passwordHint": "Leave empty for single sign-on only.",
"users.created": "Created",
"users.you": "you",
"users.setPassword": "Set password",
"users.newPasswordFor": "New password for {name} (min. 8 characters):",
"users.enable": "Enable",
"users.disable": "Disable",
"users.deleteConfirm": "Delete user \"{name}\"? Their sessions stay visible to admins.",
"users.add": "Add user",
"folders.intro": "Folders inside the workspace that selected users may work in, in addition to their own folder.",
"folders.none": "No shared folders yet.",
"folders.noUsers": "No users without admin role.",
"folders.add": "Share a folder",
"folders.addHint": "The folder is created if it does not exist. Existing project folders can be shared too.",
"folders.remove": "Stop sharing",
"folders.removeConfirm": "Stop sharing this folder? The files are kept.",
"agents.intro": "Modes and subagents with their model. A subagent can run on another model or host – e.g. a small model that writes docs or translations in the background.",
"agents.primaryTitle": "Modes",
"agents.subagentTitle": "Subagents",
"agents.modePrimary": "mode",
"agents.modeSubagent": "subagent",
"agents.source.builtin": "built-in",
"agents.source.config": "config",
"agents.source.file": "file",
"agents.model": "Model",
"agents.sessionModel": "Session model",
"agents.modelHint": "Fixed model for this agent; empty = the model selected in the chat.",
"agents.maxParallel": "Max. parallel",
"agents.maxParallelShort": "max. {n} parallel",
"agents.description": "Description",
"agents.descriptionHint": "The main agent decides by this description when to delegate.",
"agents.mode": "Type",
"agents.tools": "Tools",
"agents.toolsAll": "all tools (e.g. read, glob, grep)",
"agents.prompt": "Instructions",
"agents.reset": "Reset",
"agents.resetConfirm": "Reset \"{name}\" to the built-in defaults?",
"agents.deleteConfirm": "Delete agent \"{name}\"?",
"agents.add": "Add agent",
"agents.addWriter": "Writer (docs, summaries, translations)",
"agents.addBlank": "Empty agent",
"stats.user": "User",
"stats.allUsers": "All users",
"stats.byUser": "By user",
};
export type Messages = Record<keyof typeof en, string>;
+2 -3
View File
@@ -15,11 +15,10 @@ export const languages: Record<string, { name: string; messages: Messages }> = {
const STORAGE_KEY = "codecraft.lang";
/** English is the default; other languages are chosen in the settings. */
function detect(): string {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved && languages[saved]) return saved;
const nav = navigator.language.slice(0, 2);
return languages[nav] ? nav : "en";
return saved && languages[saved] ? saved : "en";
}
let current = detect();