Files
arboretum/packages/web/src/i18n/en.ts
T
johanleroy 8aea0ae32d fix(server, web, vscode): les terminaux ne restent plus noirs, panes côte à côte, git scopé au terminal
Un terminal pouvait rester tout noir alors que sa session tournait. Le PTY était
vivant et avait bien écrit sa sortie : la panne était dans le transport. Le replay
d'attache est une frame BINAIRE, mais un client n'apprend son numéro de canal
qu'avec le message `attached` ; le serveur envoyait le replay AVANT, donc tout
client le jetait sur un canal inconnu. Rien n'était peint, et un TUI au repos
(Claude à son prompt) ne réémet jamais rien de lui-même. `attach()` renvoie
désormais le replay et la gateway l'émet APRÈS `attached` : un seul correctif
serveur répare le web, l'app de bureau et l'extension VS Code, qui portaient le
même défaut client. Le resize de l'attache masquait le bug en provoquant un
SIGWINCH, d'où son apparence intermittente.

Seconde moitié du symptôme (« je tape et rien ne se passe ») : le dock montait
avant la liste des sessions, en déduisait « non attachable » et s'attachait en
observateur, à vie et en silence. Un pane n'attache plus avant de connaître sa
session (`sessions.loaded`).

Attaches sans écran : le message `attach` accepte un `screen` optionnel (défaut
true). Un client qui n'affiche rien et veut seulement répondre à un dialogue ne
prend plus le contrôle de la session, ne lui impose plus ses dimensions (ce qui
figeait la géométrie du vrai terminal) et ne reçoit plus le flux pour le jeter.

Rendre les pannes visibles : la raison d'un exit est écrite dans le terminal
(`[arboretum] bash exited with code 3`) avant le détachement ; un repaint est
forcé si rien n'arrive 1,2 s après l'attache, puis annoncé avec « Refresh
screen » ; les refus de canal remontent à l'écran au lieu d'un console.warn ; le
chemin du CLI claude est revalidé (périmé après une bascule nvm/asdf, le PTY
mourait sans un octet).

Colonnes de terminaux : le dock devient une rangée de colonnes redimensionnables
(3 max), chacune avec ses onglets. Algèbre pure dans lib/dock-model.ts, cinq
invariants documentés, ratios plutôt que pixels. `dockSessionIds` et
`activeDockSessionId` deviennent des computed dérivés : aucun consommateur ni
test existant ne change. Alt+clic ouvre à côté depuis les quatre panneaux. Le
plafond de hauteur du dock suit le viewport au lieu d'un 640 px figé. Correctif
préexistant au passage : PanelSplitter passait ses bornes par valeur, figées au
premier rendu, alors que le clavier les relisait.

Portée git : la vue Changements suit le worktree du terminal focalisé, ou tous
les dépôts de son groupe pour une session de groupe, avec « tout voir » à un
clic. L'index Git de la sidebar reste global (c'est la sortie d'une portée
étroite) et le badge d'activité aussi (il sert à signaler le travail qu'on ne
regarde pas). Seul le TERMINAL impose le contexte : le repli sur l'onglet
éditeur, essayé d'abord, rétrécissait la vue multi-projet dès qu'on ouvrait un
fichier.

Vérifications : acceptance-p17.mjs prouve l'ordre des trames sur un vrai
WebSocket (avec l'ancien ordre : 0 octet rejoué, échec), verify-terminals.mjs
prouve par interaction réelle que le terminal peint, que deux colonnes
coexistent, que la frappe atteint le bon PTY (fichier témoin par cwd) et que la
vue suit le terminal.
2026-08-05 10:56:42 +02:00

716 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export default {
common: {
appName: 'Arboretum',
loading: 'Loading…',
cancel: 'Cancel',
logout: 'Log out',
language: 'Language',
refresh: 'Refresh',
back: 'Back',
search: 'Search',
settings: 'Settings',
close: 'Close',
retry: 'Retry',
confirm: 'Confirm',
delete: 'Delete',
working: 'Working…',
},
login: {
title: 'Sign in with your access token',
tokenLabel: 'Access token',
tokenPlaceholder: 'arb_…',
submit: 'Sign in',
submitting: 'Signing in…',
invalidToken: 'Invalid token',
rateLimited: 'Too many attempts. Please wait before retrying',
genericError: 'Sign-in failed ({status})',
},
sessions: {
title: 'Sessions',
refresh: 'Refresh',
empty: 'No sessions yet. Start one below.',
newSession: 'New session',
cwdLabel: 'Working directory',
cwdPlaceholder: '/absolute/path/to/project',
commandLabel: 'Command',
creating: 'Creating…',
open: 'Open',
observe: 'Observe',
view: 'View',
resume: 'Resume',
fork: 'Fork',
kill: 'Kill',
confirmKill: 'Confirm kill',
live: 'live',
resumable: 'resumable',
sourceDiscovered: 'discovered',
hide: 'Hide',
unhide: 'Unhide',
hiddenBadge: 'hidden',
hideDiscovered: 'Hide external history',
showHidden: 'Show hidden',
hideHidden: 'Hide hidden',
archive: 'Archive',
unarchive: 'Unarchive',
archivedBadge: 'archived',
showArchived: 'Show archived',
hideArchived: 'Hide archived',
clients: 'no client | 1 client | {n} clients',
exitCode: 'exit code {code}',
statusLabel: {
starting: 'starting',
running: 'running',
exited: 'exited',
},
registryStatus: {
busy: 'busy',
idle: 'available',
waiting: 'waiting',
},
dialog: {
waiting: 'Waiting for your input',
deny: 'Deny (Esc)',
kind: {
trust: 'Trust',
permission: 'Permission',
question: 'Question',
plan: 'Plan',
},
},
},
dashboard: {
title: 'Worktrees',
allSessions: 'All sessions',
groups: 'Groups',
},
groups: {
title: 'Groups',
new: 'New group',
create: 'Create',
creating: 'Creating…',
nameLabel: 'Group name',
namePlaceholder: 'e.g. Payments stack',
colorLabel: 'Color',
colorNone: 'No color',
reposLabel: 'Repositories',
empty: 'No group yet. Create one above.',
noRepos: 'No repository selected.',
noReposInGroup: 'No repository in this group yet. Add some below.',
noReposRegistered: 'No repository registered yet. Add some from the dashboard first.',
open: 'Open',
edit: 'Edit repositories',
editDone: 'Done',
remove: 'Delete',
confirmDelete: 'Confirm delete',
repoCount: 'no repo | 1 repo | {n} repos',
worktreeCount: 'no worktree | 1 worktree | {n} worktrees',
sessionCount: 'no session | 1 live session | {n} live sessions',
sessions: 'Group sessions',
dashboard: 'Dashboard',
missingRepo: 'Repository unavailable',
removeFromGroup: 'Remove from group',
sessionsActive: 'no active session | 1 active session | {n} active sessions',
viewList: 'Repos',
viewGrid: 'Terminals',
gridEmpty: 'No active session in this group.',
gridCapped: 'Showing {shown} of {total} sessions. Close some terminals to see the rest.',
newFeature: 'New group session',
spanBadge: 'group · {n} repo | group · {n} repos',
},
crossRepo: {
title: 'New group session',
intro: 'Launch one Claude session that spans every repo in this group: a single conversation across all of them.',
modeLabel: 'Directories',
modeFeature: 'New branch in each repo',
modeMain: 'Main checkouts',
branchLabel: 'Branch name',
branchPlaceholder: 'feature/…',
branchHint: 'Created where missing, reused where it already exists in the repo.',
baseRefLabel: 'Base branch (optional)',
baseRefPlaceholder: "repo's default branch",
commandLabel: 'Session',
reposLabel: 'Repos in this session',
launch: 'Launch session',
launching: 'Launching…',
close: 'Close',
ok: 'worktree ready',
error: 'failed',
skippedLabel: 'skipped',
done: 'Group session started.',
sessionFailed: 'Could not start the session.',
},
repos: {
add: 'Add repo',
adding: 'Adding…',
remove: 'Remove',
pathLabel: 'Repository path',
pathPlaceholder: '/absolute/path/to/repo',
empty: 'No repository registered yet. Add one above, or scan for repos.',
invalid: 'unavailable',
scan: 'Scan',
scanResult: 'No new repository found | 1 new repository added | {n} new repositories added',
hide: 'Hide',
show: 'Show',
showHidden: 'Show hidden ({n})',
},
worktrees: {
new: 'New worktree',
create: 'Create',
creating: 'Creating…',
branch: 'Branch',
newBranch: 'create branch',
baseRefLabel: 'Base branch',
baseRefPlaceholder: 'default branch',
start: 'Start',
starting: 'Starting…',
startNone: 'no session',
startWork: 'Start work',
modeLabel: 'Mode',
modeWorktree: 'Worktree',
modeMain: 'Main branch',
branchModeLabel: 'Branch',
currentBranch: 'current branch',
newBranchOpt: 'new branch',
commandLabel: 'Command',
delete: 'Delete',
confirmDelete: 'Confirm delete',
forceDelete: 'Force delete',
prune: 'Prune',
commit: 'Commit',
commitPlaceholder: 'Commit message',
committing: 'Committing…',
push: 'Push',
pushing: 'Pushing…',
promote: 'Make main',
promoting: 'Promoting…',
promoteConfirm:
'Branch “{branch}” will become the repository’s main checkout; this worktree will be removed (the previous main branch is kept). Continue?',
forcePromote: 'Force (dirty tree)',
main: 'main',
detached: 'detached',
locked: 'locked',
prunable: 'prunable',
clean: 'clean',
dirty: '{n} changed',
noSession: 'no session',
externalSessions: 'external',
},
project: {
new: 'New project',
title: 'New project',
intro: 'Creates a new folder under the chosen root, then starts a session in it.',
nameLabel: 'Project name',
namePlaceholder: 'my-project',
rootLabel: 'Root folder',
rootPlaceholder: '/absolute/path/root',
gitInit: 'Initialize a git repository (git init)',
commandLabel: 'Command',
create: 'Create',
creating: 'Creating…',
},
launch: {
title: 'Start project',
startProject: 'Start project',
startNamed: 'Start project: {name}',
hint: 'Define the commands that start this project. Each enabled command opens its own terminal (e.g. dev server, API, database).',
labelPlaceholder: 'label',
runPlaceholder: 'npm run dev',
cwdPlaceholder: 'subdir',
enabledTitle: 'Launch this command',
remove: 'Remove',
empty: 'No launch command yet. Add one or detect them from the project.',
add: 'Add command',
detect: 'Detect',
detectNone: 'No new command detected.',
save: 'Save',
saved: 'Launch commands saved.',
start: 'Start terminals ({n})',
started: 'Started {n} terminals.',
partial: 'Started {started}, {skipped} skipped.',
stopAll: 'Stop launch ({n})',
},
workspace: {
open: 'Open IDE',
files: 'Files',
changes: 'Changes',
noChanges: 'No changes',
changesTruncated: 'Too many changes. List truncated.',
terminal: 'Terminal',
editor: 'Editor',
diff: 'Diff',
noFileOpen: 'No file open',
noFileHint: 'Pick a file in the tree to edit it, or a changed file to view its diff.',
noSession: 'No session here',
noSessionHint: 'Start a session on this worktree from the dashboard to get a terminal.',
},
ide: {
projects: 'Projects',
addProject: 'Add project',
noProjects: 'No project yet. Add one to get started.',
noWorktrees: 'No worktree',
activity: {
explorer: 'Explorer',
git: 'Git',
sessions: 'Sessions',
groups: 'Groups',
},
startClaude: 'Start Claude',
startBash: 'Start bash',
noFileOpen: 'No file open',
noFileHint: 'Pick a file in the tree to edit it, or a changed file to view its diff.',
terminals: 'Terminals',
resizePanel: 'Resize panel',
resizeColumns: 'Resize terminal columns',
splitTerminal: 'Open beside',
splitTerminalFull: 'Three columns at most',
mergeColumns: 'Merge columns',
terminalColumn: 'Terminal column {n}',
openTerminalBeside: 'Open a terminal beside',
loadFailed: 'Could not load.',
noTerminal: 'No terminal open',
noTerminalHint: 'Open a Claude session from the tree to attach a terminal here.',
comingSoon: 'Coming soon',
ready: 'Ready',
menu: {
newWorktree: 'New worktree',
workOnMain: 'Work on main branch',
openTerminal: 'Open terminal',
commit: 'Commit changes',
push: 'Push',
promote: 'Promote to main',
deleteWorktree: 'Delete worktree',
prune: 'Prune worktrees',
hideRepo: 'Hide repository',
showRepo: 'Show repository',
removeRepo: 'Remove repository',
addLocal: 'Add local repository',
cloneRemote: 'Clone remote repository',
newSession: 'New session',
newProject: 'New project',
newGroup: 'New group',
launchGroup: 'Launch group session',
editGroup: 'Edit composition',
deleteGroup: 'Delete group',
},
},
editor: {
save: 'Save',
unsaved: 'Unsaved changes',
conflict: 'This file changed on disk since you opened it.',
reload: 'Reload',
overwrite: 'Overwrite',
loadingEditor: 'Loading the code editor…',
loadingFile: 'Loading file…',
loadFailed: 'The code editor could not be loaded.',
loadFailedHint: 'Check your connection, then retry. If it keeps failing, reload the page.',
reloadPage: 'Reload page',
syncFailed: 'This file can no longer be read on disk (moved, deleted, or not readable).',
},
diff: {
binary: 'Binary file. No text diff.',
tooLarge: 'Diff too large to display.',
noChanges: 'No changes in this file.',
},
git: {
ahead: 'commits ahead',
behind: 'commits behind',
staged: 'staged',
unstaged: 'unstaged',
conflicts: 'conflicts',
noWorktreeHint: 'Pick a worktree in the explorer to see its changes.',
indexHint: 'Every open project and its worktrees. Pick one to review, commit and push it.',
locked: 'locked worktree',
prunable: 'stale worktree (prunable)',
detached: 'detached HEAD',
upstream: 'Tracking',
noUpstream: 'no upstream branch',
lastCommit: 'Last commit',
repoInvalid: 'Repository missing or no longer readable on disk.',
defaultBranch: 'Default branch',
},
remote: {
title: 'Remote access',
hint: 'How this daemon can be reached from your other devices. These are start-up flags, not hot-editable settings: exposing a web terminal must stay a deliberate choice.',
currentOrigin: 'Origin of this page',
https: 'HTTPS on (notifications and PWA available)',
http: 'Plain HTTP: push notifications and PWA install require HTTPS',
bind: 'Listening on {bind}:{port}',
loopback: 'reachable from this machine only',
exposed: 'exposed beyond this machine',
allowedOrigins: 'Origins allowed in addition to localhost',
noExtraOrigins: 'None. Only http://127.0.0.1 and http://localhost are accepted.',
originHint: 'To open Arboretum from another address (Tailscale name, LAN IP, domain behind a reverse proxy), that address must be allowed at start-up, otherwise the daemon answers 403.',
push: 'Push notifications',
pushOn: 'enabled on this device',
pushOff: 'available, not enabled',
pushUnsupported: 'unavailable (requires HTTPS; on iOS, install the PWA first)',
pwaHint: 'On mobile, install Arboretum as an app (“Add to Home Screen”) for full-screen access and notifications.',
},
changes: {
title: 'Changes',
summary: 'nothing to commit | 1 worktree to review | {n} worktrees to review',
repoDirty: 'no change | 1 changed file | {n} changed files',
allClean: 'Everything is committed and pushed',
allCleanHint: 'No uncommitted change in your open projects. Show clean worktrees to review them anyway.',
scope: {
follow: 'Follow the focused terminal',
all: 'Show every project',
allLabel: 'All projects',
group: 'Group {name}',
emptyInScope: 'Nothing to review in this scope',
emptyInScopeHint: 'This terminal works on a clean tree. Show every project to see the rest.',
},
showClean: 'Show clean worktrees',
hideClean: 'Hide clean worktrees',
expandAll: 'Expand all',
collapseAll: 'Collapse all',
unpushed: '{n} to push',
openInEditor: 'Open in an editor tab',
loadFailed: 'Could not read the changes of this worktree.',
clean: 'clean',
},
history: {
title: 'History',
empty: 'No commit on this branch.',
loadMore: 'Load more commits',
unpushed: '{n} unpushed',
unpushedHint: 'These commits only exist locally: removing this worktree would lose them.',
},
commit: {
staged: 'Staged',
changes: 'Changes',
stage: 'Stage',
unstage: 'Unstage',
stageAll: 'Stage all',
unstageAll: 'Unstage all',
discard: 'Discard',
confirmDiscard: 'Confirm discard (destructive)',
amend: 'Amend last commit',
amendPlaceholder: 'New message (leave empty to keep the previous one)',
messagePlaceholder: 'Commit message…',
commit: 'Commit',
push: 'Push',
fetch: 'Fetch',
pull: 'Pull (fast-forward)',
pullRebase: 'Pull --rebase',
alreadyPushed: 'Cannot amend: this commit was already pushed.',
},
gitconn: {
title: 'Git connections',
hint: 'Connect to Gitea, GitLab or GitHub to clone private repositories. Tokens are encrypted at rest and never shown again.',
empty: 'No connection yet.',
addConnection: 'Add a connection',
add: 'Add',
service: 'Service',
authType: 'Authentication',
pat: 'Personal access token',
appPassword: 'App password',
label: 'Label',
labelPlaceholder: 'e.g. My Gitea',
baseUrl: 'Instance URL',
username: 'Username',
usernamePlaceholder: 'optional',
token: 'Token',
password: 'Password',
test: 'Test',
added: 'Connection added',
deleted: 'Connection deleted',
testOk: 'Connected as {user}',
testFailed: 'Connection failed: {error}',
},
clone: {
button: 'Clone',
title: 'Clone a repository',
noCredential: 'No git connection configured.',
goSettings: 'Add one in Settings',
credential: 'Connection',
search: 'Search',
searchPlaceholder: 'Filter repositories…',
private: 'private',
noRepos: 'No repository found.',
more: 'Load more',
dest: 'Destination folder',
destPlaceholder: '/path/to/clone',
destHint: 'Must be inside a configured scan folder; the folder is created by the clone.',
clone: 'Clone',
starting: 'Starting…',
cloning: 'Cloning…',
done: 'Clone complete',
failed: 'Clone failed',
back: 'Back',
},
terminal: {
observer: 'observer (read-only)',
sessionEnded: 'Session ended',
back: 'Sessions',
notAttachable: 'This session runs outside Arboretum. Fork it to interact, or resume it once it has stopped.',
fullscreen: 'Fullscreen',
exitFullscreen: 'Exit fullscreen',
maximize: 'Maximize',
restore: 'Restore',
openFullscreen: 'Open fullscreen',
group: 'Group',
covers: 'covers',
connecting: 'Connecting to the terminal...',
waitingForOutput: 'No output received yet.',
refreshScreen: 'Refresh screen',
},
push: {
enable: 'Enable notifications',
disable: 'Notifications on',
denied: 'Notification permission denied. Enable it in your browser settings.',
unsupported: 'Push needs HTTPS (e.g. Tailscale Serve); on iOS, install the app to your home screen first.',
},
fs: {
browse: 'Browse…',
up: 'Up',
useFolder: 'Use this folder',
showHidden: 'Show hidden',
loading: 'Loading…',
empty: 'No subfolders',
repoBadge: 'repo',
},
ws: {
reconnecting: 'Connection lost. Reconnecting…',
},
nav: {
ide: 'IDE',
worktrees: 'Worktrees',
sessions: 'Sessions',
groups: 'Groups',
settings: 'Settings',
help: 'Help',
more: 'More',
gitea: 'Source code',
buymeacoffee: 'Buy me a coffee',
waiting: 'waiting',
},
theme: {
label: 'Theme',
dark: 'Dark',
light: 'Light',
system: 'System',
toggle: 'Toggle theme',
},
controls: {
searchPlaceholder: 'Search…',
results: 'no result | 1 result | {n} results',
sortBy: 'Sort by',
filters: 'Filters',
clearAll: 'Clear',
},
sort: {
asc: 'Ascending',
desc: 'Descending',
session: {
state: 'State',
recent: 'Most recent',
clients: 'Clients',
cwd: 'Directory',
command: 'Command',
},
worktree: {
main: 'Main first',
branch: 'Branch',
dirty: 'Changes',
ahead: 'Ahead',
behind: 'Behind',
activity: 'Activity',
},
repo: {
label: 'Name',
created: 'Created',
worktrees: 'Worktrees',
activity: 'Activity',
},
group: {
label: 'Name',
created: 'Created',
repos: 'Repositories',
activeSessions: 'Active sessions',
},
},
filter: {
state: {
label: 'State',
live: 'Live',
waiting: 'Waiting',
busy: 'Busy',
idle: 'Available',
exited: 'Exited',
resumable: 'Resumable',
},
command: {
label: 'Command',
},
source: {
label: 'Source',
managed: 'Managed',
discovered: 'Discovered',
},
repo: {
label: 'Repository',
},
worktree: {
dirtiness: 'Changes',
dirty: 'Dirty',
clean: 'Clean',
session: 'Session',
with: 'With session',
without: 'Without session',
flags: 'Flags',
locked: 'Locked',
prunable: 'Prunable',
},
},
pagination: {
prev: 'Previous',
next: 'Next',
pageOf: 'Page {page} / {pageCount}',
perPage: 'Per page',
all: 'All',
},
palette: {
placeholder: 'Jump to repo, worktree, session, group…',
empty: 'No match',
hint: '↑↓ navigate · ↵ open · esc close',
types: {
repo: 'Repo',
worktree: 'Worktree',
session: 'Session',
group: 'Group',
action: 'Action',
},
actions: {
newSession: 'New session',
addRepo: 'Add repo',
newGroup: 'New group',
splitTerminal: 'Split the terminal dock',
},
},
toast: {
sessionCreated: 'Session started',
sessionKilled: 'Session killed',
sessionResumed: 'Session resumed',
sessionForked: 'Session forked',
sessionHidden: 'Session hidden',
sessionUnhidden: 'Session shown',
sessionsHidden: 'no session hidden | 1 session hidden | {n} sessions hidden',
sessionArchived: 'Session archived',
sessionUnarchived: 'Session unarchived',
repoAdded: 'Repository added',
repoRemoved: 'Repository removed',
worktreeCreated: 'Worktree created',
worktreeDeleted: 'Worktree deleted',
pruned: 'Worktrees pruned',
pushed: 'Pushed',
groupCreated: 'Group created',
groupUpdated: 'Group updated',
groupDeleted: 'Group deleted',
groupSessionLaunched: 'Group session started',
groupSessionPartial: 'Group session started ({n} repo(s) skipped)',
projectCreated: 'Project created',
genericError: 'Something went wrong',
dismiss: 'Dismiss',
},
attention: {
title: 'Needs attention',
count: 'no session waiting | 1 session waiting | {n} sessions waiting',
empty: 'Nothing waiting',
},
settings: {
title: 'Settings',
// Préférences
preferences: 'Preferences',
preferencesHint: 'These apply to this browser only.',
language: 'Language',
notifications: 'Notifications',
notificationsEnabled: 'Enabled. You will be notified when a session needs input.',
notificationsDisabled: 'Disabled.',
enableNotifications: 'Enable notifications',
disableNotifications: 'Disable notifications',
testNotification: 'Send a test',
testSent: 'Test notification sent',
pushUnsupported: 'Push needs HTTPS (e.g. Tailscale Serve); on iOS, install the app to your home screen first.',
// Accès & sécurité
security: 'Access & security',
securityHint: 'Tokens grant full access to this server. Create one per device and revoke any you no longer use.',
tokens: 'Access tokens',
newTokenLabel: 'Label',
newTokenPlaceholder: 'e.g. phone, laptop',
createToken: 'Create token',
tokenCreated: 'Token created',
copyTokenHint: 'Copy this token now. For security it will never be shown again.',
copy: 'Copy',
copied: 'Copied',
current: 'current',
created: 'Created {date}',
lastUsed: 'last used {date}',
neverUsed: 'never used',
revoke: 'Revoke',
confirmRevoke: 'Confirm revoke',
tokenRevoked: 'Token revoked',
lastTokenError: 'You cannot revoke the last active token. Create another one first.',
save: 'Save',
saved: 'Saved',
// Sécurité & conformité
compliance: 'Security & compliance',
complianceHint: 'Export or erase the data tied to your token (GDPR), and review the audit log of sensitive operations.',
exportData: 'Export my data',
refreshAudit: 'Audit log',
deleteData: 'Erase my data',
deleteConfirm: 'Erase the data tied to your token (push subscriptions) and revoke this token? This cannot be undone.',
deleteDone: 'Your data has been erased.',
// Découverte des dépôts
discovery: 'Repository discovery',
discoveryHint: 'Arboretum scans these folders for git repositories and registers them automatically. Hidden repos are kept and never re-added.',
scanRoots: 'Scan folders',
scanRootsEmpty: 'No folder configured. Your home directory is used by default.',
addRoot: 'Add folder',
removeRoot: 'Remove',
scanInterval: 'Re-scan interval (minutes)',
scanIntervalHint: '0 disables periodic scanning. Changes apply on the next daemon restart.',
// Sessions (rétention / archivage auto)
sessions: 'Sessions',
sessionsHint: 'Finished sessions older than the retention window are automatically archived (hidden from the list by default). Nothing is deleted. Archived sessions stay resumable and forkable.',
retentionDays: 'Auto-archive after (days)',
retentionDaysHint: '0 never archives. Archived sessions are hidden by default; toggle "Show archived" in the sessions list to see them.',
// Claude CLI
claudeCli: 'Claude CLI',
claudeCliHint: 'Where Arboretum finds the `claude` binary and its data. Handy when the daemon runs as a service with a minimal PATH that does not include ~/.local/bin.',
claudeBinStatusOk: 'Detected',
claudeBinStatusNotFound: 'Not found. Sessions cannot start until this is resolved.',
claudeBinStatusBadPath: 'The configured path is not an executable file.',
claudeBinSourceConfigured: 'configured',
claudeBinSourcePath: 'PATH',
claudeBinPathLabel: 'Binary path override',
claudeBinPathHint: 'Absolute path to the `claude` executable. Leave empty to auto-detect via PATH. Takes effect on the next session.',
claudeBinPathPlaceholder: 'e.g. /home/you/.local/bin/claude',
claudeHomeLabel: 'Claude home directory',
claudeHomeHint: 'Override the ~/.claude location (transcripts & session registry). Applies on the next daemon restart.',
claudeHomePlaceholder: 'e.g. /home/you/.claude',
// Serveur (lecture seule)
server: 'Server',
serverHint: 'Set at startup via CLI flags. Changing them requires restarting the daemon.',
version: 'Version',
port: 'Port',
bind: 'Bind address',
allowedOrigins: 'Allowed origins',
dataDir: 'Data directory',
vapidPublicKey: 'VAPID public key',
vapidContact: 'VAPID contact',
none: 'none',
flagHint: 'CLI flag: {flag}',
// Support
support: 'Support',
supportHint: 'Arboretum is a free, self-funded side project. If it saves you time, you can support its development.',
supportCta: 'Buy me a coffee',
},
help: {
title: 'Help',
intro: 'Everything Arboretum can do, and how to use it.',
searchPlaceholder: 'Search help…',
noMatch: 'No matching topic',
},
};