diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91f2066d1..984a9bf9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,7 +87,7 @@ jobs: - name: Audit frozen dependencies run: bun audit - run: bun scripts/check-docs.ts - - run: bun test scripts/ci ./bench ./apps/shell/scripts + - run: bun test scripts/ci scripts/migrate ./bench ./apps/shell/scripts # Missing sentences are warnings here and in the nightly; release.yml refuses them. - run: bun scripts/ci/translations.ts - run: bun run check:architecture diff --git a/AGENTS.md b/AGENTS.md index 73af25322..0dccf2e74 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,8 +51,10 @@ prerequisites, adjacent runtime files and shell E2E coverage. webview of that window. - UI colors, radii and durations come from `app.css`, strings from `lib/strings.ts`, which serves them in the language the app speaks. Write - English in `lib/strings.en.ts` and mirror it in every translation - ([docs/language.md](docs/language.md)). No native ``, `window.confirm` or hard-coded hex. - Each connected machine owns its client and Store. Route actions through the owning Store; project and thread IDs can collide between machines. diff --git a/docs/language.md b/docs/language.md index e741a1c22..0f026d9b1 100644 --- a/docs/language.md +++ b/docs/language.md @@ -55,8 +55,8 @@ provider's own name. | File | What it holds | |---|---| | `packages/ui/src/lib/strings.ts` | What every component imports: `strings` in the language of the moment, and `fill`. | -| `packages/ui/src/lib/strings.en.ts` | English. Every user-facing sentence of the UI, and the shape every translation mirrors. | -| `packages/ui/src/lib/strings.fr.ts` | French, typed `Translation`: the English catalogue's shape, literals widened, every sentence optional. | +| `packages/ui/src/lib/strings.en.ts` | English. Spreads one file per top-level block of `strings/en/` (`chat.ts`, `settings.ts`): every user-facing sentence of the UI, and the shape every translation mirrors. | +| `packages/ui/src/lib/strings.fr.ts` | French, typed `Translation`: spreads `strings/fr/`, one file per English part with the same name, literals widened, every sentence optional. | | `packages/ui/src/lib/i18n.svelte.ts` | The choice, the detection, `fill`, and the proxy `strings.ts` re-exports. | | `packages/ui/catalogue-plugin.ts` | The build step that writes each translation over English, so its chunk is whole. | @@ -84,8 +84,19 @@ imports `lib/i18n.svelte` for `setLocaleSetting` and the list of locales. ## Adding a sentence -Put it in `strings.en.ts`, under the block its screen belongs to, then in -`strings.fr.ts` at the same path. +Put it in `strings/en/.ts`, the file of the block its screen belongs to, +then in `strings/fr/.ts` at the same path. Two changes to different +parts never edit the same file. A new block is a new part: a file in +`strings/en/` and in every translation's folder, spread by each index; +`scripts/ci/translations.ts` fails when one of them is missing. + +A branch cut before the split still edits the single `strings.en.ts` and +`strings.fr.ts`, so merging main conflicts on both. Once `git merge origin/main` +stops, run `bun scripts/migrate/split-catalogues.ts`: it splits the merge base +and the branch versions with the codemod that made the split, replays each +part's change onto main's part with `git merge-file`, adds a part the branch +created to both indexes and stages the result. A part both sides changed on the +same lines keeps conflict markers and is listed; resolve it like any conflict. English alone is enough to merge and to ship a nightly. A sentence French has not got shows in English, key by key, never per screen, and a dotted path never @@ -107,9 +118,10 @@ switch. In a component, read `strings.x.y` in the markup or in a ## Adding a language -1. Copy `strings.fr.ts` to `strings..ts`, translate it, keep the type, - and name its export after the code: `scripts/ci/translations.ts` finds a - catalogue by that file name and that export. +1. Copy `strings.fr.ts` to `strings..ts` and `strings/fr/` to + `strings//`, translate them, keep the type, and name each export after + the code: `scripts/ci/translations.ts` finds a catalogue by those file names + and that export. 2. Add the code to `Locale` and `LOCALES` in `i18n.svelte.ts`, and a dynamic import of the catalogue to `LOADERS`. The build finds the chunk by its file name and preloads it on a device that speaks the language. diff --git a/docs/onboarding.md b/docs/onboarding.md index ace0dc0dd..365845d62 100644 --- a/docs/onboarding.md +++ b/docs/onboarding.md @@ -177,8 +177,8 @@ same host settings and shell command as Settings. ## Adding a screen 1. A key in `ORDER` and in `OnboardingStep`, in `lib/onboarding.ts`. -2. A block under `onboarding` in `lib/strings.en.ts`, with a `title` and at - most one sentence, then the same block in `lib/strings.fr.ts`. +2. A block under `onboarding` in `lib/strings/en/onboarding.ts`, with a `title` and at + most one sentence, then the same block in `lib/strings/fr/onboarding.ts`. 3. A branch in `Onboarding.svelte`, between the two it sits between. 4. `ONBOARDING_VERSION` up, since the tour changed shape. diff --git a/packages/ui/catalogue-plugin.ts b/packages/ui/catalogue-plugin.ts index 74cfcc4ee..b93f3e4c5 100644 --- a/packages/ui/catalogue-plugin.ts +++ b/packages/ui/catalogue-plugin.ts @@ -4,7 +4,8 @@ import { parseSync, type Plugin } from 'vite'; /** * A translation as its module ships it: English with every sentence the - * translation has written over it (`src/lib/i18n.svelte.ts`). A device loads + * translation has written over it (`src/lib/i18n.svelte.ts`), one part at a + * time: `strings/fr/chat.ts` over `strings/en/chat.ts`. A device loads * the one catalogue it speaks, and a sentence the translation lacks still * answers in English without the English chunk. The build and the tests both * run it, so what a test reads is what ships. `scripts/ci/translations.ts` @@ -15,11 +16,12 @@ export function completeCatalogues(): Plugin { name: 'boite-complete-catalogues', enforce: 'pre', transform(code, id) { - const locale = /[\\/]src[\\/]lib[\\/]strings\.([a-z]{2})\.ts$/.exec(id)?.[1]; - if (!locale || locale === 'en') return null; - const english = join(dirname(id), 'strings.en.ts'); + // One file per part of the catalogue: `strings/fr/chat.ts` over `strings/en/chat.ts`. + const [, locale, part] = /[\\/]src[\\/]lib[\\/]strings[\\/]([a-z]{2})[\\/]([\w-]+)\.ts$/.exec(id) ?? []; + if (!locale || !part || locale === 'en') return null; + const english = join(dirname(id), '..', 'en', `${part}.ts`); this.addWatchFile(english); - return { code: overEnglish(readFileSync(english, 'utf8'), code, locale), map: null }; + return { code: overEnglish(readFileSync(english, 'utf8'), code, locale, part), map: null }; }, }; } @@ -62,12 +64,14 @@ function keyOf(property: Property, file: string): string { * order, each sentence the translation's when it has one. Values are copied as * written, so a sentence that is a function (a count, a plural) stays one. */ -export function overEnglish(english: string, translation: string, locale: string): string { +export function overEnglish(english: string, translation: string, locale: string, part: string): string { + const englishFile = `strings/en/${part}.ts`; + const translationFile = `strings/${locale}/${part}.ts`; const merge = (reference: Node, translated: Node | undefined): string => { const own = new Map(); - for (const property of (translated?.properties ?? []) as Property[]) own.set(keyOf(property, `strings.${locale}.ts`), unwrap(property.value)); + for (const property of (translated?.properties ?? []) as Property[]) own.set(keyOf(property, translationFile), unwrap(property.value)); const entries = (reference.properties as Property[]).map((property) => { - const key = keyOf(property, 'strings.en.ts'); + const key = keyOf(property, englishFile); const value = unwrap(property.value); const theirs = own.get(key); const name = english.slice(property.key.start, property.key.end); @@ -76,6 +80,6 @@ export function overEnglish(english: string, translation: string, locale: string }); return `{\n${entries.join(',\n')}\n}`; }; - const merged = merge(catalogueObject(english, 'strings.en.ts', 'strings'), catalogueObject(translation, `strings.${locale}.ts`, locale)); + const merged = merge(catalogueObject(english, englishFile, 'strings'), catalogueObject(translation, translationFile, locale)); return `export const ${locale} = ${merged};\n`; } diff --git a/packages/ui/src/lib/catalogue-plugin.test.ts b/packages/ui/src/lib/catalogue-plugin.test.ts index 83aff689b..eba111dbd 100644 --- a/packages/ui/src/lib/catalogue-plugin.test.ts +++ b/packages/ui/src/lib/catalogue-plugin.test.ts @@ -20,7 +20,7 @@ export const fr: Translation = { `; test('a translation ships whole: its sentences over English, English for the rest, in English order', () => { - const source = overEnglish(english, french, 'fr'); + const source = overEnglish(english, french, 'fr', 'common'); // The sample's one type annotation stripped, the module run as a function body. const module = { fr: new Function(source.replace('export const fr = ', 'return ').replace(/\(n: number\)/g, '(n)'))() as Record }; expect(module.fr).toEqual({ @@ -35,6 +35,6 @@ test('a translation ships whole: its sentences over English, English for the res }); test('a catalogue entry that is not a plain key and value is refused with its file', () => { - expect(() => overEnglish(english, `export const fr = { ...other };`, 'fr')).toThrow(/strings\.fr\.ts: a catalogue holds plain/); - expect(() => overEnglish(english, `export const de = {};`, 'fr')).toThrow(/strings\.fr\.ts: expected export const fr/); + expect(() => overEnglish(english, `export const fr = { ...other };`, 'fr', 'common')).toThrow(/strings\/fr\/common\.ts: a catalogue holds plain/); + expect(() => overEnglish(english, `export const de = {};`, 'fr', 'common')).toThrow(/strings\/fr\/common\.ts: expected export const fr/); }); diff --git a/packages/ui/src/lib/strings.en.ts b/packages/ui/src/lib/strings.en.ts index 6f83ecae3..f1b9346d6 100644 --- a/packages/ui/src/lib/strings.en.ts +++ b/packages/ui/src/lib/strings.en.ts @@ -1,3004 +1,183 @@ -/** Every user-facing string of the UI. No literal UI text lives anywhere else. */ -export const strings = { - subscriptionProxy: { - heading: 'Subscription proxy', - enable: 'Use a subscription proxy', - hint: 'Claude and Codex use this machine\'s gateway, Grok does too behind Douane, and OpenCode 2 adds its other models to its own. Limits opens its dashboard inside Boite. Other agents keep their own configuration.', - baseUrl: 'API URL', dashboardUrl: 'Limits dashboard URL', key: 'API key (optional)', - keyHint: 'Leave empty to keep the saved key. This is the proxy\'s API key, not its dashboard management key.', - clearKey: 'Remove saved key', save: 'Save proxy', saved: 'Proxy saved', - configure: 'Configure proxy', native: 'Show account limits', - showDashboard: 'Show proxy dashboard', - openGateway: 'Open {name}', - signInHint: 'Sign in with Open {name}, then reload here. If the page stays blank, this dashboard may block embedded views.', - mixedContent: 'This page uses HTTPS. Set an HTTPS dashboard URL to view it here, or open the HTTP dashboard separately.', - enabledHint: 'When on, the accounts signed in on this machine are no longer used for Claude and Codex, nor for Grok behind Douane.', - /** The single account a proxied provider shows. */ - account: 'Requests go through {name} at {origin}. This machine\'s own sign-ins are not used.', - via: 'via {name}', - quotasFailed: 'Could not read the {name} limits: {error}', - quotasEmpty: '{name} reports no limits yet.', - /** An entry that stands for several gateway accounts, as their average. */ - averageOne: '{name} · 1 account', - average: '{name} · {count} accounts', - entryStatus: { ready: 'Ready', cooldown: 'Cooling down', error: 'Error', disabled: 'Disabled' }, - creditOf: '{remaining} of {limit}', - creditUsed: '{used} used', - /** Where the gateway runs: a URL the user types, or Douane run by this machine's core. */ - where: 'Where the gateway runs', - remote: 'Remote gateway', local: 'Douane on this machine', - localHint: 'The Douane plugin, installed and run by Boite from Settings > Plugins. The proxy points at its loopback port by itself, and follows it if the port changes.', - localMissing: 'The Douane plugin is not installed on this machine.', - localStopped: 'The Douane plugin is stopped.', - localStarting: 'The Douane plugin is starting.', - localRunning: 'Douane {version} answers on 127.0.0.1:{port}.', - openPlugins: 'Open Plugins', - /** Moving this machine's sign-ins and OpenCode keys into Douane. */ - move: { - open: 'Move my connections to Douane', - openHint: 'Douane takes the sign-ins and API keys this machine already has, so nobody signs in again or pastes a key.', - title: 'Move connections to Douane', - intro: 'Claude, Codex and Grok sign-ins move: their tokens rotate, so once Douane holds one it serves that agent through the gateway and this machine\'s copy stops working. Turning the gateway off later means signing in again here. API keys are copied as they are.', - loading: 'Reading this machine\'s sign-ins', - empty: 'Nothing here that Douane can take.', - token: 'Management token (optional)', - tokenHint: 'Only for a Douane whose admin API asks for one. It stays on this machine and is never shown again.', - tokenSaved: 'A management token is saved; leave empty to keep it.', - clearToken: 'Remove saved token', - confirm: 'Move {count}', - moving: 'Moving', - failed: 'Could not move: {error}', - login: '{provider} sign-in', - copilot: 'GitHub Copilot sign-in', - endpoint: '{source} API key', - opencodeGo: 'OpenCode Go key', - route: 'served as {route}', - outcomes: { moved: 'Moved', present: 'Already there', refused: 'Refused', missing: 'No longer here' }, - reasons: { - signedOut: 'Not signed in', unreadable: 'Login file unreadable', noAddress: 'No known API address', - otherApi: 'Not an OpenAI-compatible API', otherSignIn: 'Douane cannot take this sign-in', routeTaken: 'Route used by another endpoint', - }, - }, - }, - /** A settings card an agent put in the conversation (`boite settings request|open`). */ - settingRequest: { - heading: 'Suggested setting', - headingOpen: 'Look in Settings', - headingSensitive: 'Sensitive setting', - apply: 'Apply', - show: 'Show in Settings', - open: 'Open Settings', - refuse: 'Refuse', - dismiss: 'Dismiss', - sensitiveHint: 'This one changes from Settings, beside its explanation. The card closes once it is changed.', - guestHint: 'Only the computer running Boite can change this setting.', - elsewhere: 'This is in the Settings of the computer running Boite.', - loadFailed: 'This settings card did not load.', - to: 'to', - applied: 'Applied', - changed: 'Set to {value} instead', - refused: 'Refused', - opened: 'Settings opened', - dismissed: 'Dismissed', - closed: 'Closed without an answer', - on: 'On', - off: 'Off', - none: 'None', - worktreeProject: 'Inside each project', - worktreeShared: 'In {dir}', - compactAt: 'From {tokens} tokens', - compactAny: 'At any size', - keys: { - asyncQuestions: 'Let agents ask without stopping', - shortAnswers: 'Short answers', - toolSummaries: 'Tool summaries (Claude)', - titleModel: 'Title model', - threadDoneRetentionDays: 'Delete done threads after', - threadDeletionRetentionDays: 'Purge deleted threads after', - worktreeStorage: 'Where new worktrees go', - focusGuard: 'Keep agent windows out of the foreground', - muteAgents: 'Mute agent audio', - agentCpuCapPercent: 'CPU cap for all agents', - agentMemoryBudgetPercent: 'Memory budget for all agents', - threadMemoryCapMb: 'Memory cap per conversation', - memoryReserveMb: 'Memory reserved for other apps', - memoryProtection: 'Protect available memory', - reapOrphans: 'Stop what agents leave running', - autoUpdateHarnesses: 'Update agents automatically', - listenOnLan: 'Reachable on the local network', - publicUrl: 'Public address for phones', - browserOrigins: 'Allowed browser origins', - warmProcessMinutes: 'Keep an agent warm after its turn', - autoCompact: 'Automatic compaction', - autoResumeLimitedThreads: 'Resume threads after a usage limit', - }, - /** What 0 means for the settings where it is not a number. */ - zero: { - agentCpuCapPercent: 'No cap', - threadMemoryCapMb: 'Automatic', - memoryReserveMb: 'Automatic', - threadDoneRetentionDays: 'Never', - threadDeletionRetentionDays: 'Never', - warmProcessMinutes: 'Released at once', - }, - }, - /** A card asking the user to change one setting of a persistent agent (`agents.settings.request`). */ - agentSettingRequest: { - heading: 'Suggested change to {name}', - apply: 'Apply', - show: 'Open its settings', - refuse: 'Refuse', - guestHint: 'Only the computer running Boite can change an agent\'s settings.', - projectsHint: 'Untick a project to leave it out.', - entrust: 'Entrust', - takeBack: 'Take back', - loadFailed: 'This agent setting card did not load.', - to: 'to', - applied: 'Applied', - changed: 'Set to {value} instead', - refused: 'Refused', - closed: 'Closed without an answer', - on: 'On', - off: 'Off', - none: 'None', - noProjects: 'No project', - defaultEffort: 'Default', - minutes: '{count} min', - /** A plain question a persistent agent asked without stopping, shown in its conversation. */ - send: 'Send', - keys: { - projects: 'Projects', - model: 'Model', - effort: 'Reasoning effort', - autoMemory: 'Update memory after each task', - subagents: 'Subagents', - maxRunMinutes: 'Maximum minutes per execution', - }, - }, - remoteBrowser: { - display: 'Display', resolution: 'Page resolution', fitPhone: 'Fit this screen', phone: 'Phone', tablet: 'Tablet', rotate: 'Rotate', width: 'Width', height: 'Height', apply: 'Apply', sharedSize: 'Resolution also changes the page the agent sees.', restoreSize: 'Restore the default size', previewZoom: 'Preview zoom (this device only)', fit: 'Fit', panHint: 'Drag to pan the preview. Use the arrows to scroll the page.', - address: 'Address', addressPlaceholder: 'Enter an address', back: 'Back', forward: 'Forward', reload: 'Reload', badAddress: 'Enter a web address (http or https).', - title: 'Agent’s browser', waiting: 'Waiting for the page', paused: 'Paused', reconnecting: 'Reconnecting', pause: 'Pause', resume: 'Resume', - interact: 'Tap or swipe the shared page', interactDesk: 'The shared page: click it, then type', - deskHint: 'Click the page, then type in it. Drag to select, double-click a word, scroll with the wheel. Copy, cut and paste use the keyboard.', - copy: 'Copy the selected text', copied: 'Copied.', nothingSelected: 'Nothing is selected in the page. Double-tap a word first.', copyCut: 'The selection was too long: its start was copied.', image: 'Live view of the agent’s browser', scrollUp: 'Scroll up', scrollDown: 'Scroll down', - text: 'Tap a page field, then type here', send: 'Send text to the page', gesture: 'Tap to click. Drag to scroll. Return sends the text and Enter; ⌫ on an empty field erases in the page.' - }, - /** The browser the conversation's agent drives on the machine that runs it, watched from any client. */ - agentBrowser: { - cover: 'This agent controls a browser on {machine}', coverHint: 'Nothing streams to this device until you show it.', - show: 'Show', hide: 'Hide', hideHint: 'Stop watching and cover the view again', - none: 'The agent has no browser open', noneHint: 'It appears here when the agent opens a page.', - unavailable: 'No agent browser on {machine}', tabs: 'The agent’s tabs', untitled: 'Untitled page', - newTab: 'New tab', closeTab: 'Close {name}', newTabHint: 'Open a page in this conversation’s browser on {machine}. The agent sees it and can use it.' - }, - /** The Device panel: simulators and emulators an agent opened with `boite device`. */ - devicePanel: { - cover: 'This agent controls a device on {machine}', coverHint: 'Nothing streams to this device until you show it.', - show: 'Show', hide: 'Hide', hideHint: 'Stop watching and cover the view again', - empty: 'No device is open in this conversation. An agent opens one with boite device open, or open one here.', - onMachine: 'On this machine', add: 'Open a device', - none: 'No simulators or emulators were found on this machine.', - starting: 'Starting {name}. This can take a minute.', failed: '{name} did not start', - stopped: 'Off', running: 'Running', booting: 'Starting', - viewOnly: 'View only. The agent drives iOS Simulators with xcrun simctl.', - back: 'Back', home: 'Home', recents: 'Recent apps', rotate: 'Rotate', power: 'Power button', enter: 'Enter', backspace: 'Delete the previous character', - text: 'Type into the focused field', send: 'Send the text', ascii: 'Only printable ASCII characters reach the device.', - screenshot: 'Save a screenshot', close: 'Close {name}', shutdown: 'Power off', - interact: 'Tap or swipe the device screen', interactDesk: 'The device screen: click it, then type', - deskHint: 'Click the screen, then type. Drag to swipe, scroll with the wheel, Esc goes back. Paste types the clipboard; copying from the device is not available.', image: 'Live view of {name}', gesture: 'Tap to touch. Drag to swipe.' - }, - agents: { - runtime: "Models and limits", - brain: "Brain", - routines: "Routines", - defaultModel: "Default model", - allowedModels: "Allowed models for this agent", - subagents: "Subagent models", - enableSubagents: "Allow subagents", - addModel: "Add model", - removeRoute: "Remove this model", - compactEvery: "Compact after this many turns", - runMinutes: "Maximum minutes per execution", - runtimeHint: 'The default model must be allowed. Removing access stops the work that uses it and pauses it for review.', - brainHint: 'Instructions and skills persist across models. Shared contexts keep their own memory.', - compactNow: "Compact this context", - routineHint: "Your agents do these on their own, even with this window closed. A task still running is never started twice.", - runNow: "Run now", - schedule: "Schedule", - once: "Once", - interval: "At an interval", - daily: "Daily", - everyMinutes: "Every N minutes", - timezone: "Time zone", - accountAccess: "Account access", - accountAccessHint: 'Which agents may use each account, subagents included. Removing access stops the executions that use it.', - allAgents: "All agents, including future agents", - heading: 'Agents', intro: 'Agents with their own roles, memory and missions. Conversation subagents appear in Subagents.', back: 'Back to chat', - profiles: 'Agents', groups: 'Groups', teams: 'Teams', missions: 'Missions', attention: 'Needs attention', - list: 'All agents', create: 'New', edit: 'Edit', - search: 'Search', empty: 'Nothing here yet. Create your first entry.', select: 'Choose an entry to see its activity.', - name: 'Name', domain: 'Role', instructions: 'Instructions', identityHint: 'The identity and memory stay when you change its model.', - model: 'Model and account', noAccount: 'Add a provider account in Settings before creating an agent.', permissions: 'Permissions', - ask: 'Ask before actions', edits: 'Allow edits', plan: 'Plan only', full: 'Full access', deny: 'Do not ask', - noApprovals: 'This provider has no interactive tool approval channel. Its launch permissions apply; business decisions remain available.', - tools: 'Collaboration tools', messages: 'Messages', memory: 'Memory', artifacts: 'Results', decisions: 'Decisions', - members: 'Members', responsibility: 'Responsibility', description: 'Description', project: 'Project', noProject: 'No project', - team: 'Team', noTeam: 'No team', group: 'Discussion group', noGroup: 'No associated group', - mode: 'Who responds', mentions: 'Selected recipients', round: 'One response per member', autonomous: 'Take turns automatically', - maxTurns: 'Maximum turns', perAgent: 'Turns per agent', modeHint: 'Each new message starts an allowance. Replies keep the same allowance. Automatic exchanges stop at either limit.', - paused: 'Paused', pause: 'Pause', resume: 'Resume', archive: 'Archive', active: 'Active', archived: 'Archived', - archiveTitle: 'Archive "{name}"?', archiveBody: 'Archiving stops this agent’s work and removes it from the active list. Its profile and history remain available through search. Type its current name exactly to confirm.', archiveName: 'Agent name', - objective: 'Objective', expectedResult: 'Expected result', minutes: 'Total execution minutes', tokens: 'Token limit, blank for none', - finishMission: 'Finish mission', reopenMission: 'Reopen mission', - conversation: 'Chat', activity: 'Activity', send: 'Send', message: 'Write a message', recipients: 'Recipients', - user: 'You', noWork: 'No execution yet.', loadEarlier: 'Load earlier', - openRun: 'Open execution', requested: 'Requested', executed: 'Executed', noUsage: 'Usage not reported', - pending: 'Queued', running: 'Working', waiting: 'Waiting for a decision', done: 'Done', cancelled: 'Cancelled', interrupted: 'Interrupted', error: 'Failed', review: 'Ready for review', open: 'Open', assigned: 'Assigned', accepted: 'Accepted', - included: 'Included in execution', processed: 'Processed', failed: 'Failed', limited: 'Exchange limit reached', - reconcile: 'Review and resume', reconcileHint: 'Inspect the execution and files first. Describe what is already done and what can continue.', reconcileNote: 'What did you verify?', - answer: 'Answer', answerPlaceholder: 'Your decision and instructions', - task: 'Task', tasks: 'Tasks', addTask: 'Add task', acquire: 'Assign', dependencies: 'Wait for tasks', approve: 'Accept result', revise: 'Request changes', reject: 'Reject', feedback: 'Feedback for the next attempt', - result: 'Result', verification: 'Verification reported by the agent', workspace: 'Workspace', copyPath: 'Copy path', - resources: 'Resources', addResource: 'Add resource', directory: 'Directory', url: 'URL', value: 'Value', read: 'Read', write: 'Write', - resourceHint: 'Access declarations guide the agent. The provider’s permissions still control filesystem actions.', - addMemory: 'Add memory', text: 'Text', scope: 'Scope', sources: 'Sources', ownerMemory: 'Added by you', - searchKnowledge: 'Search memory and resources', expiresAt: 'Expires at, optional', expired: 'Expired', expireMemory: 'Expire now', - engine: 'Background work', concurrency: 'Concurrent agents', engineHint: 'Closing this page leaves work running on the connected core.', - idle: 'Available', offline: 'Agents could not be loaded from this machine. Reconnect or update its core.', retry: 'Retry', - stale: 'Connection lost. This is the last received state. Work may still be running on the host.', - history: 'History', - minutesUnit: 'minutes', status: 'Status', settings: 'Settings', overview: 'Overview', createAction: 'Create', rolePlaceholder: 'Research, implementation or review', - newTitle: { profile: 'New agent', group: 'New group', team: 'New team', mission: 'New mission' }, - summary: '{running} working · {pending} queued', - engineSettings: 'Engine settings', engineSettingsHint: 'How much background work this core runs, and which accounts its agents may use.', - advanced: 'More options', identity: 'Identity', noMembers: 'Create an agent first.', - brainFiles: 'Brain files', procedures: 'Procedures', proceduresHint: 'AGENTS.md in the brain folder. Reusable ways of working.', notes: 'Notes', notesHint: 'MEMORY.md in the brain folder. What this agent keeps between conversations.', - contexts: 'Conversation contexts', memories: 'Memories', toEveryone: 'Everyone', - missionMembers: 'Agents on this mission', dangerZone: 'Stop the engine', dangerHint: 'Stops every agent on this core and disconnects its clients.', - stopEngine: 'Stop this core', stopEngineBody: 'Stops all work on this core and disconnects its clients. Started work will need review before resuming. Reopen Boite on the host to start the core again.', - createAgent: 'Create an agent', noAgents: 'No agents yet', noAgentsOn: 'No agents on {machine} yet', more: 'More', noMatch: 'Nothing matches', - noMessagesYet: 'No messages yet', noMissions: 'No missions yet', previewFrom: '{name}: {text}', unread: '{count} unread', - backToChat: 'Back to the conversation', backTo: 'Back to {name}', editRoles: 'Edit roles', startChat: 'Start the conversation', - asks: '{name} asks you', - planned: 'Planned tasks', plannedResult: 'Planned task', - entrustTo: 'Entrust to an agent', entrustToName: 'Entrust to {name}', entrustedTo: 'Entrusted to {name}', takeBack: 'Take it back', - threadEvent: { entrusted: '{name} took over "{title}"', done: 'Done · {title}', blocked: 'Needs you · {title}', stopped: 'Stopped · {title}' }, - openThread: 'Open the thread', deliveryFailed: '{name} did not get it', - messageTo: 'Message {name}', sayHello: 'Say hello to {name}, or plan a task it will do on its own.', - liveWorking: '{name} is working', liveWaiting: '{name} needs you', seeThread: 'Open its thread', - welcomeHint: 'An agent keeps its name, its memory and its tasks. Talk to it, or plan work it does on its own.', - runsHeading: 'Where it runs', runsHint: 'Its conversations, memory and planned tasks stay on this machine. The models come from that machine’s accounts.', - machine: 'Machine', modelLabel: 'Model', machineOffline: 'Offline', machineNotOwner: 'Owner access needed', - machineUnreachable: '{name} is not connected. Reconnect it, then create the agent again.', whatItDoesHint: 'Its standing instructions. You can change them later in its settings.', - namePlaceholder: 'Mira, Scout, Pixel', whatItDoes: 'What it does', instructionsPlaceholder: 'For example: every morning, read the new issues and sum them up in five lines.', - ownedBy: '{name} runs this thread', ownedHint: 'Talk to {name} in its conversation. This thread shows what it does.', openAgent: 'Open {name}', - projects: 'Projects', projectsHint: 'It follows their threads, starts new ones, answers their questions and tells you what matters.', noProjects: 'Open a project first.', looksAfter: 'Looks after {names}', - seeWork: 'See its work', aboutProjects: 'About your projects', memoryUpdated: 'Memory updated', memoryUpdating: 'Updating memory', - autoMemory: 'Update memory after each task', autoMemoryHint: 'A small helper rewrites its notes from what you asked and what it answered.', - brainFolder: 'Brain folder', brainFolderHint: 'Where every agent keeps its instructions and notes on this machine. Changing it moves them all.', brainDefault: 'Default, in Boite’s data folder', - brainChange: 'Change', brainUseDefault: 'Use default', brainMove: 'Move brains here', brainPath: 'Folder on this machine', brainBrowse: 'Browse', brainParent: 'Parent folder', brainUseFolder: 'Use this folder', - robot: { - family: 'Style', shape: 'Shape', color: 'Color', eyes: 'Eyes', top: 'Accessory', shuffle: 'Shuffle', customize: 'Customize', done: 'Done', - option: '{part} {n}', families: { jelly: 'Jelly', bubble: 'Bubble', capsule: 'Capsule', retro: 'Retro' }, - }, - when: { - who: 'Agent', pickAgent: 'Choose an agent', what: 'What should it do?', whatPlaceholder: 'For example: sum up the new messages and tell me what needs an answer.', - when: 'When?', modes: { once: 'Once', daily: 'Every day', days: 'Some days', interval: 'Repeatedly' }, - inAnHour: 'In an hour', thisEvening: 'This evening, 18:00', tomorrowMorning: 'Tomorrow, 09:00', dateAndTime: 'Date and time', - otherInterval: 'Every', minutesUnit: 'minutes', at: 'At', inThePast: 'This date has passed. Pick a time to come.', pickDays: 'Pick at least one day.', - next: 'Next: {when}', name: 'Name', schedule: 'Plan it', newRoutine: 'Plan a task', scheduleThis: 'Plan this message as a task', scheduleShort: 'Plan', - everyMinutes: 'Every {count} minutes', everyHour: 'Every hour', everyHours: 'Every {count} hours', everyDay: 'Every day', everyDays: 'Every {count} days', - minutesShort: '{count} min', hoursShort: '{count} h', - onceAt: 'Once, {date}', daily: 'Every day at {time}', weekdays: 'Weekdays at {time}', weekend: 'Weekends at {time}', onDays: 'Every {days} at {time}', - today: 'today at {time}', tomorrow: 'tomorrow at {time}', dateAt: '{date} at {time}', - lastRun: 'Last run {when}: {state}', stillRunning: 'Its last run has not finished.', - }, - }, - previewComments: { - annotate: 'Reference an element', - picking: 'Select an element in the page. Escape cancels.', - add: 'Add to message', - added: 'Element added to your message.', - unavailable: 'Element selection is unavailable on this page. Use the desktop app for cross-origin pages.', - failed: 'Element selection failed. Reload the page and try again.', - reveal: 'Show this element in the preview', - remove: 'Remove element reference', - stale: 'This preview has changed page. Reopen the original page or select the element again.', - missing: 'This element no longer exists on the page. Select it again.', - tooMany: 'A message can reference at most 8 page elements.', - activityUnsupported: 'Element references cannot be used with /goal or /loop. Send a regular message or remove the references.', - stashUnsupported: 'This draft contains element references and cannot be stashed. Send it or remove the references first.' -}, - artifacts: { viewReplay: 'Play again', viewExpand: 'View full size', viewCollapse: 'Close full size', loadImage: 'Load image', mediaFailed: 'This media could not be previewed. Try again or download the file to open it.', videoFailed: 'This device cannot play this video. Download it to watch it in another app.', saving: 'Saving', zoomIn: 'Zoom in', zoomOut: 'Zoom out', fitImage: 'Fit image', openLocal: 'Open this file in its default application.', preview: 'Preview', download: 'Download', close: 'Close preview', closeImage: 'Close image', enlarge: 'View full size', saved: 'Saved in Downloads', loading: 'Loading file', unavailable: 'No inline preview for this file. Download it to open it.', failed: 'Could not open this file', ambiguous: (name: string, paths: string) => `${name} names several files: ${paths}`, ownerOnly: 'Local file links need an owner connection. Ask the agent to attach the file to share it with this device.' }, - - brain: { - heading: 'Brain', description: 'Your agents share the same instructions and skills.', - folder: 'Brain folder', pathHint: 'Folder path', save: 'Connect folder', browse: 'Browse', useFolder: 'Choose this folder', - enabled: 'Use with agents', disconnect: 'Disconnect', sync: 'Synchronize', working: 'Working', - empty: 'Connect your brain', emptyHint: 'Choose the folder with your instructions and skills.', - change: 'Change folder', - autoPull: 'Pull automatically', onStartup: 'At startup', periodic: 'Periodically', minutes: 'min', - globalInstructions: 'Global AGENTS.md', globalDetails: 'Connected harnesses', - globalHint: 'Uses this brain in every project, including outside Boite. Existing files are backed up and restored when disabled.', - boiteGuide: 'Boite guide', boiteGuideHint: 'A note after AGENTS.md when an agent session starts: the boite command and the panel. About 300 tokens.', - linked: 'Linked', existing: 'Already linked', blocked: 'Needs attention', - interval: 'Pull interval in minutes', intervalHint: 'Choose a whole number from 1 to 1440 minutes.', - detected: 'Contents', instructions: 'Instructions', skill: 'Skills', plugin: 'Plugins', - pluginHint: 'Detected here. Install plugins through their agent.', - details: 'Sync details', noGit: 'Local folder. Connect a Git repository to sync between computers.', - noUpstream: 'Set an upstream branch in Git to enable sync.', dirty: 'Commit your changes in Git before syncing.', - counts: '{ahead} to send, {behind} to receive', lastSync: 'Last synchronized', never: 'Not synchronized yet', - upToDate: 'Up to date', noEntries: 'Nothing detected in this category.', - missing: 'Update Boite on this machine to connect a brain.', - }, - /** The Hooks card under the brain (`HooksCard.svelte`): each agent's own hooks and the runs that did not pass. */ - hooks: { - heading: 'Hooks', - description: 'The scripts your agents run on their own events. The counts and the recent list start when Boite starts on this machine.', - refresh: 'Refresh hooks', - missing: 'Update Boite on this machine to see your hooks.', - hookOne: '{count} hook', hookMany: '{count} hooks', - moduleOne: '{count} module', moduleMany: '{count} modules', - none: 'No hooks yet', - notFound: 'Nothing found', - runOne: '{count} run', runMany: '{count} runs', - noRuns: 'No runs yet', - blockedOne: '{count} blocked', blockedMany: '{count} blocked', - failedOne: '{count} failed', failedMany: '{count} failed', - skippedOne: '{count} skipped', skippedMany: '{count} skipped', - sourceMissing: 'Not found', - sourceError: 'Unreadable', - unreadable: 'Could not read {path}', - configOnly: 'Boite reads this configuration but does not see each run.', - sharedOne: 'Shared with {count} separate account', sharedMany: 'Shared with {count} separate accounts', - /** A separate account without a file the provider's own profile has: `{account}` its label. */ - accountMisses: '{account} does not get {path}', - /** The providers without hooks, joined into one list: `{names}`. */ - withoutOne: '{names} does not run hooks.', withoutMany: '{names} do not run hooks.', - recent: 'Recent', - since: 'Since {time}', - quiet: 'Nothing blocked or failed.', - /** Under the newest runs, the older ones folded: `{count}` of them. */ - more: 'Show {count} more', - outcomes: { blocked: 'Blocked', failed: 'Failed', stopped: 'Stopped', skipped: 'Skipped' }, - }, - diagnostics: { - heading: 'Diagnostics', - intro: 'What this machine recorded about its own problems. An export or an issue is anonymized: paths, names and addresses become placeholders, thread ids stay.', - status: 'Status', - loading: 'Reading the logs', - version: 'Boite', - system: 'System', - uptime: 'Core running for', - lastDay: 'Last 24 hours', - levels: { debug: 'debug', info: 'info', warn: 'warnings', error: 'errors' }, - files: 'Log files', - noFiles: 'No log file yet', - problems: 'Problems', - problemsHint: 'Warnings and errors of the last 24 hours, grouped by kind, errors first.', - problemsEmpty: 'No warning or error in the last 24 hours.', - times: '{count}×', - lastSeen: 'last {time}', - latest: 'Latest message', - threads: 'Threads it touched', - missingThread: 'not on this machine', - log: 'Log', - level: 'Level', - atLeast: { debug: 'Everything', info: 'Info and above', warn: 'Warnings and errors', error: 'Errors only' }, - origin: 'Source', - origins: { all: 'All sources', core: 'Core', shell: 'Desktop app', ui: 'Interface' }, - thread: 'Thread', - allThreads: 'All threads', - search: 'Search the log', - period: 'Period', - periods: { '15m': 'Last 15 minutes', '1h': 'Last hour', '24h': 'Last 24 hours', '7d': 'Last 7 days' }, - loadOlder: 'Load older', - noOlder: 'Nothing older in this period.', - empty: 'No record matches these filters.', - live: 'New records appear while this page is open.', - duration: 'Duration', - details: 'Details', - ids: 'Ids', - agent: 'Agent', - parent: 'Started by', - turn: 'Turn', - request: 'Request', - run: 'Run', - export: 'Export anonymized logs', - exportHint: 'The last 24 hours in one text file a developer can read: environment, grouped problems, threads and the timeline.', - exportDone: 'Saved {name} ({size}).', - exportDownloaded: 'Downloaded {name} ({size}).', - report: 'Report a problem', - reportHint: 'Drafts an issue on GitHub for Boite with your description and, if you agree, the anonymized logs.', - title: 'Title', - titlePlaceholder: 'What went wrong, in a few words', - description: 'What happened', - descriptionPlaceholder: 'What you did, what you expected and what you saw', - includeLogs: 'Include anonymized logs', - preview: 'Preview', - previewHint: 'This is the text the issue will contain.', - previewUpdate: 'Update the preview', - submit: 'Create the issue', - openGithub: 'Open on GitHub', - ghMissing: 'GitHub CLI is not set up on this machine, so the issue opens in your browser. Drag {file} into it to attach the full logs.', - ghMissingNoFile: 'GitHub CLI is not set up on this machine, so the issue opens in your browser.', - opened: 'The issue opened in your browser.', - created: 'Issue created:', - cancel: 'Cancel', - close: 'Close', - agentAccess: 'Agents can read anonymized logs', - agentAccessHint: 'An agent can check whether Boite itself failed and draft an issue about it. It reads the anonymized view only, never the files on disk.', - failed: 'Diagnostics did not load: {reason}', - outdated: 'Boite on {machine} is older than these diagnostics: its log shows below, but the status, the export and the report need an update. Update it in Settings, Machines and updates.', - outdatedHere: 'Boite on this machine is older than these diagnostics: its log shows below, but the status, the export and the report need an update. Update it in Settings, Machines and updates.' - }, - telemetry: { - heading: 'Privacy and analytics', - description: 'Basic counters are on by default. Enhanced analytics are optional. No messages, project names, paths, account details or recordings are collected.', - basic: 'Anonymous counters', - basicHint: 'Count active installations, launches, conversations and turn outcomes. Usage identifiers change daily; a separate identifier counts active installations only.', - enhanced: 'Enhanced analytics', - enhancedHint: 'Share selected public models, reasoning level, speed and permission modes, durations and token volumes with a stable random installation ID. Private models are grouped as other. Turning this off requests deletion of its profile and events.', - unconfigured: 'This build has no analytics relay configured. Nothing is sent.', - pending: 'Deletion is pending. Boite retries in the background. PostHog removes events asynchronously after accepting the request.', - export: 'Export analytics data', retry: 'Retry deletion', - }, - speech: { - enabled: 'Voice dictation', - enabledHint: 'Enable dictation on this device. Turning it off removes the microphone from the message bar, including when a model is installed.', - microphone: 'Microphone', - microphoneHint: 'The input used for dictation on this device.', - defaultMicrophone: 'System default', - microphoneNumber: 'Microphone {number}', - microphoneUnavailable: 'The selected microphone is unavailable. Choose another input.', - microphoneDisconnected: 'The microphone was disconnected. Choose another input and try again.', - testMicrophone: 'Test microphone', - stopMicrophoneTest: 'Stop test', - inputLevel: 'Microphone input level', - microphoneTestHint: 'Speak to check the level. This test stays on your device.', - heading: 'Voice', description: 'The microphone beside Send, up to two minutes at a time. The text waits in the composer until you send it.', - start: 'Dictate', stop: 'Finish dictation', listening: 'Listening', opening: 'Waiting for microphone', transcribing: 'Transcribing', - private: 'On this core only', cloud: 'Transcribed by your API provider', retry: 'Retry transcription', discard: 'Discard recording', - live: 'Live preview', previewFailed: 'Live preview paused. Finish to transcribe the full recording.', - local: 'Local', api: 'API', engine: 'Engine', - engineHint: 'Local transcribes on this machine and the audio never leaves it. API sends the audio to Groq or OpenRouter with your key.', - streaming: 'Live transcription', - whistleLanguages: 'English, French, German, Spanish, Italian, Dutch and Polish', - setup: 'Dictation is not set up on this machine yet. Open Voice settings to set it up in one click.', ownerSetup: 'Ask the owner to configure Voice on this core.', - unsupported: 'Voice needs a newer Boite on {machine}', unsupportedHint: 'This machine runs a Boite version without the voice engine. Update Boite there, then open Voice again.', - unsupportedDictation: 'Dictation needs a newer Boite on {machine}. Update Boite there, then try again.', - notReady: 'Not set up yet', ready: 'Ready', - readyLocal: 'Ready, {model} on this machine', readyApi: 'Ready, {provider} transcribes', - notSetUp: 'Dictation is not set up', needsCli: 'This machine also needs whisper-cli on PATH, or its path under Advanced.', - statusKey: 'Add a {provider} key', downloadingModel: 'Downloading {model}', - statusFailed: 'The download did not finish', statusBroken: 'Voice settings could not be read', repair: 'Save to repair', - setUp: 'Set up dictation', - update: 'Update engine', updateHint: 'A faster engine is available, 8 MB.', - https: 'Microphone access needs HTTPS or localhost. Open this machine through its HTTPS address.', - linux: 'Dictation is not available in the Linux app yet: its webview does not grant the microphone. Open Boite in a browser to dictate.', - denied: 'Microphone permission was denied. Allow it in your browser or system settings, then try again.', - noMicrophone: 'No microphone was found. Connect one and try again.', microphoneBusy: 'The microphone is unavailable. Check whether another app is using it.', - silence: 'No speech was detected. Try again closer to the microphone.', failed: 'Could not record audio. Try again.', - model: 'Model', modelHint: 'Nemotron transcribes live in many languages. Whistle uses little disk space and supports seven languages. Whisper remains available for compatibility.', - tiers: { fast: 'Small download', balanced: 'Balanced', accurate: 'Accuracy' }, - download: 'Download', downloading: 'Downloading', remove: 'Remove {model}', - link: 'Add from a link', linkPlaceholder: 'https://huggingface.co/.../ggml-model.bin', - linkHint: 'An https link to a whisper.cpp ggml file, such as the ones on huggingface.co/ggerganov/whisper.cpp.', - add: 'Add', - advanced: 'Advanced', executable: 'Whisper executable', modelPath: 'Model file', savePaths: 'Save paths', - pathHint: 'Leave both empty to use the models above. A model file set here wins over the list.', - provider: 'Provider', providerHint: 'Both run Whisper Large v3 Turbo. Keys stay on this core.', groq: 'Groq', openrouter: 'OpenRouter', - groqKey: 'Groq key', openrouterKey: 'OpenRouter key', keySaved: 'Saved', keyEmpty: 'Paste a key', keyReplace: 'Paste a new key to replace it', - removeKey: 'Remove', saveKeys: 'Save keys', - fallback: 'Fall back to the other provider', fallbackHint: 'When the first provider fails. Needs both keys. Local never falls back to an API.', - language: 'Language', languageHint: 'Empty detects the language. A two-letter code such as en or fr fixes it.', - saved: 'Saved', ownerOnly: 'The owner of this machine manages it.', - }, - phone: { - preparing: 'The app is still preparing offline files. Reload and try again.', - settingsOffline: 'Settings have not been downloaded yet. Reconnect and open Settings again.', - dialogOffline: 'This dialog has not been downloaded yet. Reconnect and open it again.', - heading: 'Phone app', ownAddress: 'Use another HTTPS address', publicUrl: 'Public HTTPS address', urlPlaceholder: 'https://boite.example.com', - publicUrlHint: 'The HTTPS origin of your reverse proxy, which must forward the page and /rpc to this core. Links to add a device use it.', - install: 'Install Boite', installed: 'Boite is running as an installed app.', - installHint: 'On iPhone, open Share in Safari, then Add to Home Screen. On Android, use Install app or Add to Home screen in the browser menu.', - httpsRequired: 'This connection uses HTTP. Open Boite through HTTPS to enable offline files and notifications.', - ownOrigin: 'Notifications are enabled from the app installed from this machine\'s address.', - pairFirst: 'Add this app to your group before enabling notifications.', - unsupported: 'This browser does not support push here. On iPhone, add Boite to the Home Screen and open it from its icon.', - pushHint: 'When an agent finishes, fails or needs your answer, even while Boite is closed.', - enable: 'Enable notifications', disable: 'Disable notifications', test: 'Send test notification', - enabled: 'Notifications enabled for this device.', testSent: 'The push service accepted the test notification.', - denied: 'Notifications were not allowed. You can change this in the browser or device settings.', - subscriptionFailed: 'The browser did not return a complete push subscription.', - multiMachine: 'Notifications come from the machine this app was installed from, and through it from every other machine of its group.', - relayed: "This machine reaches this device through the machine this app was installed from: enable notifications there, and this machine's come with them.", - tailscale: { - heading: 'HTTPS through Tailscale', - hint: 'Tailscale gives this machine an HTTPS address on your tailnet. Boite serves itself there with tailscale serve, and links to add a device use it. A phone needs Tailscale connected to reach it.', - checking: 'Checking Tailscale…', - missing: 'Tailscale is not installed on this machine.', - download: 'Get Tailscale', - stopped: 'Tailscale is installed but not connected. Open Tailscale and connect, then check again.', - needsLogin: 'Tailscale is signed out on this machine. Sign in, then check again.', - signIn: 'Sign in to Tailscale', - httpsDisabled: 'HTTPS certificates are off for your tailnet. In the Tailscale admin console, DNS page, turn on MagicDNS and HTTPS Certificates, then check again.', - openAdmin: 'Open the admin console', - off: 'Ready: this core can be served at {url}.', - on: 'This core is served at {url}.', - onPublic: 'Links to add a device use this address.', - conflict: '{url} already serves {target}. Boite leaves it alone unless you replace it.', - error: 'The tailscale command did not answer as expected.', - enable: 'Enable HTTPS via Tailscale', - disable: 'Disable', - replace: 'Replace…', - refresh: 'Check again', - pair: 'Add a device', - approve: 'Allow Serve', - replaceTitle: 'Replace what {url} serves?', - replaceBody: 'Tailscale serves {target} at this address now. Replacing it points the address at Boite, and what used it stops being reachable there.', - replaceConfirm: 'Replace', - disableTitle: 'Stop serving Boite through Tailscale?', - disableBody: 'Phones paired through {url} cannot reach this core until it is served again. Their keys stay valid.', - details: { - 'permission-denied': 'Tailscale refused the change. On Linux, run sudo tailscale set --operator=$USER once, then try again.', - 'serve-consent': 'Your tailnet has not allowed Serve yet. Open the link, approve it, then try again.', - 'not-logged-in': 'Tailscale is signed out. Sign in, then try again.', - timeout: 'Tailscale did not answer in time. Try again.', - unknown: 'Tailscale refused the change. Run tailscale serve status on this machine to see why.' - } - } - }, - mobile: { - clearSearch: 'Clear search', - /** Recent's filter: the conversations whose agent or sub-agents are still at work. */ - atWork: '{count} at work', atWorkFilter: 'Show only the conversations at work', - draftTitle: 'What would you like to do?', draftHint: 'Start a task, ask a question, or pick up your work.', - pairTitle: 'Your Boite, on your phone', - pairBody: 'On any of your machines, open Settings, Machines, Add a device, then scan its QR code here.', - pairStep: 'On your computer: Settings, Machines, Add a device.', - pairInstalled: 'Just installed Boite? Add it here too: the iPhone app keeps its connection separately from the browser.', - pairPrivacy: 'The QR code works once and expires within minutes. This phone then reaches every machine of the group.', - scanInApp: 'Use the Scan button here rather than the Camera app: the camera opens the link in Safari, which keeps a connection of its own.', - pairingRequired: 'Add this app to your group', - offlineTitle: 'Waiting for your computer', - offlineBody: 'Keep Boite running on your computer and check your connection. If you use Tailscale, keep it connected on both devices.', - localNetworkTitle: 'Allow the local network', - localNetworkBody: 'Your machines are on your local network, and your browser blocks Boite from it. In the browser that runs the app (Chrome or another), open Settings, Site settings, Local network and allow {host}. On Android, also allow Nearby devices for that browser in its app settings.', - troubleshoot: 'Troubleshoot', troubleshootSteps: 'Connection checks', - checkLocalNetwork: 'Local network', - checkLocalNetworkHint: 'In the browser that runs the app: Settings, Site settings, Local network, allow {host}.', - localNetworkStates: { granted: 'Allowed', denied: 'Blocked', prompt: 'Not answered' }, - checkNearby: 'Nearby devices', - checkNearbyHint: 'In Android settings: Apps, the browser that runs the app, Permissions, Nearby devices, Allow. Android asks for it on top of Local network.', - checkReach: 'Reach {host}', checkOpen: 'Open', - checkReachHint: 'Open it in a browser tab. If the page does not load, this phone cannot reach the machine: check Tailscale, the VPN or the Wi-Fi.', - localNetworkAsk: 'When your browser asks, allow Boite to reach devices on your local network: your machines are there.', - emptyTitle: 'Start a conversation', - emptyBody: 'Send a task to an agent. Your conversations will appear here.', - emptyActivity: 'All caught up', - computer: 'Connected computer', - pairAgain: 'Add again', - settingsDevice: 'This phone', - settingsPhone: 'App & notifications', - settingsBack: 'Back to settings', - navigation: 'Navigation', threads: 'Conversations', activity: 'Activity', project: 'Choose project', - search: 'Find a conversation', noActivity: 'No agents need your attention.', noThreads: 'No matching conversations.' - }, - machines: { - heading: 'Machines', local: 'This PC', projects: 'Projects', recent: 'Recent', - intro: 'Each machine runs its own agents on its own files.', - details: 'Details', address: 'Address', updateFailed: 'Update failed', - label: 'Machine name', icon: 'Machine icon', icons: { desktop: 'Desktop', laptop: 'Laptop', server: 'Server', rack: 'Server rack', cloud: 'Cloud', cpu: 'Processor' }, link: 'Link from another machine', add: 'Add machine', adding: 'Connecting', connect: 'Connect', - addHint: 'On a machine of your group, open Settings, Machines, Add a device, copy the link and paste it here.', - scan: 'Scan the code', pasteLink: 'Paste the link', scanTitle: 'Scan the code', - linkInsecure: 'This link leads to a plain HTTP address, which the app cannot open. Add the device from a machine that has an HTTPS address.', - scanHint: 'On any of your machines, open Settings, Machines, Add a device and point the camera at its QR code.', - scanStarting: 'Starting the camera', scanClose: 'Close the camera', - scanErrors: { - insecure: 'The camera needs an HTTPS address. Open Boite through its HTTPS address, or paste the link.', - denied: 'Camera access was refused. Allow it for this site in the browser settings, or paste the link.', - missing: 'No camera was found on this device. Paste the link instead.', - failed: 'The camera could not start. Paste the link instead.' - }, - linkReaches: 'This link connects to {host}.', linkReplaces: 'This link connects to {host}, listed here as {machine}. It replaces the key held for it.', linkConnected: 'This link is for {host}, already connected here as {machine}.', - remove: 'Remove machine', removeTitle: 'Remove {machine}?', - removeBody: 'Boite forgets this machine on this device.', - open: 'Open machine', invalidUrl: 'Machine URL must be an HTTP or HTTPS URL without credentials, query or fragment.', - duplicate: 'This machine is already connected.', refreshPr: 'Refresh pull request', - manual: 'Connect with URL and token', - filter: 'Filter machines', all: 'All machines', - main: 'Main machine', mainLast: 'Last one used', - mainHint: 'Boite opens on this machine. When it is off, Boite opens on another one.', - mainLastHint: 'Boite opens on the machine you used last.', - timeout: 'Machine did not answer within 12 seconds. Check its address and browser origins, then reconnect.', - linkTitle: 'Connect to {host}?', - linkBody: 'The link you opened points this page at a machine this device has never connected to. That machine will see everything you type here. Connect only if you made this link or trust who sent it. Cancel keeps your current machine.', - linkConfirm: 'Connect', - browserOrigins: 'Allowed browser origins', - browserOriginsHint: 'Machines of the group, and the addresses they are served at, are allowed on their own. Add here only a page served by something outside the group. One exact http(s) origin per line.', - settings: 'Settings for this machine', settingsHint: 'Resource limits, process retention, agent updates and worktree storage apply only to this machine.', - synced: 'Common preferences synchronized with {source}.', - syncBrainAbsent: 'Connect a brain folder on this machine to copy its switches too.', - syncKeysAbsent: 'One of the two machines runs a Boite without keybindings, so they stayed as they were.', - syncStopped: 'The copy stopped at {stage}: {error}. What came before it stays copied.', - syncStages: { settings: 'the settings', keybindings: 'the keybindings', brain: 'the brain switches' }, - syncProviders: 'Still to sign in on this machine: {providers}. Sign-ins never leave the machine they were made on.', - syncOpenProviders: 'Open its providers', - threadFailed: 'A thread failed here: {thread}', - agentLinkAddressRequired: 'Update both machines to relay through the app, or set a reachable HTTPS address for {machine} in General settings.', - }, - group: { - rename: 'Rename group', merging: 'Grouping connected machines', - otherGroup: '{machine} belongs to {group}. Leave that group on the machine to move it here.', - heading: 'Group', - intro: 'Your machines and your phones, in one group. Each machine reaches the others on its own, and a phone added on any of them reaches all of them. Every machine of a group has full control of the others: group only what is yours.', - name: 'Group name', namePlaceholder: 'Home', - join: 'Join', joining: 'Joining', joinLabel: 'Link from another machine', - joinHint: 'On a machine of the group, open Settings, Machines, Add a device, copy the link and paste it here. This machine joins that group, and its phones come along.', - members: 'Machines and devices of the group', self: 'This machine', connected: 'Connected', connecting: 'Connecting', - relayed: 'Connected through {machine}', through: 'Through {machine}', - unreachable: 'Not reachable from here. It may be off, asleep or on another network.', - insecure: 'That machine gives no address this device may use, and no machine of the group can carry it there. Give it a public HTTPS address.', - loopback: 'This machine listens only on itself, so the others cannot reach it. Connect it to Tailscale, or turn on listening on the network.', - copied: 'Copied', - addDevice: 'Add a device', adding: 'Preparing', - addOlder: 'Boite on {machine} is too old to add a device. Update it under Updates on this page, then come back.', - scanTitle: 'Scan with your phone', - addHint: 'Open Boite on the phone and tap Scan the code. On a computer, copy the link and paste it under Join.', - qr: 'The link that adds a device, as a QR code', - copyLink: 'Copy link', link: 'Link for a computer', - expires: 'Works once, until {time}', - device: 'Device', phone: 'Phone', browser: 'Browser', devicesList: 'Devices of the group', - deviceAdded: 'Added on {machine}', - deviceLegacy: 'Moves to the group key the next time it connects', - thisDevice: 'This device', - revokeDevice: 'Remove from the group', revokeDeviceTitle: 'Remove {device} from the group?', - revokeDeviceBody: 'Every machine of the group refuses it from now on, including those that are off, as soon as they come back. Adding it again takes a new code.', - remove: 'Remove from the group', removeTitle: 'Remove {machine} from the group?', - removeBody: 'The other machines stop trusting it as each one hears of it. A machine that is off learns it when it comes back. The phones added on it stay in the group.', - leave: 'Leave the group', leaveTitle: 'Leave {group}?', - leaveBody: 'This machine forgets the group. Phones added on this machine leave the group with it.', - syncAll: 'Keep the settings of every machine like {source}', - syncAllHint: 'Copies limits, process guards, agent updates, keybindings and brain switches to every machine of the group, now and after each change, while this window is open. Network access and sign-ins stay on each machine.', - lanHint: 'A phone reaches this link once the local network is on and Boite has restarted.', - reach: { - loopback: 'This link only works on this computer: Boite does not listen on the local network yet.', - button: 'Make it reachable on the local network', - restarting: 'Boite is restarting to listen on the local network. Running agents finish their current step and carry on.', - failed: 'Boite did not come back on the local network. Quit it from the tray, open it again, then add the phone.' - }, - firewall: { - blocked: 'Windows Firewall blocks Boite on this {networks} network, so a phone cannot reach this computer.', - unset: 'Windows Firewall has not allowed Boite on this {networks} network yet, so a phone cannot reach this computer.', - allow: 'Allow in Windows Firewall', - allowing: 'Waiting for Windows', - allowed: 'Windows Firewall now lets phones reach Boite on every network.', - cancelled: 'Nothing changed: Windows needs an administrator to accept its prompt.', - failed: 'Windows refused the change. Allow boite-core.exe in Windows Firewall settings.', - unreadable: 'Windows Firewall could not be read. If a phone cannot connect, allow boite-core.exe there.', - networks: { public: 'public', private: 'private', domain: 'work' }, - and: ' and ' - } - }, - app: { - name: 'Boite', - /** What a nightly build calls itself: window title and tray, never the title bar. The settings name its track Boite Nightly. */ - nightlyName: 'boite (de nuit)', - /** The tag the title bar shows when the core is the dev install. */ - channelDev: 'Dev', - channelDevTitle: 'The dev channel, on its own data directory', - loading: 'Starting', - /** While the core compacts its journal before serving, once after an update. */ - compacting: 'Freeing disk space in the history. This happens once and can take a few minutes.', - noEndpointTitle: 'No core to connect to', - noEndpointBody: - 'On one of your machines, open Settings, Machines, Add a device, and scan or type its code here.', - openSettings: 'Open settings' - }, - - titlebar: { - minimize: 'Minimize', - maximize: 'Maximize', - restore: 'Restore', - quitHold: 'Hold Ctrl+Q to quit', - quitHoldHint: 'or press it twice', - quitTitle: 'Close Boite?', - quitWorkingOne: '1 agent is still working or waiting. Are you sure you want to close Boite?', - quitWorkingMany: '{count} agents are still working or waiting. Are you sure you want to close Boite?', - quitUnknownBody: 'Work on a disconnected machine could not be checked.', - quitResidentBody: 'Resident engines keep working after the app closes. Engines configured to stop with the app will stop.', - quitConfirm: 'Close Boite', - quitCancel: 'Keep open' - }, - - serverUpdate: { - heading: 'Server', - updates: 'Updates', - current: 'Up to date', - waiting: 'Waiting for work to finish', - installing: 'Restarting the server', - reconnecting: 'Reconnecting', - progress: 'Server update download', - release: 'What\'s new', - details: 'Details', - timing: 'Agents finish their current tool call, 30 seconds at most, then the server updates, restarts and resumes them. An agent update under way finishes first.', - backup: 'Conversations and settings are backed up. If startup fails, the previous version is restored.', - reconnect: 'Connected devices reconnect automatically.', - confirmTitle: 'Update {machine}?', - confirmBody: 'Agents finish their current tool call, 30 seconds at most. Version {version} then installs and they resume after the restart. Your conversations are backed up.', - manualTitle: 'Manual update', - docker: 'After agents finish, run this in the Compose directory:', - manual: 'Use the guide to update this installation.', - legacy: 'Update it once manually to enable this button.', - guide: 'Update guide' - }, - desktopUpdate: { - heading: 'Desktop app', - progress: 'Desktop app update download', - ready: 'Downloaded, ready to install', - installing: 'Installing, Boite restarts there', - current: 'Up to date', - elsewhere: 'Install this update on that machine itself: its installer may ask for a confirmation.', - confirmTitle: 'Update Boite on {machine}?', - confirmBody: 'Version {version} installs once the agents there are done. Boite restarts on {machine} and running work resumes.', - requested: 'Another machine is installing Boite {version} here. Boite restarts in a moment.' - }, - appUpdate: { - released: 'Released {time}', - changelog: 'Changelog on GitHub', - heading: 'App updates', - intro: 'Choose the releases this desktop app follows. Stable and nightly share this installation, conversations and settings.', - unsupported: 'Automatic app updates are available in installed builds: the Windows x64 installer, the macOS app, and the Linux .deb or AppImage.', - channel: 'Release channel', - stable: 'Boite', - nightly: 'Boite Nightly', - nightlyHint: 'Nightly is experimental and may contain unfinished changes. You can return to Boite even when its version number is lower.', - installedVersion: 'Installed version', - idle: 'No update check has run yet.', - checking: 'Checking for updates', - available: 'Version {version} is available.', - downloading: 'Downloading {received} of {total}', - downloaded: 'Downloaded {received}', - ready: 'Version {version} is ready to install.', - waiting: 'Waiting for work to finish', - installing: 'Agents finish their current step, then the installer starts', - current: 'Boite is up to date.', - failed: 'The update failed.', - lastChecked: 'Checked at {time}', - progress: 'App update download', - notes: 'Release notes', - retained: 'Changing channels keeps your conversations and settings.', - check: 'Check for updates', - checkingAction: 'Checking', - install: 'Install and restart', - readyAction: 'Update', - installTitle: 'Install the Boite update?', - installBody: 'Agents finish their current tool call, 30 seconds at most. Boite then updates, restarts and resumes them.', - }, - updateAll: { - heading: 'Updates', - trigger: 'Updates available', - busy: 'Updating', - run: 'Update all', - app: 'Update the app', - retry: 'Try again', - later: 'Remind me later', - restarts: 'Boite downloads its release, then asks to restart. Threads resume where they were.', - summary: (count: number) => count === 1 ? '1 update' : `${count} updates`, - downloading: (percent: number | null) => percent === null ? 'Downloading' : `Downloading ${percent}%`, - ready: 'Restart to finish', - restartsNext: 'Restart once the agents are done', - waiting: 'Waits for work to finish', - installing: 'Restarting', - updating: 'Updating', - waitingFor: (count: number) => count === 1 ? 'Waits for a thread to pause' : `Waits for ${count} threads to pause`, - failed: 'Failed', - done: 'Up to date', - allDone: 'Everything is up to date', - finish: 'Boite is ready: restart it to finish.', - restart: 'Restart' - }, - - connection: { - machine: 'Machine', - local: 'This PC', - current: 'Current machine', - machines: 'machines connected', - issues: 'need attention', - manage: 'Manage machines', - folder: 'Folder', - browse: 'Browse folders', - nativeBrowse: 'Choose in Windows Explorer', - parent: 'Parent folder', - emptyFolder: 'No subfolders', - unavailable: 'Could not connect', - idle: 'Not connected', - connecting: 'Connecting', - ready: 'Connected', - closed: 'Disconnected', - offline: 'Offline', - label: 'Connection' - }, - - threadStatus: { - idle: 'idle', - queued: 'queued', - running: 'running', - waiting: 'waiting for you', - error: 'error' - }, - - drafts: { - name: 'Drafts', - hint: 'A folder of its own for each conversation, in Documents/Boite', - openFolder: 'Work in a folder of mine', - pickFolder: 'Open a folder', - incognito: 'Incognito', - incognitoStart: 'Start this conversation incognito: nothing of it is kept once you leave it', - incognitoStop: 'Start an ordinary conversation instead', - incognitoOn: 'Incognito: this conversation and its folder are erased when you leave it' - }, - firstRun: { - heading: 'Open a project', - deviceBody: 'Projects are opened from the app the core runs in. The ones it holds show up here.', - pathPlaceholder: 'Absolute path of the folder', - }, - - /** The guided connection: from "nothing works yet" to a composer on a signed-in agent. */ - connect: { - button: 'Connect an AI', - title: 'Connect an AI', - intro: 'Boite works with an AI subscription you already have. Pick it, Boite installs what it needs and opens its sign-in page.', - more: 'Other agents', - plan: { - claude: 'Uses a Claude Pro or Max plan', - codex: 'Uses a ChatGPT plan' - }, - otherPlan: 'Uses your {provider} account', - install: 'Install {provider}', - installNote: 'Downloads {size}. Nothing else changes on this computer.', - signIn: 'Sign in to {provider}', - signInNote: 'Your browser opens the sign-in page. Come back here once it says you are connected.', - manual: '{provider} installs with its own installer. Run it, then check again here.', - manualOpen: 'Open the installer page', - external: '{provider} signs in from its own window. Sign in there, then check again.', - checkAgain: 'Check again', - ready: '{provider} is connected.', - use: 'Use {provider}', - back: 'Back', - device: 'No AI is connected on the computer running Boite yet. Connect one from that computer.', - reconnect: 'Sign in again', - signedOut: '{provider} is signed out' - }, - - drop: { - title: 'Drop to add a project', - body: 'Each folder becomes a project. Files go to the message.', - attachTitle: 'Drop to attach', - attachBody: 'The files go to the message you are writing.', - noComposer: 'Open a conversation to attach the dropped files.' - }, - - sidebar: { - doneThreads: 'Done', - /** Archived by hand to pick up later; done threads have their own group. */ - archivedGroup: 'Archived', - workingThreads: 'Working', - projectWorkingThreads: '{project}: {count} working', - projectDoneThreads: '{project}: {count} done', - projectArchivedThreads: '{project}: {count} archived', - /** The fold under the list for projects with no open conversation, and what choosing one does. */ - idleProjects: 'Idle projects', - /** The same fold once it also holds archived projects. */ - otherProjects: 'Other projects', - archivedProject: 'Archived', - groupingOptions: 'Display options', - /** The sidebar's top switch between the thread list and the Agents page. */ - surface: 'Threads or agents', - threads: 'Threads', - agents: 'Agents', - /** The count beside Threads or Agents in that switch: what works now, and whether something there needs the user. */ - surfaceWorking: '{count} at work', - surfaceWaiting: '{count} at work, something needs you', - surfaceSubagentsOne: 'including 1 subagent', - surfaceSubagentsMany: 'including {count} subagents', - /** The two ways the display options can list threads. */ - projectsView: 'Projects', - recentView: 'Recent', - /** The display options panel: its sections, and the short names its rows use under them. */ - displayView: 'View', - displayOrder: 'Project order', - displayGroup: 'Group', - groupWorkingRow: 'Working threads', - groupIdleRow: 'Idle projects', - customOrderShort: 'Custom', - autoArchiveMergedPrHint: 'A worktree conversation leaves the list for Done once its branch is merged. Its history stays. Applies to this project.', - autoRemoveWorktreesHint: 'An hour after every conversation in a worktree is done or archived, Boite deletes the worktree if nothing would be lost: its last commit is on another branch, or a merged pull request has that exact commit. A merged branch goes too. Uncommitted changes always stay. Applies to this project.', - noActiveProjects: 'No conversations need attention.', - markDone: 'Mark done', - doneToast: 'Marked "{title}" done', - doneLoadFailed: 'Some completed threads could not be loaded.', - /** The pull request under a done or archived conversation, which opens it on the forge. */ - prMerged: '#{number} merged', - prClosed: '#{number} closed', - prOpen: '#{number} open', - reopenThread: 'Move to Recent', - reopenNamedThread: 'Move "{title}" to Recent', - doneNotice: 'This thread is done. Move it to Recent to continue.', - /** Above the composer, or the done notice, of a thread whose worktree Boite removed. */ - worktreeGone: { - title: 'Worktree removed', - merged: 'Boite removed this thread\'s worktree {when}, after #{number} was merged.', - clean: 'Boite removed this thread\'s worktree {when}: every commit in it was already on another branch.', - branchDeleted: 'Branch {branch} was deleted.', - branchKept: 'Branch {branch} is still there.', - archived: 'Moved to Recent, this thread has no folder to work in.', - live: 'A new message here has no folder to run in. Start a new thread to keep working.' - }, - allProjects: 'All projects', - filterProject: 'Filter by project', - recentOrder: 'Recent activity', - customOrder: 'Custom order', - moveProjectUp: 'Move project up', - moveProjectDown: 'Move project down', - search: 'Search threads', - newThread: 'New thread', - newThreadIn: 'New thread in {project}', - addProject: 'Add a project', - noProjects: 'No project yet.', - noThreads: 'No thread yet.', - noMatch: 'Nothing matches.', - unread: 'Unread', - collapse: 'Hide sidebar', - expand: 'Show sidebar', - resize: 'Resize the sidebar, double-click to reset', - settings: 'Settings', - projectMenu: 'Project actions', - copyPath: 'Copy path', - manageProject: 'Manage project', - worktreeDefault: 'Worktree by default', - autoArchiveMergedPr: 'Hide merged PR conversations', - autoRemoveWorktrees: 'Remove settled worktrees', - backToProjectMenu: 'Back', - viewArchivedThreads: 'View archived threads', - importSession: 'Import a Claude Code session', - /** Out of the list, not out of Boite: its threads keep running and a new one brings it back. */ - archiveProject: 'Archive project', - refreshIcon: 'Refresh icon', - projectArchivedToast: 'Archived {project}', - archivedProjects: 'Archived projects', - restoreProject: 'Restore', - restoreAndStart: 'Restore {project} and start a thread', - removeProject: 'Remove from Boite', - /** On a project whose folder was deleted, moved or renamed outside Boite. */ - projectMissing: 'Folder not found', - projectMissingBody: 'The folder {path} does not exist any more: it was deleted, moved or renamed. Put it back to keep working here, or remove the project from Boite.', - projectMissingCheck: 'Check again', - removeProjectTitle: 'Remove {project} from Boite?', - removeProjectBody: 'Its threads go with it. Files on disk stay where they are.', - remove: 'Remove', - threadMenu: 'Thread actions', - /** A pencil on a thread's row: something typed in its box and not sent. */ - unsentDraft: 'Unsent draft', - retitle: 'Regenerate title', - retitling: 'Writing a title', - /** What a row shows in place of its time while the agent works or has news. */ - state: { - working: 'Working', - workingFor: 'Working for {elapsed}', - waiting: 'Needs you', - error: 'Failed', - done: 'Done', - queued: 'Queued', - /** The turn ended, the agent waits on the agents it delegated to: their results wake it. */ - delegating: 'Sub-agents', - delegatingOne: 'Waiting on 1 sub-agent', - delegatingMany: 'Waiting on {count} sub-agents', - /** The chip a parent's row carries while its delegated agents work, in every state. */ - subagentsOne: '1 sub-agent at work', - subagentsMany: '{count} sub-agents at work', - /** The chip's own words, its count written out: the full sentence stays in its title. */ - subagentsShortOne: '1 agent', - subagentsShortMany: '{count} agents', - /** The same chip once a sub-agent asks the user something: the parent reads Needs you. */ - subagentsAskingOne: '1 sub-agent needs you', - subagentsAskingMany: '{count} sub-agents need you', - /** The turn ended, the agent still watches something: a monitor it left going. */ - monitoring: 'Monitoring', - monitoringFor: 'Monitoring for {elapsed}', - /** The turn ended, the agent still runs a shell, a subagent or a workflow. */ - background: 'In background', - backgroundFor: 'In background for {elapsed}', - /** The turn ended, Boite watches pull requests for the agent and wakes it when they need it. */ - watching: 'Watching PR', - watchingMany: 'Watching {count} PRs', - /** A delegated agent's turn ended on its own background work, which wakes it: what it waits for. */ - awaiting: 'Waiting on {what}', - /** That work ended without waking it: the agent stopped there, its result never came. */ - interrupted: 'Interrupted', - /** Its machine stopped answering while it worked: what it does now is unknown until the machine is back. */ - unreachable: 'Unreachable', - unreachableWas: 'Machine unreachable. Last known state: {state}', - interruptedBy: '{what} ended without waking it: {reason}', - interruption: { - 'error': 'the task failed', - 'cancelled': 'the task was cancelled', - 'no-longer-reported': 'its process ended', - 'session-ended': 'its agent process was ended', - 'core-restarted': 'Boite or the PC restarted' - } - }, - /** A folded project's mark for the threads it hides: `2 threads: Needs you`. */ - rollupOne: '1 thread: {state}', - rollupMany: '{count} threads: {state}', - pin: 'Pin', - unpin: 'Unpin', - archive: 'Archive', - deleteUnavailable: 'Update Boite on the machine hosting this conversation to delete it. The conversation has been kept.', - deletedToast: 'Deleted "{title}". You can restore it from Settings.', - /** The toast after an archive, with its way back: the button and Ctrl+Z. */ - archivedToast: 'Archived "{title}"', - undo: 'Undo', - undoHint: 'Undo (Ctrl+Z)', - draft: 'New thread', - /** A draft row's menu: what is typed there and not sent goes. */ - deleteDraft: 'Delete draft' - }, - - /** Moving a thread to another project: the menu entry, its project picker and the question about background work. */ - threadMove: { - moveTo: 'Move to project', - /** A project put away, in the picker: moving a thread there brings it back. */ - archivedProject: '{project} (archived)', - noOtherProject: 'No other project', - /** Why the entry is out while a sub-thread works: the parent's turn end is no time to move it. */ - stopFirst: 'A sub-thread is working; stop it before moving this thread', - /** The confirm for a thread whose turn runs: the move waits for the turn to end. */ - runningTitle: 'Move when this turn ends', - /** `{folder}` is the folder the thread leaves, `{project}` the one it goes to. */ - runningBody: 'The agent is working in {folder}. The thread moves to {project} when the current turn ends.', - runningConfirm: 'Move after this turn', - /** Same, when the agent also runs something in the background: stop it with the move, or keep it running. */ - runningBackgroundBody: 'The agent is working in {folder}. The thread moves to {project} when the current turn ends. Something it started keeps running in the background: stop it then, or keep it running there until the agent starts its next turn in {project}.', - /** Under the thread's title in the sidebar and the header until the move happens. */ - pending: 'Moves to {project} after this turn', - cancelMove: 'Cancel move', - monitorsTitle: 'Monitors still running', - backgroundTitle: 'Work still running in the background', - /** `{folder}` is the folder the thread leaves, `{project}` the one it goes to. */ - backgroundBody: 'The agent still runs something in {folder}. Stop it now, or keep it running there until the agent starts its next turn in {project}.', - stopMonitors: 'Stop monitors', - stopWork: 'Stop it', - keep: 'Keep them', - /** Said by a project while a thread is dragged over it. */ - dropHere: 'Move the thread to {project}', - /** Under the card a dragged row becomes, while no project would take it. */ - dragHint: 'Drop on another project' - }, - - palette: { - placeholder: 'Search threads, or type a command', - terminal: 'Show or hide the terminal', - threads: 'Threads', - commands: 'Commands', - empty: 'Nothing matches.', - hint: 'Enter to open, Esc to close', - newThread: 'New thread', - addProject: 'Add a project', - importSession: 'Import a Claude Code session', - settings: 'Open settings', - appearance: 'Appearance settings', - providers: 'Providers and accounts', - pair: 'Add a device', - sidebar: 'Toggle the sidebar', - panel: 'Toggle the right panel', - trace: 'Show the trace', - changes: 'Show the changes', - files: 'Show the files', - tasks: 'Show the tasks', - subagents: 'Show the subagents', - agents: 'Open the agents', - pin: 'Pin this thread', - unpin: 'Unpin this thread', - rename: 'Rename this thread', - retitle: 'Regenerate the title of this thread', - archive: 'Archive this thread', - themeDark: 'Theme: dark', - themeLight: 'Theme: light', - themeSystem: 'Theme: system' - }, - - /** The import dialog: the sessions Claude Code kept for a project's folder. */ - imports: { - title: 'Import a session into {project}', - intro: 'Claude Code sessions started in this folder. One becomes a thread with its history, and the next turn continues it.', - loading: 'Looking for sessions', - empty: 'No Claude Code session for this folder.', - imported: 'Imported', - importing: 'Importing', - }, - - /** What a Claude thread's last request started with, from the context meter. */ - agentShape: { - open: 'What the agent sees', - title: 'What the agent sees', - intro: 'The system prompt and tools of the last request, as Claude Code recorded it.', - loading: 'Reading the transcript', - failed: 'Could not read the transcript: {error}', - noSession: 'No request yet: send a first message.', - noTranscript: 'The session transcript is not on this machine any more.', - unrecorded: 'This Claude Code version records no request details. Update it to 2.1.300 or later.', - system: 'System prompt', - tools: 'Tools', - block: 'Block {n}', - truncated: 'Cut here; the block holds {chars} characters.', - description: 'description {chars}', - omitted: '{n} more, counted in the total', - estimate: 'about {tokens} tokens', - estimateHint: 'Characters divided by four: an estimate, not the provider\'s count.', - chars: '{n} chars', - model: 'Model', - effort: 'Effort', - thinking: 'Thinking', - maxTokens: 'Max output', - betas: 'Betas', - version: 'Claude Code', - sent: 'Sent', - }, - - /** Images on their way to an agent, files given with an answer, and the full-screen viewer. */ - media: { - sourceTooLarge: '{name} weighs {size}: Boite shrinks images of {max} at most, so it was left out.', - viewer: 'Media viewer', - previous: 'Previous', - next: 'Next', - position: '{index} of {total}', - share: 'Share or save', - shareFailed: '{name} could not be shared.', - shareReady: '{name} is ready: tap Share again.', - answerFiles: 'Files given: {names}', - answerFileCount: '{count} files' - }, - /** The composer's slash menu: the agent's own commands over Boite's. */ - btw: { - fork: 'Fork', forking: 'Forking', - title: 'Side question', description: 'Ask a side question without interrupting the agent', - loading: 'Answering your side question', close: 'Close the side answer', - needsThread: 'Open a conversation before asking a side question.', - questionRequired: 'Type a question after /btw.', - textOnly: 'Side questions take text only. Remove attachments and preview references first.', - editing: 'Finish or cancel the message edit before asking a side question.', - offline: 'Reconnect this machine before asking a side question.' - }, - - slash: { - label: 'Commands', - agent: 'Agent', - app: 'Boite', - empty: 'No command matches', - model: 'Change the model', - effort: 'Change the reasoning effort', - mode: 'Change the permission mode' - }, - - activity: { - goal: 'Goal', - /** An agent's question it did not stop for (`boite ask`), docked above the composer like a goal. */ - question: 'Question', - questionOf: '{index} of {total}', - questionPrev: 'Previous question', - questionNext: 'Next question', - questionFold: 'Fold the question', - questionUnfold: 'Open the question', - /** A sub-agent's question or permission, docked in its parent: whose it is, and the way into its conversation. */ - subagentAsks: 'Asked by {title}', - subagentPermission: '{title} waits for your permission', - openSubagent: 'Open', - openSubagentHint: 'Open {title}', - loop: 'Loop', - tasks: 'Tasks', - taskCount: '{done}/{total} tasks', - goalDescription: 'Keep working toward an objective', - loopDescription: 'Repeat a task a set number of times or on a schedule', - goalUsage: 'Use /goal followed by an objective.', - /** `/goal2`, the Reviewed goals experiment: a fresh reviewer must pass the work. */ - goal2: 'Reviewed goal', - goal2Description: 'Work toward an objective until a fresh reviewer passes it', - goal2Usage: 'Use /goal2 followed by an objective.', - /** The goal's agent claimed completion and a reviewer conversation is checking it. */ - reviewing: 'Reviewing', - reviews: 'Reviews', - reviewRound: 'Review {round}', - reviewRoundOf: 'Review {round} of {total}', - reviewInProgress: 'Review {round} in progress', - openReview: 'Open the reviewer conversation', - pass: 'Pass', - partial: 'Partial', - fail: 'Fail', - missing: 'No verdict', - loopUsage: 'Use /loop 2 prompt for two iterations, or /loop 5m prompt for a schedule. Choose 1 to 1000 iterations.', - intervalError: 'Loop intervals must be between 1 second and 24 hours.', - active: 'Active', - paused: 'Paused', - complete: 'Complete', - /** A goal whose agent stopped on a blocker and waits for the user's answer. */ - blocked: 'Waiting for your answer', - blockedHint: 'Your next message resumes the goal.', - reply: 'Reply', - pause: 'Pause', - resume: 'Resume', - remove: 'Remove', - finish: 'Mark complete', - every: 'Every {interval}', - iterations: '{count} runs', - iteration: 'Iteration {count}', - iterationOf: 'Iteration {count} of {total}', - history: 'Iteration history', - noHistory: 'No completed iterations yet', - allTasksDone: 'All tasks completed', - waitingTasks: 'Waiting for next task', - stopped: 'Stopped', - running: 'Running', - done: 'Done', - error: 'Failed', - pending: 'Pending', - in_progress: 'In progress', - completed: 'Completed' - }, - - /** The composer's `@` menu: the project's files, the picked one written in as `@path`. */ - mention: { - label: 'Files', - empty: 'No file matches', - more: '{count} more, keep typing' - }, - - notify: { - done: 'Done', - failed: 'Failed', - openThread: 'Open thread', - needsYou: 'Needs your answer', - connected: 'Notifications are connected' - }, - - permissionMode: { - default: 'Ask', - acceptEdits: 'Edit freely', - bypassPermissions: 'Auto', - yolo: 'YOLO', - plan: 'Plan', - dontAsk: 'Auto-deny' - }, - - permissionModeLong: { - default: 'Reads freely, asks before editing a file or running a command', - acceptEdits: 'Edits files without asking, asks before running a command', - bypassPermissions: 'Runs everything without asking, anywhere on this computer', - yolo: 'Approves every tool request; skips Claude and Codex hooks', - plan: 'Plan only, no changes', - dontAsk: 'Deny anything that would need asking' - }, - - /** Codex writes inside the folder on its default mode: the chip says so instead of "Ask". */ - permissionModeCodex: { - default: 'This folder', - defaultLong: 'Edits and runs commands in this folder without asking, asks to go beyond it' - }, - - /** agy asks nothing in print mode: what its mode does not allow, it refuses by itself. */ - permissionModeAgy: { - default: 'agy settings', - defaultLong: 'Follows agy\'s own permission setting and refuses what it would ask about', - acceptEditsLong: 'Edits files without asking, refuses commands that need approval', - draft: "with agy's own permission settings" - }, - - thread: { - recoveryTitle: 'Prompt saved before restart', - recoveryBody: 'This prompt has not been sent to the agent. Resume it with its saved model and account, or remove it from the queue.', - recoveryTarget: 'Saved selection: {model} · {account}', - recoveryResume: 'Resume prompt', - recoveryDiscard: 'Remove from queue', - retryCapacityTitle: 'Model at capacity', - retryCapacityBody: 'Trying again in {time}, try {attempt} of {attempts}.', - retryCapacityCancel: 'Stop retrying', - retryLimitTitle: 'Usage limit reached', - retryLimitBody: 'Resumes at {time}, once the limit has reset, in {in}.', - retryLimitCancel: 'Do not resume', - retryContinueTitle: 'Stopped before its answer', - retryContinueBody: 'Resumes by itself in {time}, resume {attempt} of {attempts}.', - contextDetails: 'Context', - contextInput: 'Input', - contextCache: 'Cached input', - contextOutput: 'Output', - contextUsed: 'Used', - contextFree: 'Available', - contextNoBreakdown: 'This agent does not report a breakdown.', - contextNoReading: 'No measurement received from this agent yet.', - contextMeasured: 'Last measurement', - cacheTitle: 'Prompt cache', - cacheChipWarm: (left: string) => `Prompt cache warm, about ${left} left`, - cacheChipMaybe: (most: string) => `Prompt cache maybe still warm, ${most} at most`, - cacheChipCold: 'Prompt cache cold', - cacheCold: 'cold', - cacheLeft: (left: string) => `${left} left`, - cacheMaybe: (left: string) => `maybe · ${left}`, - cacheSwitched: 'Model or account changed.', - cacheLifetime: 'Lifetime', - cacheSourceReported: 'API', - cacheSourceDocumented: 'docs', - cacheRange: (lifetime: string, most: string) => `${lifetime} to ${most}`, - cacheReadLast: 'Read last turn', - minutes: (n: number) => `${n} min`, - hours: (n: number) => `${n} h`, - none: 'No thread open', - noneBody: 'Pick one on the left, or start a new one.', - trace: 'Trace', - /** The header's button: it shows or hides the panel, whatever surface is in it. */ - panel: 'Panel', - panelHint: 'Browser, files and tools beside this conversation', - terminalHint: "Terminal in this thread's folder", - cwd: 'Working directory', - model: 'Model', - defaultModel: 'Default model', - renamePlaceholder: 'Thread title', - /** The draft's own heading, the project's name in the dropdown after it. */ - start: 'Start a thread', - inWorktree: 'in a worktree', - inProject: 'in', - using: 'using', - onEffort: 'on {effort} effort', - draftMode: { - default: 'with approval requests', - acceptEdits: 'with edits allowed', - bypassPermissions: 'with all permissions', - yolo: 'with tool approvals bypassed', - plan: 'in plan mode', - dontAsk: 'with automatic denial of approval requests' - }, - changeProject: 'Change project', - /** The project dropdown's search field and what it says when no project matches. */ - searchProjects: 'Search projects', - noProjectMatch: 'No project matches "{query}"', - /** The header badge of a thread working in its own worktree; the title says where. */ - branchHint: 'Working in a worktree on this branch', - }, - - chat: { - accepted: 'Request accepted', - responseStarted: 'Agent activity received', - working: 'Working', - writing: 'Writing', - stopped: 'Stopped', - inputTokens: '{count} input tokens', - outputTokens: '{count} output tokens', - cacheTokens: '{read} read from the cache, {write} written to it', - /** The footer of a turn: `Working 12s`, then `Worked 4m 41s`. */ - workingFor: 'Working for {time}', - workedFor: 'Worked for {time}', - compacting: 'Compacting conversation', - compactionElapsed: 'Elapsed: {time}', - turnOwnTime: 'This reply alone: {time}', - compactedFor: 'Compacted in {time}', - compactionFailed: 'Compaction failed', - compactionStopped: 'Compaction stopped', - progress: { starting: 'Starting agent', thinking: 'Thinking', compacting: 'Compacting context', retrying: 'Retrying request', tool: 'Running tool', working: 'Receiving activity', waiting: 'Waiting for provider' }, - providerSignal: 'Provider signal {time} ago', - noActivity: 'No new activity for {time}', - /** The jump button once something arrived below the reader. */ - newMessage: '1 new message', - newMessages: '{count} new messages', - findPlaceholder: 'Find in thread', - findNone: 'No match', - findCount: '{at} of {total}', - findPrevious: 'Previous match (Shift+Enter)', - findNext: 'Next match (Enter)', - finishedAt: 'done {time}', - /** `{what}` is a list of `backgroundOne` and `backgroundMany` joined by `backgroundJoin`. */ - backgroundRunning: '{what} still running', - forkOrigin: 'Forked from', forkReturn: 'Send conclusions back', forkSummary: 'Conclusions for the original conversation', - forkReturned: 'Conclusions sent to the original conversation', forkSourceUnavailable: 'Original conversation unavailable', - forkNative: 'Native context preserved', forkSeeded: 'Visible history copied', - backgroundJoin: ', ', - backgroundOne: { shell: '{count} shell', agent: '{count} agent', monitor: '{count} monitor', workflow: '{count} workflow', other: '{count} task' }, - backgroundMany: { shell: '{count} shells', agent: '{count} agents', monitor: '{count} monitors', workflow: '{count} workflows', other: '{count} tasks' }, - backgroundStop: 'Stop the agent and what it runs in the background', - /** The chip on a tool card whose work went on in the background. */ - backgroundChip: 'in the background', - /** A call's line while it runs and once it ran; `{subject}` is a file name, a pattern, a host or a program. */ - toolLive: { command: 'Running {subject}', read: 'Reading {subject}', edit: 'Editing {subject}', write: 'Writing {subject}', search: 'Searching {subject}', fetch: 'Fetching {subject}', web: 'Searching the web for {subject}', agent: 'Delegating {subject}', other: 'Running {subject}' }, - toolDone: { command: 'Ran {subject}', read: 'Read {subject}', edit: 'Edited {subject}', write: 'Wrote {subject}', search: 'Searched {subject}', fetch: 'Fetched {subject}', web: 'Searched the web for {subject}', agent: 'Delegated {subject}', other: 'Used {subject}' }, - /** The line of a call whose input names nothing to show. */ - toolBare: { command: 'Ran a command', read: 'Read a file', edit: 'Edited a file', write: 'Wrote a file', search: 'Searched the code', fetch: 'Fetched a page', web: 'Searched the web', agent: 'Delegated a task', other: 'Used a tool' }, - /** `Running {subject}` for a command whose program has no name. */ - toolCommandWord: 'a command', - /** A folded run of calls: one clause per kind, joined into one sentence. */ - toolRunOne: { command: 'Ran {count} command', read: 'Read {count} file', edit: 'Changed {count} file', search: 'Searched the code once', fetch: 'Fetched {count} page', web: 'Searched the web once', agent: 'Delegated {count} task', other: 'Used {count} tool', attempt: 'Attempted {count} call' }, - toolRunMany: { command: 'Ran {count} commands', read: 'Read {count} files', edit: 'Changed {count} files', search: 'Searched the code {count} times', fetch: 'Fetched {count} pages', web: 'Searched the web {count} times', agent: 'Delegated {count} tasks', other: 'Used {count} tools', attempt: 'Attempted {count} calls' }, - toolRunJoin: ', ', - toolRunLast: ' and ', - toolRunShow: 'Show each call', - toolRunHide: 'Hide the calls', - toolIssueOne: '{count} unsuccessful call', - toolIssueMany: '{count} unsuccessful calls', - system: 'System', - toolInput: 'Input', - toolOutput: 'Output', - toolDocuments: 'Documents', - /** Under a text the core cut because its message was too heavy to send whole. */ - textOmitted: 'This message is too long to show in full. {count} characters are left out.', - /** The chip on a folded card: `1 diff`, `2 diffs`, `1 doc`, `3 docs`. */ - documentChip: { - diff: '{count} diff', - diffs: '{count} diffs', - doc: '{count} doc', - docs: '{count} docs' - }, - documentImage: 'What the tool produced', - /** An image the user sent with the prompt, when it came with no name. */ - imagePart: 'Image sent with the prompt', - /** A picture whose bytes could not be fetched from its machine. */ - imageUnavailable: 'This picture could not be loaded. Scroll back to it to retry.', - diffHidden: '{count} unchanged lines', - diffIgnoreWhitespace: 'Ignore whitespace changes', - diffSideBySide: 'Old and new side by side', - /** A diff whose every change was spaces, with whitespace ignored. */ - diffWhitespaceOnly: 'Only whitespace changed.', - diffShowAll: 'Show all {count} lines', - diffAdded: '+{count}', - diffRemoved: '-{count}', - noOutput: 'No output', - toolExitCode: 'Exit code {code}', - toolCommandFailed: 'No diagnostic was reported. See the full output below.', - /** Under an expanded tool input that opened cut to six lines. */ - showAll: 'Show all', - toolStatus: { - running: 'running', - done: 'done', - error: 'error', - denied: 'denied' - }, - thinking: 'Thinking', - thinkingShow: 'Show the reasoning', - thinkingHide: 'Hide the reasoning', - /** The divider a compaction draws: `{pre}` and `{post}` formatted token counts. */ - compaction: 'Context compacted, {pre} to {post} tokens', - compactionNoPost: 'Context compacted from {pre} tokens', - compactionUnknown: 'Context compacted', - compactionManual: 'by hand', - /** The accent line on the first message after a move: the agent was told where it works now. */ - moveExplained: 'Move explained to the agent', - moveShow: 'Show what the agent was told', - moveHide: 'Hide what the agent was told', - /** At the foot of a thread the user moved: the note rides on the next prompt, not sent yet. */ - movePending: 'Moved to {project} · the agent will be told with your next message', - movePendingShow: 'Show what the agent will be told', - movePendingHide: 'Hide what the agent will be told', - /** A line of the timeline: the agent moved its own thread with `boite thread move`. */ - movedByAgent: 'Moved by the agent to {project}', - /** A line of the timeline: Boite pinged the cached prompt while the thread waits on its agents. */ - keepalive: 'Prompt cache kept warm', - keepaliveMany: 'Prompt cache kept warm {count} times', - keepaliveRunning: 'Keeping the prompt cache warm', - /** Above the first prompt of a thread another thread's agent started with `boite thread new`. */ - startedByAgent: 'Started by the agent of {title} in {project}', - /** Above the first prompt of a thread another agent started: the prompt is that agent's, not the user's. */ - promptFromAgent: 'Sent by the agent of', - /** A line of the starting thread's timeline: its agent started another thread. */ - agentStartedThread: 'The agent started {title} in {project}', - openLinkedThread: 'Open this thread', - /** One of the user's own hooks ended the turn (`part.type === 'hook'`). */ - hookBlocked: 'A hook blocked this message', - hookStopped: 'A hook stopped the turn', - /** The line above with what the hook said: `{label}` one of the two, `{message}` its words. */ - hookSays: '{label}: {message}', - /** What the agent asks to do, one sentence per kind of tool (`lib/tool-summary.ts`). */ - permissionAsk: { - command: 'Run a command', - edit: 'Change {file}', - editFiles: 'Change files', - editFolder: 'Write files in {folder} from now on', - write: 'Create or replace {file}', - read: 'Read {file}', - search: 'Search the files for {subject}', - fetch: 'Open {subject}', - web: 'Search the web for {subject}', - agent: 'Start a helper agent', - other: 'Use its {tool} tool' - }, - permissionTechnical: 'Technical details', - turnFiles: 'Changed files', - showTurnDiff: 'Show diff', - hideTurnDiff: 'Hide diff', - /** Opens the Changes panel: everything the working tree changed since its last commit, all turns together. */ - allChanges: 'All changes in this folder', - expandFileFolders: 'Expand all folders', - collapseFileFolders: 'Collapse all folders', - fileCreated: 'New', - fileChanged: 'Changed', - fileDeleted: 'Deleted', - openFile: 'Open {name}', - revealFile: 'Show in folder', - revealFailed: 'The folder could not be opened: {reason}', - allow: 'Allow', - deny: 'Deny', - allowed: 'Allowed', - denied: 'Denied', - questionHeading: 'Asks you', - questionAsyncHeading: 'Question', - questionAsyncHint: 'The agent keeps working. Without an answer it goes on with a default.', - questionAnswer: 'Answer', - questionAnswered: 'Answered', - questionCancelled: 'No longer waiting for an answer', - questionSkip: 'Skip', - /** The composer is the free field of the question; the card points there. */ - questionReplying: 'Write your answer in the message box below, with any photos or files.', - questionWrite: 'Answer in writing', - /** The timeline's line for a question waiting in the dock above the composer. */ - questionDocked: 'Waiting for your answer above the message box', - questionOpen: 'Answer it', - streaming: 'writing', - jumpToLatest: 'Jump to latest', - /** The one line at the top of the timeline while an older page is being fetched. */ - loadingOlder: 'Loading earlier messages', - loadingNewer: 'Loading later messages', - /** The view a thread shows while its page downloads. */ - opening: 'Loading the conversation', - /** Received against the total, both already formatted: "1.2 MB / 3.4 MB". */ - openingProgress: '{received} / {total}', - /** What a picture left on the core says until it is fetched: "Loading 240 kB". */ - partLoading: 'Loading {size}', - outline: 'Messages in this conversation', - messageGroup: 'Browse {count} messages', - earlierMessages: 'Load earlier messages', - /** The rail's own chevrons: they move its window over the conversation without moving the timeline. */ - outlineEarlier: 'Show earlier messages in the rail', - outlineLater: 'Show later messages in the rail', - goToMessage: 'Go to message {number}: {text}', - copied: 'Copied', - /** The card an agent's proposed plan is read in, before it starts editing. */ - plan: 'Plan', - planDownload: 'Download .md', - planSave: 'Save to folder', - planSaveHint: "Write the plan as a markdown file at the root of this thread's folder", - planSaved: 'Saved as {name}', - copyMessage: 'Copy message', - editMessage: 'Edit and send again from here', - retry: 'Retry this answer', - fork: 'Fork from here', - forkHere: 'Fork into a new thread', - forkWorktree: 'Fork into a new worktree', - /** A prompt's exact time, under its bubble: `Sent Tue 28 Sep 2026, 10:31:05`. */ - sentAt: 'Sent {time}', - error: 'Error' - }, - - agentMessages: { - heading: 'Discussions', - open: 'Open the discussion', back: 'All discussions', openThread: 'Open this conversation', - empty: 'No discussion with another agent yet.', - /** The two halves of a discussion's count, for screen readers and hover. */ - sentReceived: '{sent} sent, {received} received', - /** A letter of this thread's own agent, in a preview. */ - mine: 'This agent', - /** A delegation prompt the user typed, on this thread's side. */ - you: 'You', - result: 'Result', - about: 'Works on', - replyTo: 'In reply to', - /** A burst touched more discussions than the timeline lists. */ - more: '+{count} more', - /** What a timeline row says above the other agent's name: which way the burst went. */ - sentOne: 'Sent to', sentMany: '{count} messages sent to', - receivedOne: 'Received from', receivedMany: '{count} messages received from', - exchanged: 'Exchange with', - /** A thread link in an answer that no connected machine or contact holds. */ - threadMissing: 'No connected machine has this conversation.', - /** Who started or delegated whom. Where a conversation runs is said by its project and machine, not a tag. */ - relation: { - starter: 'Started this thread', started: 'Started from here', parent: 'Parent', child: 'Subagent', resident: 'Looks after the project' - } - }, - coordination: { - options: 'Advanced settings', - heading: 'Communication between conversations', off: 'Off', on: 'On', - summaryOff: 'Other agents cannot find, read or contact this thread.', - summaryOn: 'Other agents here and on linked machines can find this thread, read its conversation and message it.', - ownerOnly: 'Only the owner can change coordination. This device can inspect the directory and exchanges.', - resources: 'Resources and responsibilities', resourcesPlaceholder: 'What this agent owns, can answer, or should avoid', - remote: 'Across projects and machines', remoteHint: 'Let agents in other projects and on linked machines find this thread, read its conversation and message it. Both threads must enable this.', - pause: 'Pause coordination', resume: 'Resume coordination', paused: 'Paused', - sends: '{used} of {limit} sends this hour', wakes: '{used} of {limit} wake turns this hour', receives: 'Recipients accept up to {limit} letters each hour.', - sent: '{used} sent this hour', woken: '{used} wake turns this hour', - directory: 'Authorized contacts', directoryEmpty: 'No opted-in thread in this project is available.', unavailable: 'Unavailable contacts', - details: 'Show exchange', - machineNotConnected: 'Connect to this machine in Settings to open its thread.', - status: { - queued: 'Queued in the outbox', received: 'Received by the recipient core', delivered: 'Submitted to the recipient agent', - uncertain: 'Submission outcome uncertain. Boite will not replay it.', expired: 'Expired', rejected: 'Rejected' - }, - bubbleStatus: { - queued: 'Waiting', received: 'Waiting', delivered: 'Sent', uncertain: 'Unconfirmed', expired: 'Expired', rejected: 'Failed' - } - }, - /** The persistent agent the owner entrusted a project to, where its project and threads show it. */ - resident: { - lookedAfterBy: 'Looked after by {name}' - }, - contextMode: { - light: 'Light', - neutral: 'Neutral', - custom: 'Custom', - rules: 'rules', - heading: '{label} context', - without: 'Started without: {parts}.', - instructions: 'instruction files', - guides: 'Boite and brain guides', - mcp: 'MCP servers', - hooks: 'hooks', - tools: 'Tools: {tools}.', - withRules: 'Its rules: {rules}' - }, - delegation: { - off: 'Off', - paused: 'Paused', - heading: 'Subagents', - nativeHeading: 'Started by the provider', - nativeNoResult: 'No result reported yet.', - processHeading: 'Started from a command', - processResult: 'Progress and results are in the command output.', - nativeStatus: { running: 'Working', done: 'Completed', error: 'Failed', stopped: 'Stopped', unknown: 'Status unknown' }, - parent: 'Parent thread', - activeOne: '1 active subagent', - activeMany: '{count} active subagents', - configure: 'Settings', - enabled: 'Let this conversation start subagents', - enabledHint: 'The agent can hand parts of a task to other agents. Each one works in its own conversation and reports back here.', - anyModel: 'Let the agent choose the model', - anyModelHint: 'For each task the agent picks a model and how hard it thinks, for example Codex for a code review. Off, subagents use this conversation\'s model or the models below.', - profiles: 'Suggested models', - profilesHint: 'The agent sees these by name. Add one to suggest a model and reasoning level for a kind of task.', - empty: 'No subagents yet. Ask the agent to split the work: each subagent appears here and its conversation opens with one click.', - addProfile: 'Add a model', - profileName: 'Model {count}', - profileLabel: 'Model for the subagent', - removeProfile: 'Remove this model', - pause: 'Pause subagents', - resume: 'Resume subagents', - backToTeam: 'Back to the subagents', - stopAll: 'Stop all', - send: 'Send to the subagent', - startedOne: 'Started 1 subagent', - startedMany: 'Started {count} subagents', - completed: '{done}/{total} completed', - failed: '{count} failed', - stopped: '{count} stopped', - elapsed: 'Elapsed time', - showTeam: 'View subagents', - stoppedStatus: 'Stopped', - failedStatus: 'Failed', - /** Its turn ended on its own background work, which wakes it: not finished. */ - waitingStatus: 'Waiting', - /** That work ended without waking it: its result never came. */ - interruptedStatus: 'Interrupted', - interrupted: '{count} interrupted', - resumeAgent: 'Resume', - resumeAgentHint: 'Ask this subagent to check how its background work ended and finish its task', - /** What Resume sends the interrupted subagent. */ - resumeAgentText: 'Your background work ({what}) ended without waking you: {reason}. Check how it ended, then finish your task.', - activeSection: 'Active', - finishedSection: 'Finished' - }, - - workflow: { - activeOne: '1 workflow', - activeMany: '{count} workflows', - status: { running: 'Running', paused: 'Paused', done: 'Done', failed: 'Failed', stopped: 'Stopped' }, - stepStatus: { waiting: 'Waiting', running: 'Running', done: 'Done', failed: 'Failed', skipped: 'Skipped', stopped: 'Stopped' }, - steps: '{done}/{total} steps', - phase: 'Phase {n}', - pause: 'Pause', - resume: 'Resume', - stop: 'Stop', - retry: 'Retry', - stopTitle: 'Stop "{name}"?', - stopBody: 'Running steps are stopped and the rest never starts. Finished steps keep their results.', - forEach: 'One per item of {path}', - after: 'After {steps}', - condition: 'Only if {path}', - task: 'Task', - result: 'Result', - output: 'Output', - items: 'Executions', - openThread: 'Open conversation', - back: 'Back to the graph', - close: 'Close details', - notStarted: 'Not started yet.', - skipped: 'Skipped: its condition did not hold.', - card: 'Workflow', - show: 'Show the workflow', - tokens: '{tokens} tokens' - }, - - composer: { - /** Above the box while a sent message is being edited. */ - editing: 'Editing a sent message: sending replaces it and everything after it', - editingQueued: 'Send or remove the queued messages before replacing a sent message.', - filesUnavailable: 'The messages were rewound, but this edit has no complete file backup. The code could not be restored.', - editingCancel: 'Stop editing (Esc)', - /** A question waits for a written answer: the box writes it, the row above names the question. */ - replying: 'Answering: {question}', - replyPlaceholder: 'Write your answer', - replySend: 'Send the answer', - replyIgnore: 'Write a message', - replyIgnoreHint: 'Leave the question waiting and write an ordinary message', - switchTitle: 'Switch a {tokens} token thread to {provider}?', - switchBody: '{provider} starts a new session and receives excerpts of the opening and of the most recent exchanges, about 20k tokens. The rest is lost to it. Compacting first changes nothing.', - switchConfirm: 'Switch', - switchCancel: 'Stay on {provider}', - cacheTitle: 'Rebuild the cache of a {tokens} token thread?', - cacheBody: 'This change may make the provider process the whole conversation again uncached on the next turn, using more quota. Changing a model or reasoning effort can affect the cache, as can enabling Claude Fast mode for the first time.', - cacheConfirm: 'Change', - cacheCancel: 'Keep as is', - options: 'Message options', - placeholder: 'Message {provider} in {project}', - placeholderNoProject: 'Message the agent', - /** A new conversation, before anything is sent. */ - placeholderNew: 'What do you want to do?', - placeholderOffline: '{machine} is offline: messages wait here and go out when it reconnects', - send: 'Send', - stop: 'Stop', - queued: 'Sent after the next tool, or when the turn ends', - /** A refused send held the pending messages until the user sends again. */ - queuedPaused: 'Held after a refused send', - sendNow: 'Send now', - queueWaitForEnd: 'Goals and loops wait for the current turn to finish. Your command remains queued.', - queueTurnNotReady: 'The running turn has not reached this client yet. Your message remains queued.', - queueNotAccepted: 'This turn did not accept the queued input. It remains queued and will be sent when the turn ends.', - sendNowHint: 'Sends pending messages to the running agent without stopping it. Enter in the empty composer does the same.', - retryQueuedHint: 'Sends all pending messages together again. Enter in the empty composer does the same.', - /** The outbox: messages written while the machine was away, kept on this device. */ - outboxKept: 'Kept on this device and sent in order, once each', - outboxWaiting: 'Waiting for {machine}', - outboxNext: 'Goes out when the thread is free', - outboxSending: 'Sending…', - outboxFailed: 'Not sent: {reason}', - outboxRetry: 'Send again', - outboxSettings: 'The model and effort it was written with could not be set again.', - removeQueued: 'Remove this pending message', - picker: 'Provider and model', - models: 'Models', - searchModels: 'Search models', - noModels: 'No model matches', - noModelsAvailable: 'The agent has not listed any models. Refresh to retry.', - showAllModels: 'Show all {count} models', - legacyModels: 'Legacy models', - refreshModels: 'Refresh models', - probing: "Reading the agent's models", - notInstalled: 'Not installed on this machine', - installInSettings: 'Install in Settings', - effortTitle: 'Reasoning effort', - speed: 'Response speed', - /** The reasoning chip of a model whose agent keeps its own level until one is picked. */ - effortDefault: 'Default', - effortDefaultHint: 'Let the agent pick its own level for this model', - standardSpeed: 'Standard speed', - favorites: 'Favorites', - favorite: 'Add to favorites', - unfavorite: 'Remove from favorites', - favoritesEmpty: 'Star a model to find it here.', - favoriteUnavailable: 'This favorite is no longer offered by the account. Choose another model.', - compact: 'Compact context', - compactBusy: 'Wait for the current turn to finish', - compactUnavailable: 'This agent has not advertised context compaction', - compactNoSession: 'Send a message before compacting', - newBadge: 'new', - lockedHint: 'A thread keeps its provider and account', - mode: 'Permissions', - model: 'Model', - unavailable: 'not installed', - noAccount: 'no account', - noProvider: 'No AI connected', - /** The draft's worktree switch, off and on. */ - worktree: 'Worktree', - worktreeOff: 'Start in a git worktree on its own branch', - worktreeOn: 'Starts in a git worktree on its own branch', - /** Images sent with the prompt: the strip above the box and what refuses one. */ - attach: 'Attach files', - editQueued: 'Edit this pending message, or press Up in the empty composer', - attachReadError: '{name} could not be read.', - attachRemove: 'Remove {name}', - attachAlt: 'Attached file', - imageReference: '[Image {number}]', - imagePreview: 'Preview {image}', - imagePreviewClose: 'Close image preview', - attachPending: 'Reload to recover', - /** The last tile of a full strip: how many more it hides, and folding them back. */ - attachMore: 'Show {count} more', - attachLess: 'Show fewer', - attachUnnamed: 'the attachment', - attachFormat: '{name} is {type}, and an image must be one of {formats}.', - attachTooLarge: '{name} is too big: a file may weigh {max} at most.', - attachTooMany: 'A turn carries at most {max} files, so {name} was left out.', - attachTotalTooLarge: 'The files of one turn weigh {max} at most together, so {name} was left out.', - attachNoImages: '{provider} takes no images: send the prompt without them.' - }, - - /** The panel on the right of the chat: its tab strip, its launcher, its surfaces. */ - rightPanel: { - resize: 'Resize the panel, double-click to reset', - maximize: 'Maximize the panel', - restore: 'Restore the panel', - newSurface: 'Open a surface', - scrollLeft: 'Scroll the tabs left', - scrollRight: 'Scroll the tabs right', - closeTab: 'Close {title}', - closeOthers: 'Close others', - closeToRight: 'Close to the right', - closeAll: 'Close all', - trace: 'Trace', - browser: 'Browser', - changes: 'Changes', - files: 'Files', - file: 'File', - tasks: 'Tasks', - device: 'Device', - ownerOnly: "Only in the owner's app", - launcher: 'Open a surface in this panel', - untitled: 'Browser' - }, - - /** The changes surface: `git status` of the thread's working directory. */ - changes: { - clean: 'Working tree clean', - noBranch: 'No branch', - ahead: '{count} ahead', - behind: '{count} behind', - count: '{count} changed', - oneCount: '1 changed', - staged: 'Staged', - stagedShort: 'S', - binary: 'Binary file, nothing to show line by line', - truncated: 'This file was cut short: the diff below is not the whole file.', - pick: 'Pick a file to see its diff', - loading: 'Reading the working tree', - renamedFrom: 'Renamed from {path}', - /** A narrow panel shows the list or one diff: the way back, and the steps between files. */ - backToList: 'All changes', - previousFile: 'Previous file', - nextFile: 'Next file', - position: '{index} of {count}', - status: { - added: 'Added', - modified: 'Modified', - deleted: 'Deleted', - renamed: 'Renamed', - copied: 'Copied', - untracked: 'Untracked', - conflict: 'Conflict' - } - }, - - /** The tasks surface: the thread's activity, the agent's tasks, the project's todos. */ - tasks: { - goalSection: 'Goal and loop', - noActivity: 'No goal and no loop on this thread', - nextRun: 'Next run {time}', - iterations: '{count} iterations', - agentSection: 'Agent tasks', - noTasks: 'The agent has reported no task list', - todoSection: 'Project todos', - noTodos: 'No todo on this project', - addPlaceholder: 'Add a todo', - add: 'Add', - // The chip on a claimed card. Short enough to leave the card's own text - // readable on a narrow panel; the whole sentence is its tooltip. - awaiting: 'Awaiting you', - awaitingHint: 'This card waits on your confirmation', - markOpen: 'Move back to open', - markClaimed: 'Mark it waiting on you', - markDone: 'Confirm it is done', - removeTodo: 'Remove this todo', - collapse: 'Fold this section', - expand: 'Unfold this section', - /** The pull requests the agent asked Boite to watch (`boite pr watch`). */ - watchSection: 'Pull request watches', - noWatches: 'No pull request watched', - watchHint: 'Boite reads the pull request on GitHub about once a minute and wakes the agent when a check fails, the checks pass, a review or comment lands or the branch conflicts', - watchNotRead: 'Not read yet', - watchNoChecks: 'No checks', - watchChecks: '{total} checks', - watchRunning: '{count} running', - watchFailed: '{count} failed', - watchConflict: 'Conflicts with {base}', - watchRateLimited: 'GitHub rate limit, next read {time}', - watchUnreachable: 'GitHub out of reach, next read {time}', - stopWatch: 'Stop watching', - stopWatchOf: 'Stop watching #{number}' - }, - - /** The file tree and the file viewer, the two surfaces reading the working directory. */ - files: { - tree: 'Files of the working directory', - noRoot: 'No working directory', - filter: 'Filter', - filterHint: 'Narrow the rows already loaded', - reading: 'Reading this directory', - empty: 'This directory is empty', - noMatch: 'No loaded row matches', - failed: 'This directory could not be read. {reason}', - capped: 'Only the first {count} entries of this directory are listed.', - // The viewer and the editor, one file per tab. - readFailed: 'This file could not be read. {reason}', - writeFailed: 'This file could not be saved. {reason}', - readingFile: 'Reading this file', - noFile: 'This tab carries no path', - truncated: 'This file was cut short at its size limit, so what is below is not the whole file and cannot be saved.', - readOnly: "Saving a file is the owner's, so this one opens read only.", - saving: 'Saving', - unsaved: 'Unsaved changes', - discardOne: 'Close {name} without saving?', - discardMany: 'Close {count} files without saving?', - discardBody: 'What was typed since the last save is lost.', - discardConfirm: 'Close without saving', - editorLabel: 'File contents', - zoomIn: 'Zoom in', - zoomOut: 'Zoom out', - fit: 'Fit', - actual: '1:1', - natural: '{width} x {height}', - picture: 'The picture of this file', - download: 'Download this file', - binary: 'Nothing here reads as text.' - }, +/** + * Every user-facing string of the UI. No literal UI text lives anywhere else. + * + * One file per top-level key in `./strings/en/`, so two changes to different + * parts of the UI never edit the same file. A new key goes in the file of its + * part; a new part is a file here and in every translation, and a line in each + * index. + */ +import { strings as subscriptionProxy } from './strings/en/subscriptionProxy'; +import { strings as settingRequest } from './strings/en/settingRequest'; +import { strings as agentSettingRequest } from './strings/en/agentSettingRequest'; +import { strings as remoteBrowser } from './strings/en/remoteBrowser'; +import { strings as agentBrowser } from './strings/en/agentBrowser'; +import { strings as devicePanel } from './strings/en/devicePanel'; +import { strings as agents } from './strings/en/agents'; +import { strings as previewComments } from './strings/en/previewComments'; +import { strings as artifacts } from './strings/en/artifacts'; +import { strings as brain } from './strings/en/brain'; +import { strings as hooks } from './strings/en/hooks'; +import { strings as diagnostics } from './strings/en/diagnostics'; +import { strings as telemetry } from './strings/en/telemetry'; +import { strings as speech } from './strings/en/speech'; +import { strings as phone } from './strings/en/phone'; +import { strings as mobile } from './strings/en/mobile'; +import { strings as machines } from './strings/en/machines'; +import { strings as group } from './strings/en/group'; +import { strings as app } from './strings/en/app'; +import { strings as titlebar } from './strings/en/titlebar'; +import { strings as serverUpdate } from './strings/en/serverUpdate'; +import { strings as desktopUpdate } from './strings/en/desktopUpdate'; +import { strings as appUpdate } from './strings/en/appUpdate'; +import { strings as updateAll } from './strings/en/updateAll'; +import { strings as connection } from './strings/en/connection'; +import { strings as threadStatus } from './strings/en/threadStatus'; +import { strings as drafts } from './strings/en/drafts'; +import { strings as firstRun } from './strings/en/firstRun'; +import { strings as connect } from './strings/en/connect'; +import { strings as drop } from './strings/en/drop'; +import { strings as sidebar } from './strings/en/sidebar'; +import { strings as threadMove } from './strings/en/threadMove'; +import { strings as palette } from './strings/en/palette'; +import { strings as imports } from './strings/en/imports'; +import { strings as agentShape } from './strings/en/agentShape'; +import { strings as media } from './strings/en/media'; +import { strings as btw } from './strings/en/btw'; +import { strings as slash } from './strings/en/slash'; +import { strings as activity } from './strings/en/activity'; +import { strings as mention } from './strings/en/mention'; +import { strings as notify } from './strings/en/notify'; +import { strings as permissionMode } from './strings/en/permissionMode'; +import { strings as permissionModeLong } from './strings/en/permissionModeLong'; +import { strings as permissionModeCodex } from './strings/en/permissionModeCodex'; +import { strings as permissionModeAgy } from './strings/en/permissionModeAgy'; +import { strings as thread } from './strings/en/thread'; +import { strings as chat } from './strings/en/chat'; +import { strings as agentMessages } from './strings/en/agentMessages'; +import { strings as coordination } from './strings/en/coordination'; +import { strings as resident } from './strings/en/resident'; +import { strings as contextMode } from './strings/en/contextMode'; +import { strings as delegation } from './strings/en/delegation'; +import { strings as workflow } from './strings/en/workflow'; +import { strings as composer } from './strings/en/composer'; +import { strings as rightPanel } from './strings/en/rightPanel'; +import { strings as changes } from './strings/en/changes'; +import { strings as tasks } from './strings/en/tasks'; +import { strings as files } from './strings/en/files'; +import { strings as browserTools } from './strings/en/browserTools'; +import { strings as browser } from './strings/en/browser'; +import { strings as browserProfiles } from './strings/en/browserProfiles'; +import { strings as terminal } from './strings/en/terminal'; +import { strings as trace } from './strings/en/trace'; +import { strings as protection } from './strings/en/protection'; +import { strings as taskManager } from './strings/en/taskManager'; +import { strings as resources } from './strings/en/resources'; +import { strings as install } from './strings/en/install'; +import { strings as accounts } from './strings/en/accounts'; +import { strings as usage } from './strings/en/usage'; +import { strings as controls } from './strings/en/controls'; +import { strings as settings } from './strings/en/settings'; +import { strings as keyboard } from './strings/en/keyboard'; +import { strings as experiments } from './strings/en/experiments'; +import { strings as features } from './strings/en/features'; +import { strings as quotas } from './strings/en/quotas'; +import { strings as harnessUpdates } from './strings/en/harnessUpdates'; +import { strings as providerSettings } from './strings/en/providerSettings'; +import { strings as plugins } from './strings/en/plugins'; +import { strings as time } from './strings/en/time'; +import { strings as errors } from './strings/en/errors'; +import { strings as units } from './strings/en/units'; +import { strings as effortLevels } from './strings/en/effortLevels'; +import { strings as parts } from './strings/en/parts'; +import { strings as common } from './strings/en/common'; +import { strings as onboarding } from './strings/en/onboarding'; - browserTools: { - title: 'Browser testing tools', desktop: 'Desktop', laptop: 'Laptop', portrait: 'Portrait', landscape: 'Landscape', - system: 'Follow system appearance', light: 'Light page', dark: 'Dark page', diagnostics: 'Page diagnostics and actions', - startRecording: 'Record this browser tab', stopRecording: 'Stop recording', reviewRecording: 'Recorded video', - clear: 'Clear', pageEvents: 'Page events', actions: 'Actions', noEvents: 'No page events captured.', noActions: 'No actions recorded yet.', - omitted: '{count} older or oversized events omitted.', recorded: '{seconds} seconds · {fps} fps · {mb} MB · silent {format}', - recordingError: 'Recording stopped after an error.', recordingLimit: 'Recording stopped by itself at the {mb} MB size limit. The video plays up to that point.', - frameRate: 'Record at {rate} frames per second', codec: 'Encode as {codec}', codecUnavailable: 'This computer cannot encode it', - unplayable: 'This view cannot play {codec} video. Download it to watch it in another player.', - download: 'Download video', discard: 'Discard video', - }, - browser: { - resetViewport: 'Fit page to window', - enlarge: 'Enlarge browser', - restore: 'Restore panel', - detach: 'Float inside the app', - dock: 'Return to panel', - move: 'Move the floating panel', - loading: 'Loading page', - failed: 'The page could not be opened. {reason}', - invalidUrl: 'Enter a valid HTTP or HTTPS address, or a search query.', - resetZoom: 'Reset page zoom', - back: 'Back', - forward: 'Forward', - reload: 'Reload', - urlPlaceholder: 'Search or enter URL', - openExternal: 'Open in the system browser', - slotEmpty: 'The page opens here once the shell provides the webview', - slotLinux: 'The built-in browser is not available in the Linux app yet. Open the page in your own browser.' - }, - - browserProfiles: { - /** The card in Settings, General: agent control and the profiles below it. */ - card: 'Built-in browser', - heading: 'Browser profiles', - hint: 'Each profile keeps its own cookies, storage and sign-ins in the built-in browser, and keeps them after a restart. A private tab keeps nothing once the last private tab closes.', - default: 'Default', - private: 'Private', - profile: 'Browser profile: {name}', - newTabIn: 'New tab in {name}', - newPrivateTab: 'New private tab', - manage: 'Manage browser profiles', - isDefault: 'Opens new tabs', - makeDefault: 'Open new tabs here', - renameLabel: 'New name for {name}', - addLabel: 'New profile name', - addPlaceholder: 'Work, Personal…', - add: 'Add profile', - invalidName: 'Choose a name of 1 to 40 characters that no other profile uses, other than Default or Private.', - tooMany: 'At most {count} browser profiles.', - deleteTitle: 'Delete the profile {name}?', - deleteBody: 'Its open tabs close, and its cookies, storage and sign-ins are erased from this computer. This cannot be undone.', - deleteFailed: 'The profile was removed, but its data could not be erased: {reason}', - copySignIns: 'Copy sign-ins to agents', - copySignInsHint: 'Send the cookies of {name} to the agent browser of a machine, into the same profile', - copied: '{count} cookies of {name} copied to {machine}.', - copiedNone: '{name} holds no cookie to copy.', - copyFailed: 'The sign-ins could not be copied: {reason}' - }, - - terminal: { - title: 'Terminal', - hide: 'Hide the terminal', - close: 'Close the shell', - resize: 'Resize the terminal', - /** A tab, `Terminal 2`, named after its first shell as T3 Code does. */ - tab: 'Terminal {n}', - tabs: 'Terminals', - new: 'New terminal', - splitRight: 'Split right', - splitDown: 'Split down', - closeTab: 'Close the tab', - resizePanes: 'Resize the split', - find: 'Find in the terminal', - history: 'Recent commands', - historySearch: 'Search the commands', - historyEmpty: 'No command in this shell\'s history yet', - historyNone: 'No command matches', - historyOld: 'This machine runs an older Boite that cannot list them', - historyLoading: 'Reading the history', - /** Under the list: what the keys do with the command picked. */ - historyHint: 'Enter runs it, Shift+Enter puts it at the prompt', - running: 'A command is running', - /** `exit 1`: the status of the last command, which failed. */ - failed: 'The last command failed (exit {code})' - }, - - trace: { - exact: 'All child processes tracked', - limited: 'Limited process tracking', - finished: 'Finished', - parent: 'Parent PID', - cpuTime: 'CPU time', - currentMemory: 'Memory now', - active: 'active', - recorded: 'recorded', - exe: 'Executable', - pid: 'PID', - duration: 'Duration', - cpu: 'CPU', - memory: 'Peak memory', - io: 'I/O', - exit: 'Exit', - live: 'live', - empty: 'This thread has launched no process yet.', - }, - - protection: { - quiet: 'Peace and quiet', - limits: 'Resource limits', - tasks: 'Task manager', - windows: 'Focus, audio and resource limits apply on the Windows host.', - }, - taskManager: { - title: 'Task manager', scope: 'Live processes on this machine, grouped by agent.', - agents: 'Agents', live: 'Updated every 2 seconds', paused: 'Updates paused', disconnected: 'Machine disconnected', - loading: 'Reading agent usage', unsupported: 'Update Boite on this machine to see agent usage.', - loadFailed: 'The task manager could not load. Try again.', - search: 'Find an agent', noMatches: 'No matching agents.', sortBy: 'Sort by usage', - sort: { memory: 'Most memory', cpu: 'Most CPU', disk: 'Most disk activity', network: 'Most network activity' }, - disk: 'Disk', network: 'Network', read: 'Read', write: 'Write', download: 'Download', upload: 'Upload', - observed: 'Observed', unavailableHint: 'No measurement is available yet, or this counter is unsupported. A missing value does not mean zero usage.', - partialHint: 'This total includes only agents with available measurements.', - diskHint: 'Storage reads and writes observed while this view is open. Brief processes can be missed. This is activity, not folder size.', - tcpHint: 'TCP downloads and uploads observed while this view is open. Brief connections and UDP traffic can be missed.', - stop: 'Stop processes', stopConfirm: 'Stop every process in this conversation?', stopping: 'Stopping processes', - }, - resources: { - memory: 'Memory', - resolved: (mb: number) => `${Math.round(mb)} MB`, - auto: (mb: number) => `Auto: ${Math.round(mb)} MB`, - agents: 'Agents / budget', - available: 'Available / reserve', - unknown: 'Not available', - state: 'Protection status', - states: { ok: 'Enough memory', critical: 'Memory limit crossed' }, - killTitle: 'Process stopped to free memory', - killCount: (count: number) => `${count} processes stopped to free memory`, - stoppedProcesses: (count: number) => `Show ${count} stopped processes`, - memorySettings: 'Memory settings', - protectionOff: 'Memory protection is off', - killReason: { - 'thread-quota': (limit: string) => `This conversation exceeded its ${limit} memory limit.`, - budget: (limit: string) => `The agents exceeded their ${limit} shared memory budget.`, - machine: (limit: string) => `Available memory fell below the ${limit} reserve.`, - }, - threadCap: 'This conversation reached its memory limit. Boite refused more memory.', - budget: 'The agents reached their shared memory budget. Boite refused more memory.', - throttled: (limit: string) => `This conversation's processes are slowed by their ${limit} memory limit. They stall instead of failing.`, - empty: 'Nothing is running right now.', - processes: 'processes', - killTree: 'Kill tree', - killConfirm: 'Kill every process of this thread?', - killConfirmYes: 'Kill', - }, - - /** A provider whose files Boite downloads itself. */ - install: { - action: 'Install', - update: 'Update', - repair: 'Reinstall', - remove: 'Uninstall', - removeTitle: 'Uninstall {provider}?', - removeBody: 'The files Boite downloaded go with it. Your accounts stay, and it can be installed again later.', - removeConfirm: 'Uninstall', - removeCancel: 'Keep', - absent: 'Not installed · {size}', - downloading: 'Downloading · {percent}', - verifying: 'Checking the download', - extracting: 'Unpacking', - installed: 'Installed by Boite', - updateAvailable: 'Version {installed} · {available} is available', - progress: 'Download progress' - }, - - accounts: { - heading: 'Providers', - login: 'Sign in', - loginStarting: 'Starting the sign-in', - loginOpen: 'Open the sign-in page', - loginHint: 'Finish in your browser. This row turns ready on its own.', - loginInputPlaceholder: 'Paste the code the page gives you, if it gives one', - loginRedirectPlaceholder: 'On another device? Paste the localhost address the page ends on', - loginSend: 'Send', - terminalHint: 'Answer its questions in the terminal, then close it once you are signed in.', - terminalDone: 'Close the terminal', - remove: 'Remove', - removeTitle: 'Remove {account}?', - removeBody: 'This signs the account out of Boite and deletes its files.', - removeDefaultBody: 'Boite stops using this login. It stays signed in on your computer.', - removeThread: '1 conversation still uses this account. Its next message moves it to another signed-in account of the same agent, which reloads its whole context.', - removeThreads: '{count} conversations still use this account. The next message in each moves it to another signed-in account of the same agent, which reloads its whole context.', - removed: 'Account removed', - removedHint: 'The account of this conversation was removed. Choose a model on another account to continue.', - status: { - unknown: 'unknown', - ok: 'ok', - unauthenticated: 'not signed in', - error: 'error' - } - }, - - usage: { - heading: 'Usage', - note: 'Costs are the tokens at each model\'s public API rates, from models.dev. A model with no listed rate uses the cost its agent reported. On a subscription this is not an invoice.', - thread: 'Thread', - input: 'Input', - output: 'Output', - cost: 'API-rate estimate', - total: 'Total', - empty: 'No token spent yet.', - intro: 'Spent through Boite. Tokens include cache reads and writes.', - range: 'Range', - days: '{days} days', - metric: 'Measure', - provider: 'Provider', - allProviders: 'All providers', - machine: 'Machine', - allMachines: 'All machines', - machinesOffline: 'Not counted, not connected: {machines}', - offline: 'Not connected', - unknownProvider: 'Unknown provider', - filterUnsupported: 'This machine did not filter usage by provider. Update Boite on it and retry.', - noActivity: 'No turns', - noUsage: 'Not reported', - coverage: 'Tokens {reported}/{turns} turns · Cost {priced}/{turns}', - partialCost: '{count} turns have no price and are left out of the estimate.', - rates: 'Public API rates of {model}, per million tokens: input {input}, output {output}, cache read {cacheRead}, cache write {cacheWrite}.', - cacheSplit: 'Cache: {read} read, {write} written.', - cacheSplitCost: 'Cache: {read} read ({readCost}), {write} written ({writeCost}).', - reportedCost: 'No public rate matches this model, so the cost is the one its agent reported.', - noRates: 'No public rate matches this model and its agent reported no cost.', - olderCore: 'The machine that ran these turns has a Boite older than public API rates, so only a cost its agent reported shows here. Update it in Settings, Machines and updates to price them.', - noTokens: 'The agent reported no tokens for these turns, so they have no cost.', - priceOf: 'the price of {model}', - metrics: { tokens: 'Tokens', cost: 'API estimate', turns: 'Turns' }, - totalOf: { - tokens: 'Tokens, last {days} days', - cost: 'API-rate estimate, last {days} days', - turns: 'Turns, last {days} days' - }, - summary: '{turns} turns · {tokens} tokens · {cost} at API rates', - summaryNoPrice: '{turns} turns · {tokens} tokens · No price', - summaryMissing: '{turns} turns · Usage not reported', - overview: 'Overview', - chart: 'Per day', - chartLabel: '{metric} per day and provider. Use the arrow keys to read each day.', - share: '{percent} of the total', - breakdown: 'Breakdown', - byModel: 'By model', - byDay: 'By day', - model: 'Model', - day: 'Day', - turns: 'Turns', - tokens: 'Tokens', - cache: 'Cache', - noPrice: 'No price', - defaultModel: 'Default model', - other: 'Other', - threads: 'Top threads', - noRankedThreads: 'No threads to rank for this measure.', - threadTurns: '{turns} turns', - untitled: 'Untitled thread', - archived: 'Archived', - emptyRange: 'No turn finished in the last {days} days.', - unpriced: 'No price for {providers}: no public rate and no reported cost.', - unreported: '{count} turns reported no usage. They count as turns only.', - limits: 'Limits', - refresh: 'Refresh usage', - loading: 'Reading usage', - failed: 'Could not read usage: {error}', - }, - - /** The buttons a device shows or puts away, on the Appearance page and in a button's right click. */ - controls: { - heading: 'Buttons', - headingHint: 'A hidden button stays reachable from the command palette and the keyboard shortcuts.', - preset: 'Preset', - everyday: 'Essentials', - developer: 'Everything', - header: 'Conversation header', - composer: 'Message bar', - sidebar: 'Sidebar', - panel: 'Side panel menu', - project: 'Project name', - branch: 'Branch', - context: 'Context gauge', - hide: 'Hide this button', - customize: 'Choose the buttons', - allHidden: 'Every card is hidden on this device.' - }, - - settings: { - heading: 'Settings', - modelDefaults: 'Default models', - modelDefaultsHint: 'For new threads. Saved on this device.', - modelDefaultUnavailable: '{provider} does not offer the default model {model} on this account. Choose an available model in the picker or change the default in Settings.', - tabs: { - home: 'Home', - general: 'General', - machines: 'Machines and updates', - advanced: 'Advanced', - appearance: 'Appearance', - keyboard: 'Keyboard', - accounts: 'Providers', - plugins: 'Plugins', - usage: 'Usage', - resources: 'Protection', - experiments: 'Experiments', - diagnostics: 'Diagnostics' - }, - back: 'Back to threads', - search: 'Search settings', - searchEmpty: 'No setting matches', - homeGroups: ['App', 'Agents', 'Usage and limits', 'System'], - providersReady: '{count} connected', - providersOne: '1 connected', - machinesCount: '{count} machines', - devicesCount: '{count} paired devices', - devicesOne: '1 paired device', - connectProvider: 'Connect a provider', - conversations: 'Conversations', - groupWorkingThreads: 'Group working threads', - groupWorkingThreadsHint: 'Fold working and background threads in Projects and Recent. Pins, drafts, errors and threads that need you stay visible. This device only.', - groupOtherProjects: 'Group idle projects', - groupOtherProjectsHint: 'Fold projects without a visible conversation or draft into Idle projects in the Projects view. Turn off to keep all projects in the list. This device only.', - app: 'App', - tourReplay: 'Replay the tour', - execution: 'Agent execution', - warmProcessMinutesHint: 'How long an agent stays loaded after a turn. 0 stops it right away.', - autoCompact: 'Automatic compaction', - autoCompactHint: 'Boite asks the agent to compact between two turns, never during one. Each compaction counts as usage.', - autoCompactWhen: 'When', - autoCompactCondition: 'Condition', - autoCompactRecommended: 'Recommended', - autoCompactCacheExpiry: 'Before the prompt cache expires', - autoCompactCacheExpiryHint: 'One minute before the provider drops the cached conversation, if nothing happened since. Read at the cached price, and your next message starts from the short version.', - autoCompactBackground: 'While the agent waits on background work', - autoCompactBackgroundHint: 'After an answer that left a command or a monitor running. The compaction fills the wait.', - autoCompactTurnEnd: 'As soon as the agent has finished', - autoCompactTurnEndHint: 'Two seconds after an answer that left nothing running. A message sent right after waits for the compaction.', - autoCompactThreshold: 'Only above a size', - autoCompactThresholdHint: 'Smaller conversations are left alone, and so is a model that never gets that far.', - autoCompactThresholdOff: 'Off: every conversation is compacted at the moments above, whatever its size.', - autoCompactUnit: 'tokens', - projects: 'Projects', - /** The language the UI speaks, on this device. `system` follows the machine. */ - language: 'Language', - languageSystem: 'System', - languageNames: { en: 'English', fr: 'Français' }, - theme: 'Theme', - display: 'Display', - accent: 'Accent colour', - accentNames: ['Blue', 'Violet', 'Rose', 'Orange', 'Amber', 'Green', 'Teal'], - colors: { - title: 'Colours', - hint: 'Light and dark keep separate colours. Saved on this device.', - palette: 'Colour palette', - palettes: { default: 'Original', oled: 'OLED', catppuccin: 'Catppuccin', 'tokyo-night': 'Tokyo Night', nord: 'Nord', gruvbox: 'Gruvbox', amethyst: 'Amethyst' }, - customize: 'Customize colours', - roles: { background: 'Background', frame: 'Sidebar and frame', surface: 'Panels and messages', foreground: 'Text', accent: 'Accent' }, - edit: 'Choose a colour to edit', - reset: 'Reset', - undo: 'Undo', - field: '{color}: saturation and brightness', - coordinates: 'Saturation {saturation}%, brightness {brightness}%', - hue: 'Hue', - hex: 'Hex', - invalidHex: 'Use a hex colour such as #7AA2F7.', - contrast: 'Text contrast', - contrastHint: 'The lowest text contrast across the background, frame and panels. The target is 4.5:1.', - contrastLow: 'Low text contrast {ratio}:1', - improveContrast: 'Improve contrast' - }, - themeSystem: 'System', - themeDark: 'Dark', - themeLight: 'Light', - themeGrain: 'Grain', - reading: 'Reading', - font: 'Text font', - fontSystem: 'System', - /** Drawn in every face of the picker: mixed case, digits, and the glyphs that get confused (Il1, O0, rn). */ - fontSample: 'Rerun the 12 failing tests in Il1/O0/rn.ts', - monoFont: 'Code font', - monoSystem: 'System', - zoom: 'Interface size', - zoomHint: 'Ctrl+= and Ctrl+- anywhere in the app, Ctrl+0 back to 100%.', - zoomOut: 'Smaller', - zoomIn: 'Larger', - zoomReset: 'Back to 100%', - zoomFailed: 'The zoom did not change: {error}', - experiments: { - intro: 'These are unfinished and can change or disappear.' - }, - cacheKeepalive: 'Keep the prompt cache warm', - cacheKeepaliveHint: 'While a conversation waits on its agents or workflows, Boite sends a one-word turn shortly before the provider drops its cached prompt, so their result is read at the cached price. Each ping counts as usage. A ping that misses the cache ends the pings.', - cacheKeepaliveHours: 'For at most', - cacheKeepaliveHoursHint: 'Counted from the end of the conversation\'s last turn. Pings also stop when the agents finish, when you write or stop, and after as many pings as one cache miss would cost.', - cacheKeepaliveUnit: 'hours', - material: 'Window material', - workspace: 'Workspace', - startIn: 'The app opens on', - startDrafts: 'Drafts', - startProject: 'The last project', - panelStart: 'The side panel opens on', - panelLauncher: 'Its menu', - panelFiles: 'Files', - panelChanges: 'Changes', - materialAcrylic: 'Acrylic', - materialMica: 'Mica', - materialSolid: 'Solid', - chatWidth: 'Chat width', - groupChanges: 'Group modified files in the conversation', - expandDiffs: 'Expand change previews by default', - chatWidthComfortable: 'Comfortable', - chatWidthWide: 'Wide', - chatWidthFull: 'Full', - terminalCursor: 'Terminal cursor', - terminalCursorBar: 'Bar', - terminalCursorBlock: 'Block', - terminalCursorUnderline: 'Underline', - terminalGpu: 'Draw terminals on the GPU', - terminalGpuHint: 'Smoother scrolling and output. Turn it off if a terminal draws badly on this device; it applies to terminals opened next.', - connection: 'Connection', - localCore: 'This app runs on the core it started on this computer.', - pair: 'Pair', - manual: 'Core URL and token', - coreUrl: 'Core URL', - token: 'Token', - connect: 'Connect', - warmProcessMinutes: 'Warm process minutes', - listenOnLan: 'Reachable on the local network', - listenOnLanHint: 'Lets a phone on the same network reach this computer, from the next start of Boite. A --host or --lan flag wins over it.', - agentCpuCapPercent: 'CPU cap, %', - agentCpuCapHint: 'For all agents together. 0 means no cap. Windows only.', - threadMemoryCapMb: 'Memory cap per conversation, MB', - threadMemoryCapHint: 'For one conversation and its processes. 0 takes half the agents\' budget, which it cannot exceed.', - agentMemoryBudgetPercent: 'Memory budget for all agents, %', - agentMemoryBudgetHint: 'For all agents together, from 10 to 90%. The default is 60%.', - memoryReserveMb: 'Memory reserved for other apps, MB', - memoryReserveHint: 'Kept free for your other apps. 0 uses 10% of RAM, at least 3072 MB. Below 12288 MB of RAM, it uses 25%.', - memoryProtection: 'Protect available memory', - memoryProtectionHint: 'Over a limit, Boite stops the largest process tree an agent started, never the agent itself. Off also lifts the Windows memory caps.', - background: 'Background', - closeToTray: 'Keep Boite running when the window closes', - closeToTrayHint: 'Hides to the notification area and keeps turns running. Quit always stops Boite.', - launchAtLogin: 'Start Boite when I sign in', - launchAtLoginHint: 'Boite starts in the notification area with its core running, so this computer can run agents and answer your other devices without its window. Applies to this user account.', - notifications: 'Notify me when a thread finishes or asks something', - notificationsHint: 'Only for a thread you are not looking at. This machine only.', - notificationsDenied: 'Notifications are blocked for Boite on this system; allow them in its settings.', - focusGuard: 'Keep agent windows out of the foreground', - muteAgents: 'Mute agent audio', - reapOrphans: 'Stop what agents leave running', - reapOrphansHint: 'Ten seconds after a turn, a process whose parent has exited is stopped. Windows only.', - asyncQuestions: 'Let agents ask without stopping', - asyncQuestionsHint: 'Teaches agents the boite ask command when a session starts. Codex already asks this way.', - shortAnswers: 'Short answers', - shortAnswersHint: 'Asks every agent, on every message, for the result first in a few plain sentences, without preamble, recap or running commentary. Facts, numbers and commands stay.', - toolSummaries: 'Tool summaries (Claude)', - toolSummariesHint: 'Claude labels each batch of tool calls in one line instead of counting them. Each label is one request to its small model, Haiku, billed to the thread\'s account. Applies when a Claude process starts.', - titleModel: 'Title model', - titleModelHint: 'Names a thread after its first answer. Automatic uses the thread\'s own agent on its small model.', - titleModelAuto: 'Automatic', - titleModelAutoHint: "The thread's agent, small model", - titleModelSmall: 'small', - numberRange: 'A whole number from {min} to {max}.', - saved: 'Saved', - core: 'Core', - version: 'Version', - protocol: 'Protocol', - os: 'OS', - pid: 'PID', - dataDir: 'Data directory', - endpoint: 'Endpoint', - noCore: 'Not connected to a core.', - archived: { - doneRetentionLabel: 'Delete done threads after (days)', - doneRetentionHint: 'Counted from Mark done or an automatic archive after a PR merge. 3 days by default; 0 disables deletion. Manual archives are kept. Deleted conversations remain recoverable for the configured deletion retention.', - deletesAt: 'Deletion: {date}', - heading: 'Archived threads', - mergedReason: 'Archived after PR #{number} merged', - intro: 'The agent of an archived thread stops. It starts again with the first message after a restore.', - show: 'Show archived threads', - empty: 'No archived thread on this machine.', - restore: 'Restore', - }, - deleted: { - heading: 'Deleted conversations', - retentionLabel: 'Days before permanent deletion', - retentionHint: 'Counted from deletion. 0 keeps conversations indefinitely. Shortening the delay also applies to conversations already deleted.', - retentionError: 'Enter a whole number between 0 and 3650 days.', - purgesAt: 'Permanent deletion: {date}', - show: 'Show deleted conversations', - empty: 'No deleted conversation to restore on this machine.', - }, - /** The worktrees a thread leaves on disk, and their removal by hand. */ - worktrees: { - storage: 'Storage location', - storageProject: 'In each project', - storageShared: 'One shared folder', - storageProjectHint: 'Inside each project, under', - storageDirectory: 'Folder on this machine', - storagePlaceholder: 'Absolute folder path', - storageBrowse: 'Choose a folder', - storageSharedHint: 'Each project gets its own subfolder. Projects with the same name stay separate.', - storageNewOnly: 'New worktrees only. Existing ones stay where they are.', - storageInvalid: 'Enter an absolute folder path on the machine that runs your agents.', - storageSaveFailed: 'Could not save the storage location. Check the path and connection, then try again.', - heading: 'Worktrees', - intro: 'A worktree stays on disk after its thread is archived. Removing one deletes the folder and its branch.', - show: 'Show worktrees', - noProject: 'No git project on this machine.', - empty: 'No worktree left.', - sweep: 'Remove the clean ones ({count})', - remove: 'Remove', - dirty: 'uncommitted changes', - unmerged: 'commits on no other branch', - missing: 'folder gone', - heldBy: 'used by "{title}"', - archivedThread: 'thread archived: "{title}"', - heldHint: 'A thread works here: archive it first', - forceTitle: 'Remove {name} anyway?', - forceBody: 'It has {reasons}. Removing it deletes the folder and its branch, and that work is lost.' - }, - }, - - /** The Keyboard settings page: the table, the file, and the commands the palette has no word for. */ - keyboard: { - heading: 'Shortcuts', - intro: 'Click a shortcut, then press the keys you want instead. It applies at once on this machine.', - search: 'Filter commands', - groups: { general: 'General', surfaces: 'Side panel', thread: 'Thread and composer', jump: 'Go to a thread', theme: 'Theme', terminal: 'Terminal' }, - record: 'Press the keys', - change: 'Change the shortcut', - reset: 'Back to the default', - resetAll: 'Reset all', - clear: 'Remove the shortcut', - conflict: '{chord} already runs "{command}". Move it here?', - replace: 'Move it here', - needsModifier: 'Hold Ctrl, Alt or Win with the key. A key alone, or with Shift only, is typing.', - noMatch: 'No command matches.', - file: 'Shortcuts file', - openFile: 'Open the file', - copyPath: 'Copy the path', - openFailed: 'Could not open the file: {reason}', - fileHint: - 'Command ids to chords such as "mod+shift+k", or null to take a key away. mod is Ctrl here and Cmd on a Mac. Entries you leave out keep their default.', - id: 'Id', - none: 'Not set', - custom: 'changed', - problems: 'Refused', - problemsHint: 'These lines of the file were not applied. Fix them and save.', - commands: { - palette: 'Open the command palette', - browser: 'Open the browser surface', - changes: 'Open the changes surface', - files: 'Open the file tree', - tasks: 'Open the tasks surface', - closeSurface: 'Close the active surface', - stash: 'Stash the composer text, or take it back', - sendAndDraft: 'Send and open a new draft', - pin: 'Pin or unpin this thread', - reopenThread: 'Reopen the thread archived last', - copyAnswer: 'Copy the last answer', - find: 'Find in this thread', - terminalNew: 'New terminal', - terminalSplit: 'Split the terminal right', - terminalSplitVertical: 'Split the terminal down', - terminalClose: 'Close the terminal', - terminalFind: 'Find in the terminal', - terminalHistory: 'Recent commands of the shell', - terminalPreviousCommand: 'Go to the previous command', - terminalNextCommand: 'Go to the next command', - /** `Go to thread 3`: the third row of the sidebar, as drawn. */ - thread: 'Go to thread {n}' - } - }, - - experiments: { - recordingIndicators: { title: 'Show actions in recordings', hint: 'Add click markers and navigation keys to browser recordings. Typed text and password input are never shown as key labels.' }, - openChatLinks: { title: 'Open chat links in their apps', hint: 'On this desktop, clicking a local file or folder opens its default app, including shortcuts and files outside the project. Only links from this computer are opened.' }, - whip: { - title: 'Whip', - hint: 'An animated whip follows your pointer or finger. Flick to crack it, click or tap to drop it', - action: 'Throw the whip', - drop: 'Drop the whip' - }, - previewComments: { title: 'Preview comments', hint: 'Select an element in the integrated browser and add feedback to the conversation draft' }, - goal2: { title: 'Reviewed goals', hint: 'Adds /goal2: when the agent says the goal is done, a new conversation that has not seen its work checks it and answers pass, partial or fail. Anything but a pass goes back to the agent, for up to five reviews' }, - themeGrain: { - title: 'Grain theme', - hint: 'A grainy ground fading from light grey at the top left to black at the bottom right' - }, - sessionImport: { - title: 'Claude Code session import', - hint: 'Turn a session started in the terminal into a thread, from the project menu and the palette' - }, - promptCache: { - title: 'Prompt cache timer', - hint: 'A clock beside the context meter counting down how long the provider keeps the conversation cached' - }, - residentAgents: { - title: 'Agents', - hint: 'Resident agents with their own brain, missions and routines, from the sidebar. Agents you already made keep running while this is off' - }, - edgeScrollbars: { - title: 'Edge scrollbars', - hint: 'Scrollbars take no room. Each one fades in as the pointer nears the edge of its area and can still be dragged. Mouse and pen only' - }, - pressPreload: { - title: 'Load on press', - hint: 'A thread or page starts loading when you press it, before you release the click or lift your finger' - } - }, - - /** Finished features, on by default, each switched from the Settings page it belongs to. */ - features: { - chatArtifacts: { title: 'Chat files and previews', hint: 'Open file links and preview files returned by an agent in the conversation' }, - }, - - quotas: { - machineAccounts: 'Account limits for {machine}', - outdated: 'Update Boite on this machine to see its limits on this device.', - usingCredits: 'Using credits', - reorder: 'Reorder {name}. Drag or use the arrow keys.', - bankedReset: '{count} banked reset', - observed: 'Observed {time}', - remainingUnder: 'Less than {percent}% left', - bankedResets: '{count} banked resets', - resetExpires: 'Next reset expires {time}', - useReset: 'Use reset', - resetUsing: 'Using reset', - resetConfirmTitle: 'Use a reset for {account}?', - resetConfirmBody: 'This consumes the available reset closest to expiring on this {provider} account and clears its current usage limits. It cannot be undone.', - resetConfirm: 'Use one reset', - resetApplied: 'Reset applied. Usage limits refreshed.', - resetNothing: 'Nothing to reset right now. No reset was consumed.', - resetNoCredit: 'No banked reset is available for this account.', - resetAlreadyUsed: 'This reset was already used. Limits refreshed.', - resetRefreshFailed: 'Reset applied, but the new limits could not be read. Refresh to check.', - resetFailed: 'Could not use the reset: {error}', - budgetRemaining: 'Monthly budget remaining', - budgetCompact: 'Monthly budget', - creditCompact: 'Credits', - windowWeeklyCompact: 'Week', - windowMonthlyCompact: 'Month', - creditRemaining: 'Credits remaining', - creditBalance: '{count} credits', - - /** The tray icon's right-click menu. */ - trayShow: 'Show', - trayQuit: 'Quit', - names: { muse: 'Muse Code', claude: 'Claude', codex: 'Codex', antigravity: 'Antigravity', grok: 'Grok', opencode: 'OpenCode Go' }, - noReading: 'Unavailable', - cliSource: 'agy command-line login', - connect: 'Connect account', - noReset: 'Reset time not reported', - heading: 'Provider quotas', - intro: 'Subscription limits reported by your providers. Separate from tokens used in Boite.', - remaining: '{percent}% left', - resets: 'Resets {time}', - /** The limit windows the core names in English: `5 hours`, `Weekly`, `Monthly`, `Credits`, alone or on one side of ` · `. */ - windowHours: '{hours} hours', - windowWeekly: 'Weekly', - windowMonthly: 'Monthly', - windowCredits: 'Credits', - checked: 'Updated {time}', - stale: 'Last successful reading', - empty: 'Connect a provider to see its subscription limits.', - unsupported: 'This provider does not expose subscription quotas here.', - disabled: 'Quota monitoring is off for this account.', - loading: 'Reading provider limits', - slowHint: 'Antigravity can take up to two minutes to answer.', - tracked: 'Tracked accounts', - trackedHint: 'Boite only asks the accounts switched on here.', - autoResume: 'Resume threads after a usage limit', - autoResumeHint: 'A thread stopped by a subscription usage limit resumes by itself a minute after the limit resets, once per limit window. Stop, a new message or a changed model cancels it.', - providers: 'Manage providers', - unavailable: 'No quota reading available.', - refresh: 'Refresh quotas', - /** The sidebar's gauge button and the popover it opens. */ - glance: 'Account limits', - /** The popover's way to the Limits page in Settings. */ - allLimits: 'Limits page', - /** A read of the limits failed; the core's reason follows. */ - readFailed: "Couldn't read the limits: {error}", - quit: 'Quit Boite', - }, - harnessUpdates: { - heading: 'Agent updates', - intro: 'Each machine checks its own agents every six hours.', - auto: 'Update agents automatically', - autoHint: 'An agent updates with nobody connected: a running thread pauses after its tool call and goes on once the update is done', - checking: 'Checking', - available: (name: string, version: string) => `${name} ${version} is available`, - availableShort: 'Update available', - installed: (version: string) => `Installed: ${version}`, - on: (machine: string) => `On ${machine}`, - update: 'Update', skip: 'Skip', - updating: (name: string) => `Updating ${name}`, - waiting: (count: number) => count === 1 ? 'Waits for a thread to pause' : `Waits for ${count} threads to pause`, - failed: (name: string) => `${name} did not update`, - upToDate: 'Up to date', unknown: 'Checks by itself', runUpdater: 'Run its updater', - skipped: (version: string) => `${version} skipped`, - unskip: 'Offer it again', none: 'No agent on this machine has an updater Boite can run.', - route: { managed: 'Installed by Boite', self: 'Your own install' } - }, - providerSettings: { - heading: 'Providers', - intro: 'An agent already signed in on this computer is picked up on its own.', - step: { - installing: 'Installing', - install: 'Not installed', - repair: 'Installed files are missing', - manual: 'Not installed', - 'signing-in': 'Signing in', - 'sign-in': 'Not signed in', - external: 'Not signed in', - ready: 'Ready' - }, - accountsCount: '{count} accounts', - thenSignIn: 'Sign-in starts when the download finishes.', - manualHint: '{provider} comes with its own installer. Run it, then come back: Boite finds it on its own.', - externalHint: '{provider} signs in from its own app. Sign in there, then check again.', - setup: 'Install guide', - refresh: 'Check again', - details: 'Details', - isolated: 'Boite account', - default: 'Your command-line login', - reconnect: 'Sign in again', - check: 'Check connection', - connectionOk: 'Connected', - checking: 'Checking connection', - accountName: 'Account name', - revealEmail: 'Reveal email', - deviceHint: 'Enter this code on the sign-in page. Boite waits for you to finish.', - addAccount: 'Add another account', - useCli: 'Use my command-line login', - executable: 'Runs from', - accounts: 'Accounts', - installation: 'Installation', - managedCopy: "Boite's copy", - addHeading: 'Add a provider', - offHeading: 'Turned off', - off: 'Turned off', - experimental: 'Experimental', - /** The switch's accessible name: `{provider}` is the provider's own name. */ - enable: 'Use {provider}', - enableHint: 'Off, Boite starts nothing of this provider and no list offers it. Its accounts and conversations are kept.', - /** A thread whose provider is off: the composer's chip and the sentence behind it. */ - threadOff: '{provider} is turned off. Turn it on in Settings > Providers to continue this conversation.', - defaultModel: 'Default model', - autoUpdate: 'Automatic updates', - checkUpdates: 'Check for updates', - updating: 'Updating' - }, - plugins: { - heading: 'Plugins', - /** A desktop app asked to show a thread or an agent on a machine this window does not have. */ - revealUnknownMachine: 'The thread or agent an app asked to show is on a machine this window is not connected to. Add it in Settings, Machines and updates.', - revealMachineOffline: 'The thread or agent an app asked to show is on {machine}, which is not connected right now.', - intro: 'A plugin is a program that runs on the machine hosting your core, checked against the SHA-256 its manifest publishes.', - installedHeading: 'Installed', - recommendedHeading: 'Recommended', - install: 'Install', - update: 'Update to {version}', - reinstall: 'Reinstall', - remove: 'Uninstall', - stateInstalled: 'Installed', - stateInstalling: 'Downloading and checking', - stateError: 'Install failed', - stateRejected: 'Refused by Boite', - updateAvailable: '{version} available', - source: 'Source code', - atCommit: '{ref} at {commit}', - pools: 'Account pools', - empty: 'No saved account in this pool.', - add: 'Save current CLI login', - switch: 'Use this account', - forget: 'Forget account', - active: 'Active', - removeTitle: 'Uninstall {name}?', - removeBody: 'Deletes the plugin\'s folder in Boite\'s data directory. What the program saved elsewhere, such as its saved logins, stays where it is.', - switchTitle: 'Change the CLI login?', - switchBody: 'This changes the provider\'s default CLI login for future turns, including outside Boite. Isolated Boite accounts keep their own login.', - forgetTitle: 'Forget this saved login?', - forgetBody: 'Removes this login from the plugin\'s pool.', - cliScope: 'CLI logins, separate from the Boite accounts connected in Providers.', - addHeading: 'Add from a git URL', - addHint: 'The https URL of a git repository with a boite-plugin.json at its root. Boite shows what it would download before installing anything.', - urlLabel: 'Repository URL', - urlPlaceholder: 'https://github.com/owner/repo', - refLabel: 'Branch, tag or commit', - refPlaceholder: 'Default branch', - inspect: 'Preview', - inspecting: 'Reading', - previewSource: 'Source', - previewReplaces: 'Replaces', - previewDownloads: 'Downloads', - previewDigest: 'SHA-256', - previewRuns: 'Runs', - previewPools: 'Account pools', - previewApp: 'Desktop app', - previewAppBody: 'Opens a window of its own and gets full access to Boite through the owner token: it can read and drive every thread, like this app.', - app: 'Desktop app', - appRunning: 'Running', - appStarting: 'Starting again', - appStopped: 'Stopped', - appOff: 'Stopped · starts again only when you start it', - appCrashed: 'Crashed with exit code {code}', - appCrashedSignal: 'Crashed', - appUnavailable: 'Unavailable', - appStart: 'Start', - appStop: 'Stop', - appRestart: 'Restart', - startWithBoite: 'Start with Boite', - /** A gateway plugin: run in the background on a loopback port, for the subscription proxy. */ - gateway: 'Gateway', - gatewayRunning: '{version} on 127.0.0.1:{port}', - gatewayStarting: 'Starting', - gatewayUpdating: 'Updating', - gatewayCrashed: 'Crashed', - gatewayData: 'Its sign-ins and database live in {dir}, and stay there when it is uninstalled.', - removeGatewayBody: 'Deletes the program in Boite\'s data directory. Its sign-ins, settings and database stay in {dir}, ready for the next install.', - previewGateway: 'Gateway', - previewGatewayBody: 'Runs in the background on this machine, on a loopback port, with no access to Boite. The subscription proxy can point at it.', - trust: 'This program runs with your permissions on the machine hosting your core. Install it only if you trust whoever publishes it.', - confirmAdd: 'Install {name}', - refusedTitle: 'Boite refused this plugin', - refusedFile: 'File', - refusedField: 'Field', - refusedExpected: 'Expected', - /** Recommended plugins whose entry names an update source: the check, and what it last found. */ - checkUpdates: 'Check for updates', - checkingUpdates: 'Checking for updates', - upToDate: 'Up to date · checked {time}', - updateFound: 'Found at {source}, {ref} at {commit} · checked {time}', - updateIgnored: 'Not offered: {reason}', - updateFailed: 'Could not check for updates: {error}. Boite tries again later.', - noticeTitle: '{name} {version} is available', - noticeBody: 'Its download is checked against the SHA-256 its release publishes.', - noticeUpdate: 'Update', - noticeLater: 'Later', - noticeLabel: 'Plugin update', - }, - - time: { - now: 'now' - }, - - errors: { - draftStorage: 'This device could not save your draft. Keep this window open and copy your text before closing it.', - draftAttachment: 'An attachment could not be read from storage. Reload to recover it, or remove it before sending.', - prefix: 'Error', - activityUnsupported: 'This core does not support goals or loops. Update Boite on {machine}, then reconnect. Your command has not been sent.', - pullRequestUnsupported: 'Update Boite on the machine hosting this thread to show its pull request. This core does not support pull request lookup yet.', - noEndpoint: 'No core endpoint could be resolved.', - coreStart: 'The core on this computer did not start: {reason}', - connect: 'Could not connect to the core.', - clipboard: 'The clipboard refused the text.', - revoked: 'This device was removed from the group. Add it again with a new code from one of your machines.', - unpaired: 'This device is not in this machine\'s group yet. Add it with the code from Add a device.', - olderCore: 'Boite on {machine} is too old for this. Update it in Settings, Machines and updates, then try again.', - olderCoreHere: 'This machine runs a Boite too old for this. Update it in Settings, Machines and updates, then try again.', - notConnected: '{machine} is not connected. Boite reconnects by itself once it answers again.', - notConnectedHere: 'This machine is not connected. Boite reconnects by itself once it answers again.', - pairingLink: 'That is not a link to add a device. Copy the link shown by Add a device on the other machine; it starts with http and contains ?grant=.' - }, - - units: { - bytes: 'B', - kilobytes: 'kB', - megabytes: 'MB', - gigabytes: 'GB', - milliseconds: 'ms', - seconds: 's', - minutes: 'min', - hours: 'h', - tokens: 'tokens' - }, - - /** Effort and speed levels by the id the providers share; another id keeps the provider's own word. */ - effortLevels: { - none: 'None', - off: 'Off', - minimal: 'Minimal', - low: 'Low', - medium: 'Medium', - high: 'High', - xhigh: 'Xhigh', - max: 'Max', - ultra: 'Ultra', - ultrathink: 'Ultrathink', - fast: 'Fast' - }, - - /** A part of the app kept in a chunk of its own (`components/Deferred.svelte`), and the phone's navigation (App.svelte). */ - parts: { - loading: 'Loading', - failed: 'This part of Boite did not load.', - navigationFailed: 'The navigation did not load.', - dictationFailed: 'Dictation did not load. Try again' - }, - - common: { - none: 'none', - unknown: 'unknown', - yes: 'yes', - no: 'no', - close: 'Close', - moreInfo: 'About {topic}', - refresh: 'Refresh', - dismiss: 'Dismiss', - cancel: 'Cancel', - /** Verbs every view shares. A status word stays with its subject: French agrees it in gender. */ - retry: 'Try again', - save: 'Save', - rename: 'Rename', - delete: 'Delete', - copy: 'Copy', - open: 'Open', - }, - - /** - * The tour a machine sees once, the first time Boite opens on it. Every - * screen carries the switch for what it explains, so reading it and setting - * it up are the same pass. - */ - onboarding: { - /** Boite Legacy's trade offer: picking a row is the tour's last click. */ - privacy: { - title: 'SELL YOUR SOUL TO BOITE!', - intro: 'A few anonymous stats tell us what to fix first. Your messages, files and accounts never leave your machine.', - question: 'Deal?', - video: 'Trade offer meme', - basic: 'NO! Just the basic counters', - basicDefault: '(default)', - basicHint: 'Anonymous counters, no profile.', - deal: 'DEAL', - dealHint: 'Also the models and settings you use. Thank you!', - details: 'Basic: active installs under a hashed random ID, and launches, conversations and turn outcomes under IDs that change every day. No profile. Deal adds the public models you pick, reasoning level, speed and permission modes, durations and token volumes, under a stable random ID; private models count as "other". The app works the same either way, and Settings changes it anytime.', - optOut: "If you REALLY don't want to help Boite:", - offLabel: 'turn everything off :c', - doc: 'What is collected' - }, - demo: { - pause: 'Pause', play: 'Replay', resume: 'Resume', - record: 'Click the microphone', speak: 'Speak your message', review: 'Review before sending', - listening: 'Listening', draft: 'Your draft', localLabel: 'On this computer', - chooseAgent: 'Choose the next agent', handoff: 'Codex picks up the conversation', checked: 'Labels checked. Contrast improved.', nextMessage: 'Message Codex', - before: 'Before', after: 'After', buttonBefore: 'Send', buttonAfter: 'Send message', changedFile: 'ContactForm.svelte', changesReady: 'Two lines changed', - background: 'Working in the background', writing: "Preparing tomorrow's launch", notes: 'Notes', noteOne: 'Review the new contact form', noteTwo: 'Publish the portfolio', - taskResult: 'Contact form ready', loginResult: 'Login fixed', testsResult: 'Tests passed', hoverTray: 'Hover the Boite icon', - conversation: 'Change agent', voice: 'Dictate', panel: 'Inspect changes', - task: 'Build my portfolio', secondTask: 'Fix the login', thirdTask: 'Write the tests', working: 'Working', request: 'Add a contact form', answer: 'The form is ready.', followup: 'Now check the accessibility', continued: 'Same thread. Same history.', - voiceWords: 'Make the buttons easier to read', voiceHint: 'Speak, review the draft, then send.', - changes: 'Changes', reviewed: 'Review the changes beside the chat.', - usageLabel: 'Claude', window: '5-hour limit', used: '24% used', reset: 'Resets in 2 h 10 min', trayHint: 'Hover the Boite icon to see your limits without opening the app.', - desktop: 'Your computer', phone: 'Your phone', synced: 'The same conversation', reachHint: 'After pairing by QR code, follow your agents from your phone. Your computer keeps running them.', - notification: 'Contact form finished', quietBody: 'Let your agents work without taking over your screen or your speakers.', - installVoice: 'Install local dictation', downloadVoice: 'About 200 MB. Audio stays on this computer.', installingVoice: 'Downloading voice engine', cancelVoice: 'Cancel download', readyVoice: 'Dictation is ready. Use the microphone in the composer.', - }, - label: 'Getting started', - skip: 'Skip', - back: 'Back', - next: 'Next', - done: "Let's Boite", - step: 'Step {index} of {total}', - progress: 'Go to step {index}: {title}', - replay: 'Show the tour again', - changeLater: 'All of this is in Settings.', - - /** The question the preset hangs on: said plainly, with a smile, so nobody is unsure which one they are. */ - profile: { - title: 'First, which one are you?', - everyday: "I'm not a developer! Don't confuse me with code and commands!", - everydayHint: 'A new conversation waits in Documents/Boite until you give it a folder, and the agent asks before it acts.', - developer: "I'm a developer, give me the works.", - developerHint: 'The app opens on your last project, conversations show the terminal and the process trace, and the panel opens on the changes.', - later: 'Nothing is taken away either way. Settings, Appearance shows or hides any button, one by one.' - }, - - welcome: { - title: 'Boite runs your agents', - body: 'One conversation per task. The agents keep working while you do something else.' - }, - - /** Switching model inside a live conversation, the composer's picker. */ - agents: { - title: 'A conversation that follows your work', - body: 'Switch agents, dictate a message, inspect the result. Stay in the same thread.', - }, - - /** Dictation: what it does, and where this core stands on setting it up. */ - voice: { - title: 'Talk instead of typing', - body: 'Press the microphone and speak. You read the text before it is sent.', - open: 'Voice settings', - reading: 'Reading the voice engine' - }, - - /** The panel on the right, each surface with its key. */ - panel: { - title: 'The panel beside the conversation', - body: 'Changes, files, tasks and a browser, one key away.', - open: 'Keyboard shortcuts', - }, - - /** The quota bars, and the switch that fills them. */ - usage: { - title: 'See what you have left', - body: 'See which subscription is filling up and when it resets, even with Boite in the background.', - monitor: 'Read subscription limits for {account}', - connect: 'Connect a provider', - deviceHint: 'Limits are read on the machine hosting the core.' - }, - - /** The two ways this core reaches further: a phone, and another core. */ - reach: { - onPhoneTitle: 'Your computer does the work', - onPhoneBody: 'This phone shows the conversations of your computer. The agents run there and keep going when you put the phone away.', - onPhoneSettings: 'Notifications and the home screen app are in Settings.', - title: 'Step away. Keep the conversation.', - phone: 'On your phone', - phoneBody: 'Scan a QR code and your phone gets the conversations of all your machines, with notifications.', - pair: 'Add a device', - machines: 'Another Boite', - machinesBody: 'Drive a Boite running on a server or another computer from this app.', - connect: 'Connect a machine', - deviceHint: 'Pairing and machines are managed from the app the core runs in.' - }, - - /** The switches everyone ends up looking for on the first evening. */ - quiet: { - title: 'Agents that do not interrupt you', - deviceHint: 'Focus and audio are settings of the machine hosting the core.' - }, - - /** The last screen, and the only one that does something irreversible: it opens a folder. */ - project: { - title: 'Open your first project', - body: 'The folder an agent works in.', - } - } +export const strings = { + ...subscriptionProxy, + ...settingRequest, + ...agentSettingRequest, + ...remoteBrowser, + ...agentBrowser, + ...devicePanel, + ...agents, + ...previewComments, + ...artifacts, + ...brain, + ...hooks, + ...diagnostics, + ...telemetry, + ...speech, + ...phone, + ...mobile, + ...machines, + ...group, + ...app, + ...titlebar, + ...serverUpdate, + ...desktopUpdate, + ...appUpdate, + ...updateAll, + ...connection, + ...threadStatus, + ...drafts, + ...firstRun, + ...connect, + ...drop, + ...sidebar, + ...threadMove, + ...palette, + ...imports, + ...agentShape, + ...media, + ...btw, + ...slash, + ...activity, + ...mention, + ...notify, + ...permissionMode, + ...permissionModeLong, + ...permissionModeCodex, + ...permissionModeAgy, + ...thread, + ...chat, + ...agentMessages, + ...coordination, + ...resident, + ...contextMode, + ...delegation, + ...workflow, + ...composer, + ...rightPanel, + ...changes, + ...tasks, + ...files, + ...browserTools, + ...browser, + ...browserProfiles, + ...terminal, + ...trace, + ...protection, + ...taskManager, + ...resources, + ...install, + ...accounts, + ...usage, + ...controls, + ...settings, + ...keyboard, + ...experiments, + ...features, + ...quotas, + ...harnessUpdates, + ...providerSettings, + ...plugins, + ...time, + ...errors, + ...units, + ...effortLevels, + ...parts, + ...common, + ...onboarding, } as const; export type Strings = typeof strings; diff --git a/packages/ui/src/lib/strings.fr.ts b/packages/ui/src/lib/strings.fr.ts index f09435cdd..6ec3dfdf6 100644 --- a/packages/ui/src/lib/strings.fr.ts +++ b/packages/ui/src/lib/strings.fr.ts @@ -1,5 +1,6 @@ /* - * Le catalogue français, calqué sur `strings.en.ts`. + * Le catalogue français, calqué sur `strings.en.ts`, un fichier par clé de + * premier niveau dans `./strings/fr/`, nommé comme son modèle anglais. * * Le type vient de l'anglais : une clé inconnue de l'anglais arrête * `svelte-check`. Une phrase pas encore traduite peut manquer ici, elle @@ -8,2836 +9,177 @@ * Les accolades sont des emplacements remplis par `fill`, leur nom ne se * traduit pas. */ - import type { Translation } from './i18n.svelte'; +import { fr as subscriptionProxy } from './strings/fr/subscriptionProxy'; +import { fr as settingRequest } from './strings/fr/settingRequest'; +import { fr as agentSettingRequest } from './strings/fr/agentSettingRequest'; +import { fr as remoteBrowser } from './strings/fr/remoteBrowser'; +import { fr as agentBrowser } from './strings/fr/agentBrowser'; +import { fr as devicePanel } from './strings/fr/devicePanel'; +import { fr as agents } from './strings/fr/agents'; +import { fr as previewComments } from './strings/fr/previewComments'; +import { fr as artifacts } from './strings/fr/artifacts'; +import { fr as brain } from './strings/fr/brain'; +import { fr as hooks } from './strings/fr/hooks'; +import { fr as diagnostics } from './strings/fr/diagnostics'; +import { fr as telemetry } from './strings/fr/telemetry'; +import { fr as speech } from './strings/fr/speech'; +import { fr as phone } from './strings/fr/phone'; +import { fr as mobile } from './strings/fr/mobile'; +import { fr as machines } from './strings/fr/machines'; +import { fr as group } from './strings/fr/group'; +import { fr as app } from './strings/fr/app'; +import { fr as titlebar } from './strings/fr/titlebar'; +import { fr as serverUpdate } from './strings/fr/serverUpdate'; +import { fr as desktopUpdate } from './strings/fr/desktopUpdate'; +import { fr as appUpdate } from './strings/fr/appUpdate'; +import { fr as updateAll } from './strings/fr/updateAll'; +import { fr as connection } from './strings/fr/connection'; +import { fr as threadStatus } from './strings/fr/threadStatus'; +import { fr as drafts } from './strings/fr/drafts'; +import { fr as firstRun } from './strings/fr/firstRun'; +import { fr as connect } from './strings/fr/connect'; +import { fr as drop } from './strings/fr/drop'; +import { fr as sidebar } from './strings/fr/sidebar'; +import { fr as threadMove } from './strings/fr/threadMove'; +import { fr as palette } from './strings/fr/palette'; +import { fr as imports } from './strings/fr/imports'; +import { fr as agentShape } from './strings/fr/agentShape'; +import { fr as media } from './strings/fr/media'; +import { fr as btw } from './strings/fr/btw'; +import { fr as slash } from './strings/fr/slash'; +import { fr as activity } from './strings/fr/activity'; +import { fr as mention } from './strings/fr/mention'; +import { fr as notify } from './strings/fr/notify'; +import { fr as permissionMode } from './strings/fr/permissionMode'; +import { fr as permissionModeLong } from './strings/fr/permissionModeLong'; +import { fr as permissionModeCodex } from './strings/fr/permissionModeCodex'; +import { fr as permissionModeAgy } from './strings/fr/permissionModeAgy'; +import { fr as thread } from './strings/fr/thread'; +import { fr as chat } from './strings/fr/chat'; +import { fr as agentMessages } from './strings/fr/agentMessages'; +import { fr as coordination } from './strings/fr/coordination'; +import { fr as resident } from './strings/fr/resident'; +import { fr as contextMode } from './strings/fr/contextMode'; +import { fr as delegation } from './strings/fr/delegation'; +import { fr as workflow } from './strings/fr/workflow'; +import { fr as composer } from './strings/fr/composer'; +import { fr as rightPanel } from './strings/fr/rightPanel'; +import { fr as changes } from './strings/fr/changes'; +import { fr as tasks } from './strings/fr/tasks'; +import { fr as files } from './strings/fr/files'; +import { fr as browserTools } from './strings/fr/browserTools'; +import { fr as browser } from './strings/fr/browser'; +import { fr as browserProfiles } from './strings/fr/browserProfiles'; +import { fr as terminal } from './strings/fr/terminal'; +import { fr as trace } from './strings/fr/trace'; +import { fr as protection } from './strings/fr/protection'; +import { fr as taskManager } from './strings/fr/taskManager'; +import { fr as resources } from './strings/fr/resources'; +import { fr as install } from './strings/fr/install'; +import { fr as accounts } from './strings/fr/accounts'; +import { fr as usage } from './strings/fr/usage'; +import { fr as controls } from './strings/fr/controls'; +import { fr as settings } from './strings/fr/settings'; +import { fr as keyboard } from './strings/fr/keyboard'; +import { fr as experiments } from './strings/fr/experiments'; +import { fr as features } from './strings/fr/features'; +import { fr as quotas } from './strings/fr/quotas'; +import { fr as harnessUpdates } from './strings/fr/harnessUpdates'; +import { fr as providerSettings } from './strings/fr/providerSettings'; +import { fr as plugins } from './strings/fr/plugins'; +import { fr as time } from './strings/fr/time'; +import { fr as errors } from './strings/fr/errors'; +import { fr as units } from './strings/fr/units'; +import { fr as effortLevels } from './strings/fr/effortLevels'; +import { fr as parts } from './strings/fr/parts'; +import { fr as common } from './strings/fr/common'; +import { fr as onboarding } from './strings/fr/onboarding'; export const fr: Translation = { - subscriptionProxy: { - heading: 'Proxy d\'abonnements', enable: 'Utiliser un proxy d\'abonnements', - hint: 'Claude et Codex utilisent la passerelle de cette machine, Grok aussi derrière Douane, et OpenCode 2 ajoute ses autres modèles aux siens. Limites ouvre son tableau de bord dans Boite. Les autres agents gardent leur configuration.', - baseUrl: 'URL de l\'API', dashboardUrl: 'URL du tableau de bord des limites', key: 'Clé API (facultative)', - keyHint: 'Laissez vide pour garder la clé enregistrée. Utilisez la clé API du proxy, pas sa clé de gestion du tableau de bord.', - clearKey: 'Retirer la clé enregistrée', save: 'Enregistrer le proxy', saved: 'Proxy enregistré', - configure: 'Configurer le proxy', native: 'Voir les limites des comptes', - showDashboard: 'Voir le tableau du proxy', - openGateway: 'Ouvrir {name}', - signInHint: 'Connectez-vous avec Ouvrir {name}, puis rechargez ici. Si la page reste vide, ce tableau peut refuser les vues intégrées.', - mixedContent: "Cette page utilise HTTPS. Configurez une URL de tableau HTTPS pour l'afficher ici, ou ouvrez le tableau HTTP séparément.", - enabledHint: 'Activé, les comptes connectés sur cette machine ne sont plus utilisés pour Claude et Codex, ni pour Grok derrière Douane.', - account: 'Les requêtes passent par {name} ({origin}). Les connexions de ce PC ne sont pas utilisées.', - via: 'via {name}', - quotasFailed: 'Impossible de lire les limites de {name} : {error}', - quotasEmpty: '{name} ne signale encore aucune limite.', - averageOne: '{name} · 1 compte', - average: '{name} · {count} comptes', - entryStatus: { ready: 'Prêt', cooldown: 'En pause', error: 'Erreur', disabled: 'Désactivé' }, - creditOf: '{remaining} sur {limit}', - creditUsed: '{used} utilisés', - where: 'Où tourne la passerelle', - remote: 'Passerelle distante', local: 'Douane sur cette machine', - localHint: 'Le plugin Douane, installé et lancé par Boite depuis Réglages > Plugins. Le proxy pointe tout seul sur son port local, et le suit si le port change.', - localMissing: 'Le plugin Douane n\'est pas installé sur cette machine.', - localStopped: 'Le plugin Douane est arrêté.', - localStarting: 'Le plugin Douane démarre.', - localRunning: 'Douane {version} répond sur 127.0.0.1:{port}.', - openPlugins: 'Ouvrir Plugins', - move: { - open: 'Déplacer mes connexions vers Douane', - openHint: 'Douane reprend les connexions et les clés API que cette machine a déjà : personne ne se reconnecte ni ne recolle de clé.', - title: 'Déplacer les connexions vers Douane', - intro: "Les connexions Claude, Codex et Grok sont déplacées : leurs jetons changent à chaque renouvellement, donc dès que Douane en tient une, il sert cet agent par la passerelle et la copie de cette machine ne marche plus. Désactiver la passerelle plus tard oblige à se reconnecter ici. Les clés API sont copiées telles quelles.", - loading: 'Lecture des connexions de cette machine', - empty: 'Rien ici que Douane puisse reprendre.', - token: 'Jeton de gestion (facultatif)', - tokenHint: "Seulement pour un Douane dont l'API d'administration en demande un. Il reste sur cette machine et n'est plus jamais affiché.", - tokenSaved: 'Un jeton de gestion est enregistré ; laissez vide pour le garder.', - clearToken: 'Retirer le jeton enregistré', - confirm: 'Déplacer {count}', - moving: 'Déplacement', - failed: 'Déplacement impossible : {error}', - login: 'Connexion {provider}', - copilot: 'Connexion GitHub Copilot', - endpoint: 'Clé API {source}', - opencodeGo: 'Clé OpenCode Go', - route: 'servie comme {route}', - outcomes: { moved: 'Déplacée', present: 'Déjà là', refused: 'Refusée', missing: 'Plus ici' }, - reasons: { - signedOut: 'Pas connecté', unreadable: 'Fichier de connexion illisible', noAddress: "Pas d'adresse d'API connue", - otherApi: "Pas une API compatible OpenAI", otherSignIn: 'Douane ne peut pas reprendre cette connexion', routeTaken: 'Route prise par un autre point d\'accès', - }, - }, - }, - settingRequest: { - heading: 'Réglage suggéré', - headingOpen: 'À voir dans les Réglages', - headingSensitive: 'Réglage sensible', - apply: 'Appliquer', - show: 'Voir dans Réglages', - open: 'Ouvrir les Réglages', - refuse: 'Refuser', - dismiss: 'Ignorer', - sensitiveHint: "Celui-ci se change depuis les Réglages, à côté de son explication. La carte se ferme une fois le réglage changé.", - guestHint: "Seul l'ordinateur qui fait tourner Boite peut changer ce réglage.", - elsewhere: "C'est dans les Réglages de l'ordinateur qui fait tourner Boite.", - loadFailed: "Cette carte de réglage n'a pas chargé.", - to: 'vers', - applied: 'Appliqué', - changed: 'Réglé sur {value} à la place', - refused: 'Refusé', - opened: 'Réglages ouverts', - dismissed: 'Ignoré', - closed: 'Fermé sans réponse', - on: 'Activé', - off: 'Désactivé', - none: 'Aucun', - worktreeProject: 'Dans chaque projet', - worktreeShared: 'Dans {dir}', - compactAt: 'À partir de {tokens} tokens', - compactAny: 'Quelle que soit la taille', - keys: { - asyncQuestions: 'Laisser les agents demander sans s\'arrêter', - shortAnswers: 'Réponses courtes', - toolSummaries: 'Résumés des outils (Claude)', - titleModel: 'Modèle des titres', - threadDoneRetentionDays: 'Supprimer les conversations terminées après', - threadDeletionRetentionDays: 'Purger les conversations supprimées après', - worktreeStorage: 'Emplacement des nouveaux worktrees', - focusGuard: 'Garder les fenêtres des agents en arrière-plan', - muteAgents: 'Couper le son des agents', - agentCpuCapPercent: 'Plafond CPU de tous les agents', - agentMemoryBudgetPercent: 'Budget mémoire de tous les agents', - threadMemoryCapMb: 'Plafond mémoire par conversation', - memoryReserveMb: 'Mémoire réservée aux autres applications', - memoryProtection: 'Protéger la mémoire disponible', - reapOrphans: 'Arrêter ce que les agents laissent tourner', - autoUpdateHarnesses: 'Mettre à jour les agents automatiquement', - listenOnLan: 'Accessible sur le réseau local', - publicUrl: 'Adresse publique pour les téléphones', - browserOrigins: 'Origines de navigateur autorisées', - warmProcessMinutes: 'Garder un agent chaud après son tour', - autoCompact: 'Compaction automatique', - autoResumeLimitedThreads: 'Reprendre les conversations après une limite d\'usage', - }, - zero: { - agentCpuCapPercent: 'Sans plafond', - threadMemoryCapMb: 'Automatique', - memoryReserveMb: 'Automatique', - threadDoneRetentionDays: 'Jamais', - threadDeletionRetentionDays: 'Jamais', - warmProcessMinutes: 'Libéré tout de suite', - }, - }, - agentSettingRequest: { - heading: 'Changement suggéré pour {name}', - apply: 'Appliquer', - show: 'Ouvrir ses réglages', - refuse: 'Refuser', - guestHint: "Seul l'ordinateur qui fait tourner Boite peut changer les réglages d'un agent.", - projectsHint: 'Décochez un projet pour le laisser de côté.', - entrust: 'Confier', - takeBack: 'Reprendre', - loadFailed: "Cette carte de réglage d'agent n'a pas chargé.", - to: 'vers', - applied: 'Appliqué', - changed: 'Réglé sur {value} à la place', - refused: 'Refusé', - closed: 'Fermé sans réponse', - on: 'Activé', - off: 'Désactivé', - none: 'Aucun', - noProjects: 'Aucun projet', - defaultEffort: 'Par défaut', - minutes: '{count} min', - send: 'Envoyer', - keys: { - projects: 'Projets', - model: 'Modèle', - effort: 'Niveau de raisonnement', - autoMemory: 'Mettre à jour la mémoire après chaque tâche', - subagents: 'Sous-agents', - maxRunMinutes: 'Minutes maximum par exécution', - }, - }, - remoteBrowser: { - display: 'Affichage', resolution: 'Résolution de la page', fitPhone: 'Adapter à cet écran', phone: 'Téléphone', tablet: 'Tablette', rotate: 'Pivoter', width: 'Largeur', height: 'Hauteur', apply: 'Appliquer', sharedSize: 'La résolution change aussi la page que voit l’agent.', restoreSize: 'Revenir à la taille par défaut', previewZoom: 'Zoom de l’aperçu (cet appareil seulement)', fit: 'Ajuster', panHint: 'Glissez pour déplacer l’aperçu. Utilisez les flèches pour défiler dans la page.', - address: 'Adresse', addressPlaceholder: 'Saisir une adresse', back: 'Précédent', forward: 'Suivant', reload: 'Recharger', badAddress: 'Saisissez une adresse web (http ou https).', - title: 'Navigateur de l’agent', waiting: 'En attente de la page', paused: 'En pause', reconnecting: 'Reconnexion', pause: 'Pause', resume: 'Reprendre', - interact: 'Toucher ou faire défiler la page partagée', interactDesk: 'La page partagée : cliquez dessus, puis écrivez', - deskHint: 'Cliquez dans la page, puis écrivez. Glissez pour sélectionner, double-cliquez un mot, défilez à la molette. Copier, couper et coller passent par le clavier.', - copy: 'Copier le texte sélectionné', copied: 'Copié.', nothingSelected: 'Rien n’est sélectionné dans la page. Touchez deux fois un mot d’abord.', copyCut: 'La sélection était trop longue : son début a été copié.', image: 'Vue en direct du navigateur de l’agent', scrollUp: 'Défiler vers le haut', scrollDown: 'Défiler vers le bas', - text: 'Touchez un champ de la page, puis écrivez ici', send: 'Envoyer le texte à la page', gesture: 'Touchez pour cliquer. Glissez pour défiler. Retour envoie le texte puis Entrée ; ⌫ sur un champ vide efface dans la page.' - }, - agentBrowser: { - cover: 'Cet agent contrôle un navigateur sur {machine}', coverHint: 'Rien n’est diffusé vers cet appareil avant que vous l’affichiez.', - show: 'Afficher', hide: 'Masquer', hideHint: 'Arrêter de regarder et recouvrir la vue', - none: 'L’agent n’a pas de navigateur ouvert', noneHint: 'Il apparaît ici quand l’agent ouvre une page.', - unavailable: 'Pas de navigateur d’agent sur {machine}', tabs: 'Onglets de l’agent', untitled: 'Page sans titre', - newTab: 'Nouvel onglet', closeTab: 'Fermer {name}', newTabHint: 'Ouvrez une page dans le navigateur de cette conversation sur {machine}. L’agent la voit et peut s’en servir.' - }, - devicePanel: { - cover: 'Cet agent contrôle un appareil sur {machine}', coverHint: 'Rien n’est diffusé vers cet appareil avant que vous l’affichiez.', - show: 'Afficher', hide: 'Masquer', hideHint: 'Arrêter de regarder et recouvrir la vue', - empty: "Aucun appareil n'est ouvert dans cette conversation. Un agent en ouvre un avec boite device open, ou ouvrez-en un ici.", - onMachine: 'Sur cette machine', add: 'Ouvrir un appareil', - none: 'Aucun simulateur ni émulateur trouvé sur cette machine.', - starting: 'Démarrage de {name}. Cela peut prendre une minute.', failed: "{name} n'a pas démarré", - stopped: 'Éteint', running: 'Allumé', booting: 'Démarrage', - viewOnly: "Lecture seule. L'agent pilote les simulateurs iOS avec xcrun simctl.", - back: 'Retour', home: 'Accueil', recents: 'Applications récentes', rotate: 'Pivoter', power: "Bouton d'alimentation", enter: 'Entrée', backspace: 'Effacer le caractère précédent', - text: 'Écrire dans le champ actif', send: 'Envoyer le texte', ascii: "Seuls les caractères ASCII imprimables atteignent l'appareil.", - screenshot: "Enregistrer une capture d'écran", close: 'Fermer {name}', shutdown: 'Éteindre', - interact: "Touchez ou glissez sur l'écran de l'appareil", interactDesk: "L'écran de l'appareil : cliquez dessus, puis écrivez", - deskHint: "Cliquez sur l'écran, puis écrivez. Glissez pour balayer, défilez à la molette, Échap revient en arrière. Coller écrit le presse-papiers ; copier depuis l'appareil n'est pas disponible.", image: 'Vue en direct de {name}', gesture: 'Touchez pour appuyer. Glissez pour balayer.' - }, - agents: { - runtime: "Modèles et limites", - brain: "Cerveau", - routines: "Routines", - defaultModel: "Modèle par défaut", - allowedModels: "Modèles autorisés pour cet agent", - subagents: "Modèles des sous-agents", - enableSubagents: "Autoriser les sous-agents", - addModel: "Ajouter un modèle", - removeRoute: "Retirer ce modèle", - compactEvery: "Compacter après ce nombre de tours", - runMinutes: "Minutes maximum par exécution", - runtimeHint: 'Le modèle par défaut doit être autorisé. Retirer un accès arrête les travaux qui s\'en servent et les suspend pour vérification.', - brainHint: 'Instructions et skills survivent aux changements de modèle. Les contextes partagés gardent leur propre mémoire.', - compactNow: "Compacter ce contexte", - routineHint: "Vos agents s’en chargent seuls, même cette fenêtre fermée. Une tâche encore en cours n’est jamais relancée.", - runNow: "Exécuter maintenant", - schedule: "Planification", - once: "Une fois", - interval: "À intervalle régulier", - daily: "Chaque jour", - everyMinutes: "Toutes les N minutes", - timezone: "Fuseau horaire", - accountAccess: "Accès aux comptes", - accountAccessHint: 'Quels agents peuvent utiliser chaque compte, sous-agents compris. Retirer un accès arrête les exécutions qui s\'en servent.', - allAgents: "Tous les agents, actuels et futurs", - heading: 'Agents', intro: "Des agents avec leur rôle, leur mémoire et leurs missions. Les sous-agents d'une conversation apparaissent dans Sous-agents.", back: 'Retour aux conversations', - profiles: 'Agents', groups: 'Groupes', teams: 'Équipes', missions: 'Missions', attention: 'À examiner', - list: 'Tous les agents', create: 'Nouveau', edit: 'Modifier', - search: 'Rechercher', empty: 'Rien ici pour le moment.', select: 'Sélectionnez un élément pour voir son activité.', - name: 'Nom', domain: 'Rôle', instructions: 'Instructions', identityHint: 'Changer de modèle conserve son identité et sa mémoire.', - model: 'Modèle et compte', noAccount: 'Ajoutez un compte fournisseur dans les réglages avant de créer un agent.', permissions: 'Permissions', - ask: 'Demander avant les actions', edits: 'Autoriser les modifications', plan: 'Planifier uniquement', full: 'Accès complet', deny: 'Ne pas demander', - noApprovals: 'Ce fournisseur ne permet pas de valider les outils en direct. Ses permissions de lancement restent applicables. Les demandes de décision restent disponibles.', - tools: 'Outils de collaboration', messages: 'Messages', memory: 'Mémoire', artifacts: 'Résultats', decisions: 'Décisions', - members: 'Membres', responsibility: 'Responsabilité', description: 'Description', project: 'Projet', noProject: 'Sans projet', - team: 'Équipe', noTeam: 'Sans équipe', group: 'Groupe de discussion', noGroup: 'Aucun groupe associé', - mode: 'Qui répond', mentions: 'Destinataires choisis', round: 'Une réponse par membre', autonomous: 'Prendre la parole à tour de rôle', - maxTurns: 'Nombre maximum de tours', perAgent: 'Tours par agent', modeHint: 'Chaque nouveau message ouvre un budget. Ses réponses partagent ce budget. Les échanges automatiques cessent à la première limite atteinte.', - paused: 'En pause', pause: 'Suspendre', resume: 'Reprendre', archive: 'Archiver', active: 'Actif', archived: 'Archivé', - archiveTitle: 'Archiver "{name}" ?', archiveBody: 'L’archivage arrête le travail de cet agent et le retire de la liste active. Son profil et son historique restent accessibles par la recherche. Saisissez son nom actuel exactement pour confirmer.', archiveName: 'Nom de l’agent', - objective: 'Objectif', expectedResult: 'Résultat attendu', minutes: 'Minutes totales de travail', tokens: 'Limite de jetons, vide si aucune', - finishMission: 'Terminer la mission', reopenMission: 'Rouvrir la mission', - conversation: 'Discussion', activity: 'Activité', send: 'Envoyer', message: 'Écrire un message', recipients: 'Destinataires', - user: 'Vous', noWork: 'Aucune exécution pour le moment.', loadEarlier: 'Afficher les plus anciens', - openRun: 'Ouvrir cette exécution', requested: 'Demandé', executed: 'Exécuté', noUsage: 'Consommation non communiquée', - pending: 'En attente', running: 'Au travail', waiting: 'Attend une décision', done: 'Terminé', cancelled: 'Annulé', interrupted: 'Interrompu', error: 'Échec', review: 'À relire', open: 'Ouvert', assigned: 'Attribué', accepted: 'Accepté', - included: 'Inclus dans cette exécution', processed: 'Traité', failed: 'Échec', limited: 'Limite des échanges atteinte', - reconcile: 'Vérifier et reprendre', reconcileHint: 'Inspectez les fichiers et le travail effectué. Indiquez ce qui est terminé et ce qui peut continuer.', reconcileNote: 'Qu’avez-vous vérifié ?', - answer: 'Répondre', answerPlaceholder: 'Votre décision et vos instructions', - task: 'Tâche', tasks: 'Tâches', addTask: 'Ajouter une tâche', acquire: 'Attribuer', dependencies: 'Attendre ces tâches', approve: 'Accepter le résultat', revise: 'Demander des modifications', reject: 'Refuser', feedback: 'Retour pour la prochaine tentative', - result: 'Résultat', verification: 'Vérifications déclarées par cet agent', workspace: 'Espace de travail', copyPath: 'Copier le chemin', - resources: 'Ressources', addResource: 'Ajouter une ressource', directory: 'Dossier', url: 'URL', value: 'Valeur', read: 'Lecture', write: 'Écriture', - resourceHint: 'Ces accès guident le travail. Les permissions du fournisseur contrôlent les actions sur les fichiers.', - addMemory: 'Ajouter un souvenir', text: 'Texte', scope: 'Portée', sources: 'Sources', ownerMemory: 'Ajouté par vous', - searchKnowledge: 'Rechercher dans la mémoire et les ressources', expiresAt: 'Expiration facultative', expired: 'Expiré', expireMemory: 'Faire expirer', - engine: 'Travail en arrière-plan', concurrency: 'Agents simultanés', engineHint: 'Fermer cette page laisse le travail continuer sur le moteur connecté.', - idle: 'Disponible', offline: 'Chargement impossible. Reconnectez cette machine ou mettez son moteur à jour.', retry: 'Réessayer', - stale: 'Connexion perdue. Voici le dernier état reçu. Le travail peut continuer sur la machine hôte.', - history: 'Historique', - minutesUnit: 'minutes', status: 'État', settings: 'Réglages', overview: 'Vue d’ensemble', createAction: 'Créer', rolePlaceholder: 'Recherche, développement ou relecture', - newTitle: { profile: 'Nouvel agent', group: 'Nouveau groupe', team: 'Nouvelle équipe', mission: 'Nouvelle mission' }, - summary: '{running} au travail · {pending} en attente', - engineSettings: 'Réglages du moteur', engineSettingsHint: 'La quantité de travail en arrière-plan de ce moteur, et les comptes que ses agents peuvent utiliser.', - advanced: 'Plus d’options', identity: 'Identité', noMembers: 'Créez d’abord un agent.', - brainFiles: 'Fichiers du cerveau', procedures: 'Procédures', proceduresHint: 'AGENTS.md dans le dossier du cerveau. Les façons de travailler réutilisables.', notes: 'Notes', notesHint: 'MEMORY.md dans le dossier du cerveau. Ce que cet agent garde d’une conversation à l’autre.', - contexts: 'Contextes de conversation', memories: 'Souvenirs', toEveryone: 'Tout le monde', - missionMembers: 'Agents de cette mission', dangerZone: 'Arrêter le moteur', dangerHint: 'Arrête tous les agents de ce moteur et déconnecte ses clients.', - stopEngine: 'Arrêter ce moteur', stopEngineBody: 'Arrête les travaux et déconnecte les clients. Les exécutions commencées demanderont une vérification avant reprise. Rouvrez Boite sur la machine hôte pour redémarrer le moteur.', - createAgent: 'Créer un agent', noAgents: 'Aucun agent pour l’instant', noAgentsOn: 'Aucun agent sur {machine} pour l’instant', more: 'Plus', noMatch: 'Aucun résultat', - noMessagesYet: 'Aucun message pour l’instant', noMissions: 'Aucune mission pour l’instant', previewFrom: '{name} : {text}', unread: '{count} non lus', - backToChat: 'Retour à la discussion', backTo: 'Retour à {name}', editRoles: 'Modifier les rôles', startChat: 'Démarrer la discussion', - asks: '{name} vous demande', - planned: 'Tâches planifiées', plannedResult: 'Tâche planifiée', - entrustTo: 'Confier à un agent', entrustToName: 'Confier à {name}', entrustedTo: 'Confié à {name}', takeBack: 'Reprendre la main', - threadEvent: { entrusted: '{name} reprend « {title} »', done: 'Terminé · {title}', blocked: 'A besoin de toi · {title}', stopped: 'Arrêté · {title}' }, - openThread: 'Ouvrir le thread', deliveryFailed: '{name} ne l’a pas reçu', - messageTo: 'Écrire à {name}', sayHello: 'Dites bonjour à {name}, ou planifiez une tâche qu’il fera tout seul.', - liveWorking: '{name} travaille', liveWaiting: '{name} a besoin de vous', seeThread: 'Ouvrir son thread', - welcomeHint: 'Un agent garde son nom, sa mémoire et ses tâches. Parlez-lui, ou planifiez du travail qu’il fera seul.', - runsHeading: 'Où il tourne', runsHint: 'Ses conversations, sa mémoire et ses tâches planifiées restent sur cette machine. Les modèles viennent des comptes de cette machine.', - machine: 'Machine', modelLabel: 'Modèle', machineOffline: 'Hors ligne', machineNotOwner: 'Accès propriétaire requis', - machineUnreachable: '{name} n’est pas connectée. Reconnectez-la, puis créez l’agent à nouveau.', whatItDoesHint: 'Ses instructions permanentes. Vous pourrez les changer plus tard dans ses réglages.', - namePlaceholder: 'Mira, Scout, Pixel', whatItDoes: 'Ce qu’il fait', instructionsPlaceholder: 'Par exemple : chaque matin, lis les nouvelles issues et résume-les en cinq lignes.', - ownedBy: '{name} gère ce thread', ownedHint: 'Parlez à {name} dans sa discussion. Ce thread montre ce qu’il fait.', openAgent: 'Ouvrir {name}', - projects: 'Projets', projectsHint: 'Il suit leurs threads, en lance de nouveaux, répond à leurs questions et vous dit ce qui compte.', noProjects: 'Ouvrez d’abord un projet.', looksAfter: 'S’occupe de {names}', - seeWork: 'Voir son travail', aboutProjects: 'À propos de vos projets', memoryUpdated: 'Mémoire mise à jour', memoryUpdating: 'Mise à jour de la mémoire', - autoMemory: 'Mettre à jour la mémoire après chaque tâche', autoMemoryHint: 'Un petit assistant réécrit ses notes à partir de ce que vous avez demandé et de ce qu’il a répondu.', - brainFolder: 'Dossier des cerveaux', brainFolderHint: 'Où chaque agent garde ses instructions et ses notes sur cette machine. Le changer les déplace tous.', brainDefault: 'Par défaut, dans le dossier de données de Boite', - brainChange: 'Changer', brainUseDefault: 'Utiliser le dossier par défaut', brainMove: 'Déplacer les cerveaux ici', brainPath: 'Dossier sur cette machine', brainBrowse: 'Parcourir', brainParent: 'Dossier parent', brainUseFolder: 'Utiliser ce dossier', - robot: { - family: 'Style', shape: 'Forme', color: 'Couleur', eyes: 'Yeux', top: 'Accessoire', shuffle: 'Mélanger', customize: 'Personnaliser', done: 'Terminé', - option: '{part} {n}', families: { jelly: 'Gelée', bubble: 'Bulle', capsule: 'Capsule', retro: 'Rétro' }, - }, - when: { - who: 'Agent', pickAgent: 'Choisir un agent', what: 'Que doit-il faire ?', whatPlaceholder: 'Par exemple : résume les nouveaux messages et dis-moi lesquels attendent une réponse.', - when: 'Quand ?', modes: { once: 'Une fois', daily: 'Chaque jour', days: 'Certains jours', interval: 'Régulièrement' }, - inAnHour: 'Dans une heure', thisEvening: 'Ce soir, 18:00', tomorrowMorning: 'Demain, 09:00', dateAndTime: 'Date et heure', - otherInterval: 'Toutes les', minutesUnit: 'minutes', at: 'À', inThePast: 'Cette date est passée. Choisissez un moment à venir.', pickDays: 'Choisissez au moins un jour.', - next: 'Prochaine fois : {when}', name: 'Nom', schedule: 'Planifier', newRoutine: 'Planifier une tâche', scheduleThis: 'Planifier ce message comme une tâche', scheduleShort: 'Planifier', - everyMinutes: 'Toutes les {count} minutes', everyHour: 'Toutes les heures', everyHours: 'Toutes les {count} heures', everyDay: 'Tous les jours', everyDays: 'Tous les {count} jours', - minutesShort: '{count} min', hoursShort: '{count} h', - onceAt: 'Une fois, {date}', daily: 'Tous les jours à {time}', weekdays: 'Du lundi au vendredi à {time}', weekend: 'Le week-end à {time}', onDays: 'Chaque {days} à {time}', - today: 'aujourd’hui à {time}', tomorrow: 'demain à {time}', dateAt: '{date} à {time}', - lastRun: 'Dernière exécution {when} : {state}', stillRunning: 'Sa dernière exécution n’est pas terminée.', - }, - }, - previewComments: { - annotate: 'Référencer un élément', - picking: 'Sélectionnez un élément dans la page. Échap annule.', - add: 'Ajouter au message', - added: 'Élément ajouté à votre message.', - unavailable: 'Cet élément est inaccessible. Utilisez l’application de bureau pour les pages d’une autre origine.', - failed: 'La sélection a échoué. Rechargez la page et réessayez.', - reveal: 'Montrer cet élément dans l’aperçu', - remove: 'Retirer la référence à cet élément', - stale: 'Cet aperçu a changé de page. Rouvrez la page d’origine ou sélectionnez à nouveau l’élément.', - missing: 'Cet élément n’existe plus dans la page. Sélectionnez-le à nouveau.', - tooMany: 'Un message peut référencer au maximum 8 éléments de page.', - activityUnsupported: 'Les références ne sont pas disponibles avec /goal ou /loop. Envoyez un message ordinaire ou retirez les références.', - stashUnsupported: 'Ce brouillon contient des références et ne peut pas être mis de côté. Envoyez-le ou retirez les références.' -}, - artifacts: { viewReplay: 'Rejouer', viewExpand: 'Afficher en grand', viewCollapse: 'Fermer la vue agrandie', loadImage: 'Charger', mediaFailed: 'Impossible d’afficher ce média. Réessayez ou téléchargez le fichier pour l’ouvrir.', videoFailed: 'Cet appareil ne lit pas cette vidéo. Téléchargez-la pour la regarder dans une autre app.', saving: 'Enregistrement', zoomIn: 'Agrandir', zoomOut: 'Réduire', fitImage: 'Ajuster l’image', openLocal: 'Ouvrez ce fichier dans son application par défaut.', preview: 'Aperçu', download: 'Télécharger', close: 'Fermer l’aperçu', closeImage: 'Fermer l’image', enlarge: 'Voir en grand', saved: 'Enregistré dans Téléchargements', loading: 'Chargement du fichier', unavailable: 'Ce fichier ne peut pas être affiché ici. Téléchargez-le pour l’ouvrir.', failed: 'Impossible d’ouvrir ce fichier', ambiguous: (name: string, paths: string) => `${name} désigne plusieurs fichiers : ${paths}`, ownerOnly: 'Les liens vers les fichiers locaux nécessitent une connexion propriétaire. Demandez à l’agent de joindre le fichier pour le partager avec cet appareil.' }, - - brain: { - heading: 'Brain', description: 'Les mêmes instructions et skills pour tous vos agents.', - folder: 'Dossier du brain', pathHint: 'Chemin du dossier', save: 'Connecter le dossier', browse: 'Parcourir', useFolder: 'Choisir ce dossier', - enabled: 'Utiliser avec les agents', disconnect: 'Déconnecter', sync: 'Synchroniser', working: 'En cours', - empty: 'Connectez votre brain', emptyHint: 'Choisissez le dossier de vos instructions et skills.', - change: 'Changer de dossier', - autoPull: 'Pull automatique', onStartup: 'Au démarrage', periodic: 'À intervalle régulier', minutes: 'min', - globalInstructions: 'AGENTS.md global', globalDetails: 'Harness reliés', - globalHint: 'Ce brain s\'applique à tous les projets, même hors de Boite. Les fichiers existants sont sauvegardés puis restaurés à la désactivation.', - boiteGuide: 'Guide Boite', boiteGuideHint: 'Une note après AGENTS.md au début d\'une session d\'agent : la commande boite et le panneau. Environ 300 tokens.', - linked: 'Relié', existing: 'Déjà relié', blocked: 'À vérifier', - interval: 'Intervalle du pull en minutes', intervalHint: 'Un nombre entier entre 1 et 1440 minutes.', - detected: 'Contenu', instructions: 'Instructions', skill: 'Skills', plugin: 'Plugins', - pluginHint: 'Détectés ici. Installez les plugins depuis leur agent.', - details: 'Détails de la synchro', noGit: 'Dossier local. Connectez un dépôt Git pour synchroniser entre PC.', - noUpstream: 'Configurez une branche distante dans Git pour activer la synchro.', dirty: 'Enregistrez vos changements dans Git avant de synchroniser.', - counts: '{ahead} à envoyer, {behind} à recevoir', lastSync: 'Dernière synchro', never: 'Pas encore synchronisé', - upToDate: 'À jour', noEntries: 'Rien de détecté dans cette catégorie.', - missing: 'Mettez Boite à jour sur cette machine pour connecter un brain.', - }, - hooks: { - heading: 'Hooks', - description: 'Les scripts que vos agents lancent sur leurs propres événements. Les compteurs et la liste récente partent du démarrage de Boite sur cette machine.', - refresh: 'Actualiser les hooks', - missing: 'Mettez Boite à jour sur cette machine pour voir vos hooks.', - hookOne: '{count} hook', hookMany: '{count} hooks', - moduleOne: '{count} module', moduleMany: '{count} modules', - none: 'Aucun hook pour l\'instant', - notFound: 'Rien trouvé', - runOne: '{count} exécution', runMany: '{count} exécutions', - noRuns: 'Aucune exécution pour l\'instant', - blockedOne: '{count} bloquée', blockedMany: '{count} bloquées', - failedOne: '{count} en échec', failedMany: '{count} en échec', - skippedOne: '{count} ignorée', skippedMany: '{count} ignorées', - sourceMissing: 'Introuvable', - sourceError: 'Illisible', - unreadable: 'Impossible de lire {path}', - configOnly: 'Boite lit cette configuration mais ne voit pas chaque exécution.', - sharedOne: 'Partagé avec {count} compte séparé', sharedMany: 'Partagé avec {count} comptes séparés', - accountMisses: '{account} ne reçoit pas {path}', - withoutOne: '{names} n\'exécute pas de hooks.', withoutMany: '{names} n\'exécutent pas de hooks.', - recent: 'Récemment', - since: 'Depuis {time}', - quiet: 'Rien de bloqué ni en échec.', - more: 'Afficher {count} de plus', - outcomes: { blocked: 'Bloqué', failed: 'Échec', stopped: 'Arrêté', skipped: 'Ignoré' }, - }, - diagnostics: { - heading: 'Diagnostic', - intro: "Ce que cette machine a noté de ses propres problèmes. Un export ou un ticket est anonymisé : chemins, noms et adresses deviennent des marqueurs, les identifiants de conversation restent.", - status: 'État', - loading: 'Lecture des journaux', - version: 'Boite', - system: 'Système', - uptime: 'Cœur lancé depuis', - lastDay: 'Dernières 24 heures', - levels: { debug: 'débogage', info: 'info', warn: 'avertissements', error: 'erreurs' }, - files: 'Fichiers de journal', - noFiles: 'Aucun fichier de journal pour le moment', - problems: 'Problèmes', - problemsHint: 'Avertissements et erreurs des dernières 24 heures, regroupés par type, erreurs en premier.', - problemsEmpty: 'Aucun avertissement ni erreur ces dernières 24 heures.', - times: '{count}×', - lastSeen: 'dernier {time}', - latest: 'Dernier message', - threads: 'Conversations touchées', - missingThread: 'absente de cette machine', - log: 'Journal', - level: 'Niveau', - atLeast: { debug: 'Tout', info: 'Info et plus', warn: 'Avertissements et erreurs', error: 'Erreurs seulement' }, - origin: 'Source', - origins: { all: 'Toutes les sources', core: 'Cœur', shell: 'Application de bureau', ui: 'Interface' }, - thread: 'Conversation', - allThreads: 'Toutes les conversations', - search: 'Chercher dans le journal', - period: 'Période', - periods: { '15m': '15 dernières minutes', '1h': 'Dernière heure', '24h': 'Dernières 24 heures', '7d': '7 derniers jours' }, - loadOlder: 'Charger plus ancien', - noOlder: 'Rien de plus ancien sur cette période.', - empty: 'Aucune entrée ne correspond à ces filtres.', - live: 'Les nouvelles entrées apparaissent tant que cette page est ouverte.', - duration: 'Durée', - details: 'Détails', - ids: 'Identifiants', - agent: 'Agent', - parent: 'Lancée par', - turn: 'Tour', - request: 'Requête', - run: 'Exécution', - export: 'Exporter les journaux anonymisés', - exportHint: "Les dernières 24 heures dans un fichier texte lisible par un développeur : environnement, problèmes regroupés, conversations et chronologie.", - exportDone: '{name} enregistré ({size}).', - exportDownloaded: '{name} téléchargé ({size}).', - report: 'Signaler un problème', - reportHint: "Prépare un ticket GitHub pour Boite avec votre description et, si vous l'acceptez, les journaux anonymisés.", - title: 'Titre', - titlePlaceholder: "Ce qui n'a pas marché, en quelques mots", - description: "Ce qui s'est passé", - descriptionPlaceholder: "Ce que vous avez fait, ce que vous attendiez et ce que vous avez vu", - includeLogs: 'Joindre les journaux anonymisés', - preview: 'Aperçu', - previewHint: 'Voici le texte que contiendra le ticket.', - previewUpdate: "Mettre à jour l'aperçu", - submit: 'Créer le ticket', - openGithub: 'Ouvrir sur GitHub', - ghMissing: "GitHub CLI n'est pas configuré sur cette machine : le ticket s'ouvre dans votre navigateur. Glissez-y {file} pour joindre les journaux complets.", - ghMissingNoFile: "GitHub CLI n'est pas configuré sur cette machine : le ticket s'ouvre dans votre navigateur.", - opened: "Le ticket s'est ouvert dans votre navigateur.", - created: 'Ticket créé :', - cancel: 'Annuler', - close: 'Fermer', - agentAccess: 'Les agents peuvent lire les journaux anonymisés', - agentAccessHint: "Un agent peut vérifier si Boite elle-même a échoué et rédiger un ticket. Il ne lit que la vue anonymisée, jamais les fichiers sur le disque.", - failed: "Le diagnostic n'a pas pu se charger : {reason}", - outdated: "Boite sur {machine} est antérieur à ce diagnostic : son journal s'affiche plus bas, mais l'état, l'export et le signalement demandent une mise à jour. Mettez-le à jour dans Réglages, Machines et mises à jour.", - outdatedHere: "Boite sur cette machine est antérieur à ce diagnostic : son journal s'affiche plus bas, mais l'état, l'export et le signalement demandent une mise à jour. Mettez-la à jour dans Réglages, Machines et mises à jour." - }, - "telemetry": { - "heading": "Confidentialité et statistiques", - "description": "Les compteurs de base sont activés par défaut. Les statistiques améliorées restent facultatives. Aucun message, nom de projet, chemin, compte ou enregistrement n'est collecté.", - "basic": "Compteurs anonymes", - "basicHint": "Compter les installations actives, lancements, conversations et résultats des tours. Les identifiants d'usage changent chaque jour ; un identifiant distinct compte uniquement les installations.", - "enhanced": "Statistiques améliorées", - "enhancedHint": "Partager les modèles publics choisis, le niveau de raisonnement, les modes de vitesse et de permission, les durées et les volumes de jetons avec un identifiant aléatoire stable. Les modèles privés sont regroupés sous « autre ». Désactiver demande la suppression du profil et de ses événements.", - "unconfigured": "Cette version n'a pas de relais statistique configuré. Rien n'est envoyé.", - "pending": "Suppression en attente. Boite réessaie en arrière-plan. PostHog supprime les événements après acceptation de la demande.", - "export": "Exporter mes statistiques", - "retry": "Réessayer la suppression" - }, - speech: { - enabled: 'Dictée vocale', - enabledHint: "Activez la dictée sur cet appareil. La désactiver retire le micro de la barre de saisie, même si un modèle est installé.", - microphone: 'Microphone', - microphoneHint: 'Le micro utilisé pour dicter sur cet appareil.', - defaultMicrophone: 'Micro par défaut du système', - microphoneNumber: 'Microphone {number}', - microphoneUnavailable: 'Le micro choisi est indisponible. Choisissez un autre micro.', - microphoneDisconnected: 'Le micro a été débranché. Choisissez un autre micro et réessayez.', - testMicrophone: 'Tester le micro', - stopMicrophoneTest: 'Arrêter le test', - inputLevel: 'Niveau sonore du micro', - microphoneTestHint: 'Parlez pour vérifier le niveau. Ce test reste sur votre appareil.', - heading: 'Voix', description: 'Le micro à côté d\'Envoyer, deux minutes au plus à la fois. Le texte attend dans la zone de saisie jusqu\'à l\'envoi.', - start: 'Dicter', stop: 'Terminer la dictée', listening: 'Écoute', opening: 'Attente du micro', transcribing: 'Transcription', - private: 'Sur ce cœur uniquement', cloud: 'Transcrit par votre fournisseur d\'API', retry: 'Relancer la transcription', discard: 'Supprimer l\'enregistrement', - live: 'Aperçu en direct', previewFailed: 'Aperçu en direct en pause. Terminez pour transcrire tout l\'enregistrement.', - local: 'Local', api: 'API', engine: 'Moteur', - engineHint: 'Local transcrit sur cette machine et l\'audio n\'en sort jamais. API envoie l\'audio à Groq ou OpenRouter avec votre clé.', - streaming: 'Transcription en direct', - whistleLanguages: 'Anglais, français, allemand, espagnol, italien, néerlandais et polonais', - setup: 'La dictée n\'est pas encore configurée sur cette machine. Ouvrez les réglages Voix pour le faire en un clic.', ownerSetup: 'Demandez au propriétaire de configurer la voix sur ce cœur.', - unsupported: 'La voix demande un Boite plus récent sur {machine}', unsupportedHint: 'Cette machine tourne sur une version de Boite sans moteur vocal. Mettez Boite à jour là-bas, puis rouvrez Voix.', - unsupportedDictation: 'La dictée demande un Boite plus récent sur {machine}. Mettez Boite à jour là-bas, puis réessayez.', - notReady: 'Pas encore configurée', ready: 'Prête', - readyLocal: 'Prête, {model} sur cette machine', readyApi: 'Prête, {provider} transcrit', - notSetUp: 'La dictée n\'est pas configurée', needsCli: 'Cette machine a aussi besoin de whisper-cli dans le PATH, ou de son chemin sous Avancé.', - statusKey: 'Ajoutez une clé {provider}', downloadingModel: 'Téléchargement de {model}', - statusFailed: 'Le téléchargement ne s\'est pas terminé', statusBroken: 'Les réglages Voix sont illisibles', repair: 'Enregistrer pour réparer', - setUp: 'Configurer la dictée', - update: 'Mettre à jour le moteur', updateHint: 'Un moteur plus rapide est disponible, 8 Mo.', - https: 'L\'accès au micro demande HTTPS ou localhost. Ouvrez cette machine par son adresse HTTPS.', - linux: 'La dictée n\'est pas encore disponible dans l\'app Linux : sa vue web ne donne pas accès au micro. Ouvrez Boite dans un navigateur pour dicter.', - denied: 'L\'accès au micro a été refusé. Autorisez-le dans les réglages du navigateur ou du système, puis réessayez.', - noMicrophone: 'Aucun micro trouvé. Branchez-en un et réessayez.', microphoneBusy: 'Le micro est indisponible. Vérifiez qu\'une autre application ne l\'utilise pas.', - silence: 'Aucune parole détectée. Réessayez plus près du micro.', failed: 'Enregistrement impossible. Réessayez.', - model: 'Modèle', modelHint: 'Nemotron transcrit en direct dans plusieurs langues. Whistle occupe peu de place et comprend sept langues. Whisper reste disponible pour la compatibilité.', - tiers: { fast: 'Petit téléchargement', balanced: 'Équilibré', accurate: 'Précision' }, - download: 'Télécharger', downloading: 'Téléchargement', remove: 'Supprimer {model}', - link: 'Ajouter depuis un lien', linkPlaceholder: 'https://huggingface.co/.../ggml-model.bin', - linkHint: 'Un lien https vers un fichier ggml de whisper.cpp, comme ceux de huggingface.co/ggerganov/whisper.cpp.', - add: 'Ajouter', - advanced: 'Avancé', executable: 'Exécutable Whisper', modelPath: 'Fichier du modèle', savePaths: 'Enregistrer les chemins', - pathHint: 'Laissez les deux vides pour utiliser les modèles ci-dessus. Un fichier de modèle indiqué ici passe avant la liste.', - provider: 'Fournisseur', providerHint: 'Les deux font tourner Whisper Large v3 Turbo. Les clés restent sur ce cœur.', groq: 'Groq', openrouter: 'OpenRouter', - groqKey: 'Clé Groq', openrouterKey: 'Clé OpenRouter', keySaved: 'Enregistrée', keyEmpty: 'Collez une clé', keyReplace: 'Collez une nouvelle clé pour la remplacer', - removeKey: 'Supprimer', saveKeys: 'Enregistrer les clés', - fallback: 'Passer à l\'autre fournisseur', fallbackHint: 'Quand le premier échoue. Demande les deux clés. Le mode local ne passe jamais par une API.', - language: 'Langue', languageHint: 'Vide, la langue est détectée. Un code à deux lettres comme en ou fr la fixe.', - saved: 'Enregistré', ownerOnly: 'Le propriétaire de cette machine la gère.', - }, - phone: { - preparing: "L'application prépare encore ses fichiers hors ligne. Rechargez la page et réessayez.", - settingsOffline: "Les réglages ne sont pas encore téléchargés. Reconnectez-vous et rouvrez les réglages.", - dialogOffline: "Cette fenêtre n'est pas encore téléchargée. Reconnectez-vous et rouvrez-la.", - heading: 'Application mobile', ownAddress: 'Utiliser une autre adresse HTTPS', publicUrl: 'Adresse HTTPS publique', urlPlaceholder: 'https://boite.example.com', - publicUrlHint: 'L\'origine HTTPS de votre reverse proxy, qui doit transmettre la page et /rpc à ce cœur. Les liens pour ajouter un appareil passent par elle.', - install: 'Installer Boite', installed: "Boite tourne comme application installée.", - installHint: "Sur iPhone, ouvrez Partager dans Safari, puis Sur l'écran d'accueil. Sur Android, utilisez Installer l'application dans le menu du navigateur.", - httpsRequired: "Cette connexion est en HTTP. Ouvrez Boite en HTTPS pour activer les fichiers hors ligne et les notifications.", - ownOrigin: "Les notifications s'activent depuis l'application installée depuis l'adresse de cette machine.", - pairFirst: "Ajoutez cette application à votre groupe avant d'activer les notifications.", - unsupported: "Ce navigateur ne gère pas les notifications push ici. Sur iPhone, ajoutez Boite à l'écran d'accueil et ouvrez-le depuis son icône.", - pushHint: 'Quand un agent termine, échoue ou attend votre réponse, même quand Boite est fermé.', - enable: 'Activer les notifications', disable: 'Désactiver les notifications', test: 'Envoyer une notification de test', - enabled: 'Notifications activées pour cet appareil.', testSent: 'Le service push a accepté la notification de test.', - denied: "Les notifications ont été refusées. Vous pouvez changer ça dans les réglages du navigateur ou de l'appareil.", - subscriptionFailed: "Le navigateur n'a pas renvoyé un abonnement push complet.", - multiMachine: "Les notifications viennent de la machine depuis laquelle cette application a été installée, et à travers elle de toutes les autres machines de son groupe.", - relayed: "Cette machine atteint cet appareil à travers celle depuis laquelle l'application a été installée : activez-y les notifications, celles de cette machine arrivent avec.", - tailscale: { - heading: 'HTTPS via Tailscale', - hint: "Tailscale donne à cette machine une adresse HTTPS sur votre tailnet. Boite s'y sert lui-même avec tailscale serve, et les liens pour ajouter un appareil l'utilisent. Un téléphone doit avoir Tailscale connecté pour l'atteindre.", - checking: 'Vérification de Tailscale…', - missing: "Tailscale n'est pas installé sur cette machine.", - download: 'Obtenir Tailscale', - stopped: "Tailscale est installé mais pas connecté. Ouvrez Tailscale et connectez-le, puis vérifiez à nouveau.", - needsLogin: 'Tailscale est déconnecté de votre compte sur cette machine. Connectez-vous, puis vérifiez à nouveau.', - signIn: 'Se connecter à Tailscale', - httpsDisabled: "Les certificats HTTPS sont désactivés sur votre tailnet. Dans la console d'administration Tailscale, page DNS, activez MagicDNS et HTTPS Certificates, puis vérifiez à nouveau.", - openAdmin: "Ouvrir la console d'administration", - off: 'Prêt : ce cœur peut être servi sur {url}.', - on: 'Ce cœur est servi sur {url}.', - onPublic: "Les liens pour ajouter un appareil utilisent cette adresse.", - conflict: "{url} sert déjà {target}. Boite n'y touche pas, sauf si vous le remplacez.", - error: "La commande tailscale n'a pas répondu comme prévu.", - enable: "Activer l'accès HTTPS via Tailscale", - disable: 'Désactiver', - replace: 'Remplacer…', - refresh: 'Vérifier à nouveau', - pair: 'Ajouter un appareil', - approve: 'Autoriser Serve', - replaceTitle: 'Remplacer ce que sert {url} ?', - replaceBody: "Tailscale sert actuellement {target} à cette adresse. La remplacer la fait pointer vers Boite, et ce qui l'utilisait n'y est plus joignable.", - replaceConfirm: 'Remplacer', - disableTitle: 'Ne plus servir Boite via Tailscale ?', - disableBody: "Les téléphones associés via {url} ne peuvent plus joindre ce cœur tant qu'il n'est pas servi à nouveau. Leurs clés restent valides.", - details: { - 'permission-denied': 'Tailscale a refusé la modification. Sous Linux, lancez une fois sudo tailscale set --operator=$USER, puis réessayez.', - 'serve-consent': "Votre tailnet n'autorise pas encore Serve. Ouvrez le lien, approuvez, puis réessayez.", - 'not-logged-in': 'Tailscale est déconnecté. Connectez-vous, puis réessayez.', - timeout: "Tailscale n'a pas répondu à temps. Réessayez.", - unknown: 'Tailscale a refusé la modification. Lancez tailscale serve status sur cette machine pour savoir pourquoi.' - } - } - }, - mobile: { - clearSearch: 'Effacer la recherche', - atWork: '{count} en cours', atWorkFilter: 'Afficher seulement les conversations en cours', - draftTitle: 'Que voulez-vous faire ?', draftHint: 'Lancez une tâche, posez une question ou reprenez votre travail.', - pairTitle: 'Votre Boite, sur votre téléphone', - pairBody: 'Sur l’une de vos machines, ouvrez Réglages, Machines, Ajouter un appareil, puis scannez son QR code ici.', - pairStep: 'Sur votre ordinateur : Réglages, Machines, Ajouter un appareil.', - pairInstalled: 'Vous venez d’installer Boite ? Ajoutez-la ici aussi : sur iPhone, l’application garde sa connexion séparément du navigateur.', - pairPrivacy: 'Le QR code ne sert qu’une fois et expire en quelques minutes. Ce téléphone atteint ensuite toutes les machines du groupe.', - scanInApp: "Utilisez le bouton Scanner ici plutôt que l'app Appareil photo : celle-ci ouvre le lien dans Safari, qui garde sa propre connexion.", - pairingRequired: 'Ajouter cette application à votre groupe', - offlineTitle: 'En attente de votre ordinateur', - offlineBody: 'Gardez Boite ouverte sur votre ordinateur et vérifiez votre connexion. Si vous utilisez Tailscale, gardez-le connecté sur les deux appareils.', - localNetworkTitle: 'Autorisez le réseau local', - localNetworkBody: 'Vos machines sont sur votre réseau local, et votre navigateur en bloque Boite. Dans le navigateur qui fait tourner l’application (Chrome ou un autre), ouvrez Paramètres, Paramètres des sites, Réseau local et autorisez {host}. Sous Android, autorisez aussi Appareils à proximité pour ce navigateur dans ses paramètres d’application.', - troubleshoot: 'Dépanner', troubleshootSteps: 'Vérifications de la connexion', - checkLocalNetwork: 'Réseau local', - checkLocalNetworkHint: 'Dans le navigateur qui fait tourner l’application : Paramètres, Paramètres des sites, Réseau local, autorisez {host}.', - localNetworkStates: { granted: 'Autorisé', denied: 'Bloqué', prompt: 'Sans réponse' }, - checkNearby: 'Appareils à proximité', - checkNearbyHint: 'Dans les paramètres Android : Applications, le navigateur qui fait tourner l’application, Autorisations, Appareils à proximité, Autoriser. Android le demande en plus du réseau local.', - checkReach: 'Joindre {host}', checkOpen: 'Ouvrir', - checkReachHint: 'Ouvrez-la dans un onglet du navigateur. Si la page ne charge pas, ce téléphone ne joint pas la machine : vérifiez Tailscale, le VPN ou le Wi-Fi.', - localNetworkAsk: 'Quand votre navigateur le demande, autorisez Boite à accéder aux appareils de votre réseau local : vos machines s’y trouvent.', - emptyTitle: 'Commencez une conversation', - emptyBody: 'Confiez une tâche à un agent. Vos conversations apparaîtront ici.', - emptyActivity: 'Tout est à jour', - computer: 'Ordinateur connecté', - pairAgain: 'Rajouter', - settingsDevice: 'Ce téléphone', - settingsPhone: 'Application et notifications', - settingsBack: 'Retour aux réglages', - navigation: 'Navigation', threads: 'Conversations', activity: 'Activité', project: 'Choisir un projet', - search: 'Trouver une conversation', noActivity: "Aucun agent n'attend votre réponse.", noThreads: 'Aucune conversation ne correspond.' - }, - machines: { - heading: 'Machines', local: 'Ce PC', projects: 'Projets', recent: 'Récent', - intro: 'Chaque machine exécute ses propres agents sur ses propres fichiers.', - details: 'Détails', address: 'Adresse', updateFailed: 'Mise à jour échouée', - label: 'Nom de la machine', icon: 'Icône de la machine', icons: { desktop: 'Ordinateur fixe', laptop: 'Portable', server: 'Serveur', rack: 'Baie', cloud: 'Cloud', cpu: 'Processeur' }, link: "Lien d'une autre machine", add: 'Ajouter une machine', adding: 'Connexion', connect: 'Connecter', - addHint: "Sur une machine de votre groupe, ouvrez Réglages, Machines, Ajouter un appareil, copiez le lien et collez-le ici.", - scan: 'Scanner le code', pasteLink: 'Coller le lien', scanTitle: 'Scannez le code', - linkInsecure: "Ce lien mène à une adresse HTTP simple, que l'appli ne peut pas ouvrir. Ajoutez l'appareil depuis une machine qui a une adresse HTTPS.", - scanHint: "Sur l'une de vos machines, ouvrez Réglages, Machines, Ajouter un appareil et visez son QR code avec la caméra.", - scanStarting: 'Démarrage de la caméra', scanClose: 'Fermer la caméra', - scanErrors: { - insecure: "La caméra demande une adresse HTTPS. Ouvrez Boite par son adresse HTTPS, ou collez le lien.", - denied: "L'accès à la caméra a été refusé. Autorisez-le pour ce site dans les réglages du navigateur, ou collez le lien.", - missing: 'Aucune caméra trouvée sur cet appareil. Collez le lien à la place.', - failed: "La caméra n'a pas pu démarrer. Collez le lien à la place." - }, - linkReaches: 'Ce lien connecte à {host}.', linkReplaces: 'Ce lien connecte à {host}, listée ici sous le nom {machine}. Il remplace la clé enregistrée pour elle.', linkConnected: 'Ce lien vise {host}, déjà connectée ici sous le nom {machine}.', - remove: 'Retirer la machine', removeTitle: 'Retirer {machine} ?', - removeBody: 'Boite oublie cette machine sur cet appareil.', - open: 'Ouvrir la machine', invalidUrl: "L'URL d'une machine doit être une adresse HTTP ou HTTPS, sans identifiants, sans requête ni fragment.", - duplicate: 'Cette machine est déjà connectée.', refreshPr: 'Actualiser la pull request', - manual: 'Connecter avec une URL et un jeton', - filter: 'Filtrer les machines', all: 'Toutes les machines', - main: 'Machine principale', mainLast: 'La dernière utilisée', - mainHint: "Boite s'ouvre sur cette machine. Si elle est éteinte, Boite s'ouvre sur une autre.", - mainLastHint: "Boite s'ouvre sur la dernière machine utilisée.", - timeout: "La machine n'a pas répondu en 12 secondes. Vérifiez son adresse et ses origines navigateur, puis reconnectez-la.", - linkTitle: 'Se connecter à {host} ?', - linkBody: "Le lien ouvert dirige cette page vers une machine à laquelle cet appareil ne s'est jamais connecté. Cette machine verra tout ce que vous tapez ici. Ne vous connectez que si vous avez créé ce lien ou si vous faites confiance à qui vous l'a envoyé. Annuler garde votre machine actuelle.", - linkConfirm: 'Se connecter', - browserOrigins: 'Origines navigateur autorisées', - browserOriginsHint: "Les machines du groupe, et les adresses où elles sont servies, sont autorisées toutes seules. N'ajoutez ici qu'une page servie par quelque chose hors du groupe. Une origine http(s) exacte par ligne.", - settings: 'Réglages de cette machine', settingsHint: 'Les limites de ressources, le maintien des processus, les mises à jour des agents et le stockage des worktrees concernent uniquement cette machine.', - synced: 'Préférences communes synchronisées avec {source}.', - syncBrainAbsent: 'Connectez un dossier brain sur cette machine pour copier aussi ses options.', - syncKeysAbsent: "L’une des deux machines a un Boite sans raccourcis clavier : ils sont restés tels quels.", - syncStopped: 'La copie s’est arrêtée sur {stage} : {error}. Ce qui précède reste copié.', - syncStages: { settings: 'les réglages', keybindings: 'les raccourcis clavier', brain: 'les options du brain' }, - syncProviders: "À connecter sur cette machine : {providers}. Une connexion ne quitte jamais la machine où elle a été faite.", - syncOpenProviders: 'Ouvrir ses fournisseurs', - threadFailed: 'Une conversation a échoué ici : {thread}', - agentLinkAddressRequired: 'Mettez les deux machines à jour pour utiliser le relais de l’app, ou définissez une adresse HTTPS joignable pour {machine} dans les réglages généraux.', - }, - group: { - rename: 'Renommer le groupe', merging: 'Regroupement des machines connectées', - otherGroup: '{machine} appartient à {group}. Quittez ce groupe sur la machine pour la déplacer ici.', - heading: 'Groupe', - intro: "Vos machines et vos téléphones, dans un seul groupe. Chaque machine joint les autres toute seule, et un téléphone ajouté sur l'une d'elles les atteint toutes. Chaque machine d'un groupe a le contrôle total des autres : ne groupez que ce qui est à vous.", - name: 'Nom du groupe', namePlaceholder: 'Maison', - join: 'Rejoindre', joining: 'Connexion', joinLabel: "Lien d'une autre machine", - joinHint: "Sur une machine du groupe, ouvrez Réglages, Machines, Ajouter un appareil, copiez le lien et collez-le ici. Cette machine rejoint ce groupe avec ses téléphones.", - members: 'Machines et appareils du groupe', self: 'Cette machine', connected: 'Connectée', connecting: 'Connexion', - relayed: 'Connectée via {machine}', through: 'Via {machine}', - unreachable: "Injoignable d'ici. Elle est peut-être éteinte, en veille ou sur un autre réseau.", - insecure: "Cette machine ne donne aucune adresse utilisable par cet appareil, et aucune machine du groupe ne peut l'y mener. Donnez-lui une adresse HTTPS publique.", - loopback: "Cette machine n'écoute que sur elle-même : les autres ne peuvent pas la joindre. Connectez-la à Tailscale, ou activez l'écoute sur le réseau.", - copied: 'Copié', - addDevice: 'Ajouter un appareil', adding: 'Préparation', - addOlder: "Boite sur {machine} est trop ancien pour ajouter un appareil. Mettez-le à jour dans Mises à jour sur cette page, puis revenez.", - scanTitle: 'Scannez avec votre téléphone', - addHint: "Ouvrez Boite sur le téléphone et touchez Scanner le code. Sur un ordinateur, copiez le lien et collez-le sous Rejoindre.", - qr: "Le lien qui ajoute un appareil, en QR code", - copyLink: 'Copier le lien', link: 'Lien pour un ordinateur', - expires: "Sert une fois, jusqu'à {time}", - device: 'Appareil', phone: 'Téléphone', browser: 'Navigateur', devicesList: 'Appareils du groupe', - deviceAdded: 'Ajouté sur {machine}', - deviceLegacy: 'Passe à la clé du groupe à sa prochaine connexion', - thisDevice: 'Cet appareil', - revokeDevice: 'Retirer du groupe', revokeDeviceTitle: 'Retirer {device} du groupe ?', - revokeDeviceBody: "Toutes les machines du groupe le refusent dès maintenant, y compris celles qui sont éteintes, dès leur retour. Pour le rajouter, il faudra un nouveau code.", - remove: 'Retirer du groupe', removeTitle: 'Retirer {machine} du groupe ?', - removeBody: "Les autres machines cessent de lui faire confiance dès qu'elles l'apprennent. Une machine éteinte l'apprend à son retour. Les téléphones ajoutés sur elle restent dans le groupe.", - leave: 'Quitter le groupe', leaveTitle: 'Quitter {group} ?', - leaveBody: "Cette machine oublie le groupe. Les téléphones ajoutés sur cette machine quittent le groupe avec elle.", - syncAll: 'Garder les réglages de toutes les machines comme sur {source}', - syncAllHint: "Copie les limites, les protections des processus, les mises à jour des agents, les raccourcis et les options du brain vers toutes les machines du groupe, maintenant et à chaque changement, tant que cette fenêtre est ouverte. L'accès réseau et les connexions aux fournisseurs restent sur chaque machine.", - lanHint: 'Un téléphone atteint ce lien une fois le réseau local activé et Boite redémarré.', - reach: { - loopback: "Ce lien ne fonctionne que sur cet ordinateur : Boite n'écoute pas encore le réseau local.", - button: 'Rendre accessible sur le réseau local', - restarting: "Boite redémarre pour écouter le réseau local. Les agents en cours terminent leur étape et continuent.", - failed: "Boite n'est pas revenu sur le réseau local. Quittez-le depuis la zone de notification, rouvrez-le, puis ajoutez le téléphone." - }, - firewall: { - blocked: 'Le pare-feu Windows bloque Boite sur ce réseau {networks} : un téléphone ne peut pas joindre cet ordinateur.', - unset: "Le pare-feu Windows n'a pas encore autorisé Boite sur ce réseau {networks} : un téléphone ne peut pas joindre cet ordinateur.", - allow: 'Autoriser dans le pare-feu Windows', - allowing: 'En attente de Windows', - allowed: 'Le pare-feu Windows laisse désormais les téléphones joindre Boite sur tous les réseaux.', - cancelled: "Rien n'a changé : Windows a besoin qu'un administrateur accepte sa demande.", - failed: 'Windows a refusé la modification. Autorisez boite-core.exe dans les paramètres du pare-feu Windows.', - unreadable: "Le pare-feu Windows n'a pas pu être lu. Si un téléphone ne se connecte pas, autorisez-y boite-core.exe.", - networks: { public: 'public', private: 'privé', domain: "d'entreprise" }, - and: ' et ' - } - }, - app: { - name: 'Boite', - nightlyName: 'boite (de nuit)', - channelDev: 'Dev', - channelDevTitle: 'Le canal dev, sur son propre répertoire de données', - loading: 'Démarrage', - compacting: "Libération d'espace disque dans l'historique. Cela n'arrive qu'une fois et peut prendre quelques minutes.", - noEndpointTitle: 'Aucun cœur à joindre', - noEndpointBody: - "Sur l'une de vos machines, ouvrez Réglages, Machines, Ajouter un appareil, puis scannez ou tapez son code ici.", - openSettings: 'Ouvrir les réglages' - }, - - titlebar: { - minimize: 'Réduire', - maximize: 'Agrandir', - restore: 'Restaurer', - quitHold: 'Maintenez Ctrl+Q pour quitter', - quitHoldHint: 'ou appuyez deux fois', - quitTitle: 'Fermer Boite ?', - quitWorkingOne: '1 agent travaille encore ou est en attente. Êtes-vous sûr de vouloir fermer Boite ?', - quitWorkingMany: '{count} agents travaillent encore ou sont en attente. Êtes-vous sûr de vouloir fermer Boite ?', - quitUnknownBody: 'Le travail sur une machine déconnectée n’a pas pu être vérifié.', - quitResidentBody: 'Les moteurs résidents continuent de travailler après la fermeture. Ceux configurés pour s’arrêter avec l’application s’arrêteront.', - quitConfirm: 'Fermer Boite', - quitCancel: 'Garder ouvert' - }, - - serverUpdate: { - heading: 'Serveur', - updates: 'Mises à jour', - current: 'À jour', - waiting: 'En attente de la fin du travail', - installing: 'Redémarrage du serveur', - reconnecting: 'Reconnexion', - progress: 'Téléchargement de la mise à jour du serveur', - release: 'Nouveautés', - details: 'Détails', - timing: 'Les agents terminent leur outil en cours, 30 secondes au plus, puis le serveur se met à jour, redémarre et les reprend. Une mise à jour d’agent en cours se termine d’abord.', - backup: 'Conversations et réglages sauvegardés. Si le démarrage échoue, la version précédente est restaurée.', - reconnect: 'Les appareils connectés se reconnectent automatiquement.', - confirmTitle: 'Mettre à jour {machine} ?', - confirmBody: 'Les agents terminent leur outil en cours, 30 secondes au plus. La version {version} s’installe ensuite et ils reprennent après le redémarrage. Vos conversations sont sauvegardées.', - manualTitle: 'Mise à jour manuelle', - docker: 'Après la fin des agents, exécutez ceci dans le dossier Compose :', - manual: 'Consultez le guide pour mettre à jour cette installation.', - legacy: 'Une première mise à jour manuelle est nécessaire pour activer ce bouton.', - guide: 'Guide de mise à jour' - }, - desktopUpdate: { - heading: 'Application de bureau', - progress: 'Téléchargement de la mise à jour de l’application', - ready: 'Téléchargée, prête à installer', - installing: 'Installation, Boite redémarre là-bas', - current: 'À jour', - elsewhere: 'Installez cette mise à jour sur la machine elle-même : son installateur peut demander une confirmation.', - confirmTitle: 'Mettre à jour Boite sur {machine} ?', - confirmBody: 'La version {version} s’installe quand les agents de cette machine ont fini. Boite redémarre sur {machine} et le travail en cours reprend.', - requested: 'Une autre machine installe Boite {version} ici. Boite redémarre dans un instant.' - }, - appUpdate: { - released: 'Sortie {time}', - changelog: 'Changelog sur GitHub', - heading: "Mises à jour de l'application", - intro: 'Choisissez les versions suivies par cette application de bureau. Boite et Boite Nightly partagent cette installation, vos conversations et vos réglages.', - unsupported: 'Les mises à jour automatiques sont disponibles dans les versions installées : l’installateur Windows x64, l’app macOS et le .deb ou l’AppImage Linux.', - channel: 'Canal de publication', - stable: 'Boite', - nightly: 'Boite Nightly', - nightlyHint: "Boite Nightly est expérimentale et peut contenir des changements inachevés. Vous pouvez revenir à Boite même si son numéro de version est inférieur.", - installedVersion: 'Version installée', - idle: "Aucune recherche de mise à jour n'a encore eu lieu.", - checking: 'Recherche de mises à jour', - available: 'La version {version} est disponible.', - downloading: 'Téléchargement de {received} sur {total}', - downloaded: '{received} téléchargés', - ready: 'La version {version} est prête à installer.', - waiting: 'En attente de la fin du travail', - installing: "Les agents terminent leur étape, puis l'installation démarre", - current: 'Boite est à jour.', - failed: 'La mise à jour a échoué.', - lastChecked: 'Vérifié à {time}', - progress: "Téléchargement de la mise à jour de l'application", - notes: 'Notes de version', - retained: 'Changer de canal conserve vos conversations et vos réglages.', - check: 'Rechercher des mises à jour', - checkingAction: 'Recherche en cours', - install: 'Installer et redémarrer', - readyAction: 'Mise à jour', - installTitle: 'Installer la mise à jour de Boite ?', - installBody: "Les agents terminent leur appel d'outil en cours, 30 secondes au plus. Boite se met ensuite à jour, redémarre et les reprend.", - }, - updateAll: { - heading: 'Mises à jour', - trigger: 'Mises à jour disponibles', - busy: 'Mise à jour en cours', - run: 'Tout mettre à jour', - app: "Mettre à jour l'app", - retry: 'Réessayer', - later: 'Me le rappeler plus tard', - restarts: 'Boite télécharge sa version, puis demande à redémarrer. Les conversations reprennent où elles en étaient.', - summary: (count: number) => count === 1 ? '1 mise à jour' : `${count} mises à jour`, - downloading: (percent: number | null) => percent === null ? 'Téléchargement' : `Téléchargement ${percent} %`, - ready: 'Redémarre pour finir', - restartsNext: 'Redémarre une fois les agents à jour', - waiting: 'Attend la fin du travail', - installing: 'Redémarrage', - updating: 'Mise à jour', - waitingFor: (count: number) => count === 1 ? "Attend la pause d'une conversation" : `Attend la pause de ${count} conversations`, - failed: 'Échec', - done: 'À jour', - allDone: 'Tout est à jour', - finish: 'Boite est prête : redémarre pour finir.', - restart: 'Redémarrer' - }, - - connection: { - machine: 'Machine', - local: 'Ce PC', - current: 'Machine actuelle', - machines: 'machines connectées', - issues: 'à vérifier', - manage: 'Gérer les machines', - folder: 'Dossier', - browse: 'Parcourir les dossiers', - nativeBrowse: "Choisir dans l'explorateur Windows", - parent: 'Dossier parent', - emptyFolder: 'Aucun sous-dossier', - unavailable: 'Connexion impossible', - idle: 'Non connecté', - connecting: 'Connexion', - ready: 'Connecté', - closed: 'Déconnecté', - offline: 'Hors ligne', - label: 'Connexion' - }, - - threadStatus: { - idle: 'au repos', - queued: 'en attente', - running: 'en cours', - waiting: 'vous attend', - error: 'erreur' - }, - - drafts: { - name: 'Brouillons', - hint: 'Un dossier par conversation, dans Documents/Boite', - openFolder: 'Travailler dans un de mes dossiers', - pickFolder: 'Ouvrir un dossier', - incognito: 'Incognito', - incognitoStart: "Démarrer cette conversation en incognito : rien n'en reste une fois que vous la quittez", - incognitoStop: 'Démarrer plutôt une conversation normale', - incognitoOn: 'Incognito : cette conversation et son dossier sont effacés quand vous la quittez' - }, - firstRun: { - heading: 'Ouvrir un projet', - deviceBody: "Les projets s'ouvrent depuis l'application où tourne le cœur. Ceux qu'il détient apparaissent ici.", - pathPlaceholder: 'Chemin absolu du dossier', - }, - - connect: { - button: 'Connecter une IA', - title: 'Connecter une IA', - intro: "Boite fonctionne avec un abonnement IA que vous avez déjà. Choisissez-le, Boite installe ce qu'il faut et ouvre sa page de connexion.", - more: 'Autres agents', - plan: { - claude: 'Avec un forfait Claude Pro ou Max', - codex: 'Avec un forfait ChatGPT' - }, - otherPlan: 'Avec votre compte {provider}', - install: 'Installer {provider}', - installNote: "Télécharge {size}. Rien d'autre ne change sur cet ordinateur.", - signIn: 'Se connecter à {provider}', - signInNote: 'Votre navigateur ouvre la page de connexion. Revenez ici une fois connecté.', - manual: "{provider} s'installe avec son propre installateur. Lancez-le, puis vérifiez à nouveau ici.", - manualOpen: "Ouvrir la page d'installation", - external: '{provider} se connecte depuis sa propre fenêtre. Connectez-vous là-bas, puis vérifiez à nouveau.', - checkAgain: 'Vérifier à nouveau', - ready: '{provider} est connecté.', - use: 'Utiliser {provider}', - back: 'Retour', - device: "Aucune IA n'est encore connectée sur l'ordinateur qui fait tourner Boite. Connectez-en une depuis cet ordinateur.", - reconnect: 'Se reconnecter', - signedOut: "{provider} n'est plus connecté" - }, - - drop: { - title: 'Déposez pour ajouter un projet', - body: 'Chaque dossier devient un projet. Les fichiers vont dans le message.', - attachTitle: 'Déposez pour joindre', - attachBody: 'Les fichiers vont dans le message en cours.', - noComposer: 'Ouvrez une conversation pour joindre les fichiers déposés.' - }, - - sidebar: { - doneThreads: 'Terminés', - archivedGroup: 'Archivés', - workingThreads: 'En cours', - projectWorkingThreads: '{project} : {count} en cours', - projectDoneThreads: '{project} : {count} terminées', - projectArchivedThreads: '{project} : {count} archivées', - idleProjects: 'Projets inactifs', - otherProjects: 'Autres projets', - archivedProject: 'Archivé', - groupingOptions: "Options d'affichage", - surface: 'Conversations ou agents', - threads: 'Conversations', - agents: 'Agents', - surfaceWorking: '{count} au travail', - surfaceWaiting: '{count} au travail, quelque chose vous attend', - surfaceSubagentsOne: 'dont 1 sous-agent', - surfaceSubagentsMany: 'dont {count} sous-agents', - projectsView: 'Projets', - recentView: 'Récent', - displayView: 'Affichage', - displayOrder: 'Ordre des projets', - displayGroup: 'Regrouper', - groupWorkingRow: 'Conversations en cours', - groupIdleRow: 'Projets inactifs', - customOrderShort: 'Personnel', - autoArchiveMergedPrHint: 'Une conversation en worktree quitte la liste pour Terminés une fois sa branche fusionnée. Son historique reste. S’applique à ce projet.', - autoRemoveWorktreesHint: 'Une heure après que toutes les conversations d’un worktree sont terminées ou archivées, Boite supprime le worktree si rien ne se perd : son dernier commit est sur une autre branche, ou une pull request fusionnée a exactement ce commit. Une branche fusionnée part aussi. Les modifications non commitées restent toujours. S’applique à ce projet.', - noActiveProjects: 'Aucune conversation à traiter.', - markDone: 'Marquer comme terminé', - doneToast: '« {title} » marqué comme terminé', - doneLoadFailed: "Certaines conversations terminées n'ont pas pu être chargées.", - prMerged: '#{number} fusionnée', - prClosed: '#{number} fermée', - prOpen: '#{number} ouverte', - reopenThread: 'Remettre dans Récent', - reopenNamedThread: 'Remettre « {title} » dans Récent', - doneNotice: 'Cette conversation est terminée. Remettez-la dans Récent pour continuer.', - worktreeGone: { - title: 'Worktree supprimé', - merged: 'Boite a supprimé le worktree de cette conversation {when}, après la fusion de #{number}.', - clean: 'Boite a supprimé le worktree de cette conversation {when} : chacun de ses commits était déjà sur une autre branche.', - branchDeleted: 'La branche {branch} a été supprimée.', - branchKept: 'La branche {branch} existe toujours.', - archived: 'Remise dans Récent, cette conversation n’a plus de dossier où travailler.', - live: 'Un nouveau message ici n’a pas de dossier où s’exécuter. Démarrez une nouvelle conversation pour continuer.' - }, - allProjects: 'Tous les projets', - filterProject: 'Filtrer par projet', - recentOrder: 'Activité récente', - customOrder: 'Ordre personnel', - moveProjectUp: 'Monter le projet', - moveProjectDown: 'Descendre le projet', - search: 'Rechercher une conversation', - newThread: 'Nouvelle conversation', - newThreadIn: 'Nouvelle conversation dans {project}', - addProject: 'Ajouter un projet', - noProjects: 'Aucun projet pour le moment.', - noThreads: 'Aucune conversation pour le moment.', - noMatch: 'Rien ne correspond.', - unread: 'Non lu', - collapse: 'Masquer la barre latérale', - expand: 'Afficher la barre latérale', - resize: 'Redimensionner la barre latérale, double-clic pour réinitialiser', - settings: 'Réglages', - projectMenu: 'Actions du projet', - copyPath: 'Copier le chemin', - manageProject: 'Gérer le projet', - worktreeDefault: 'Worktree par défaut', - autoArchiveMergedPr: 'Masquer les conversations des PR mergées', - autoRemoveWorktrees: 'Supprimer les worktrees terminés', - backToProjectMenu: 'Retour', - viewArchivedThreads: 'Voir les conversations archivées', - importSession: 'Importer une session Claude Code', - archiveProject: 'Archiver le projet', - refreshIcon: "Rafraîchir l'icône", - projectArchivedToast: '{project} archivé', - archivedProjects: 'Projets archivés', - restoreProject: 'Restaurer', - restoreAndStart: 'Restaurer {project} et démarrer une conversation', - removeProject: 'Retirer de Boite', - projectMissing: 'Dossier introuvable', - projectMissingBody: "Le dossier {path} n'existe plus : il a été supprimé, déplacé ou renommé. Remettez-le en place pour continuer ici, ou retirez le projet de Boite.", - projectMissingCheck: 'Vérifier à nouveau', - removeProjectTitle: 'Retirer {project} de Boite ?', - removeProjectBody: 'Ses conversations partent avec lui. Les fichiers sur le disque restent où ils sont.', - remove: 'Retirer', - threadMenu: 'Actions de la conversation', - unsentDraft: 'Brouillon non envoyé', - retitle: 'Regénérer le titre', - retitling: "Rédaction d'un titre", - state: { - working: 'En cours', - workingFor: 'Travaille depuis {elapsed}', - waiting: 'Vous attend', - error: 'Échec', - done: 'Terminé', - queued: 'En attente', - delegating: 'Sous-agents', - delegatingOne: 'Attend 1 sous-agent', - delegatingMany: 'Attend {count} sous-agents', - subagentsOne: '1 sous-agent au travail', - subagentsMany: '{count} sous-agents au travail', - subagentsShortOne: '1 agent', - subagentsShortMany: '{count} agents', - subagentsAskingOne: '1 sous-agent vous attend', - subagentsAskingMany: '{count} sous-agents vous attendent', - monitoring: 'Surveille', - monitoringFor: 'Surveille depuis {elapsed}', - background: 'En arrière-plan', - backgroundFor: 'En arrière-plan depuis {elapsed}', - watching: 'Surveille une PR', - watchingMany: 'Surveille {count} PR', - awaiting: 'Attend : {what}', - interrupted: 'Interrompu', - unreachable: 'Injoignable', - unreachableWas: 'Machine injoignable. Dernier état connu : {state}', - interruptedBy: '{what} s’est arrêté sans le réveiller : {reason}', - interruption: { - 'error': 'la tâche a échoué', - 'cancelled': 'la tâche a été annulée', - 'no-longer-reported': 'son processus s’est arrêté', - 'session-ended': 'le processus de l’agent a été arrêté', - 'core-restarted': 'Boite ou le PC a redémarré' - } - }, - rollupOne: '1 conversation : {state}', - rollupMany: '{count} conversations : {state}', - pin: 'Épingler', - unpin: 'Désépingler', - archive: 'Archiver', - deleteUnavailable: 'Mettez à jour Boite sur la machine qui héberge cette conversation pour la supprimer. La conversation a été conservée.', - deletedToast: '« {title} » supprimée. Restauration possible dans les paramètres.', - archivedToast: '« {title} » archivée', - undo: 'Annuler', - undoHint: 'Annuler (Ctrl+Z)', - draft: 'Nouvelle conversation', - deleteDraft: 'Supprimer le brouillon' - }, - - threadMove: { - moveTo: 'Déplacer vers un projet', - archivedProject: '{project} (archivé)', - noOtherProject: 'Aucun autre projet', - stopFirst: 'Une sous-conversation travaille ; arrêtez-la avant de déplacer cette conversation', - runningTitle: 'Déplacer à la fin de ce tour', - runningBody: "L'agent travaille dans {folder}. La conversation passe dans {project} quand le tour en cours se termine.", - runningConfirm: 'Déplacer après ce tour', - runningBackgroundBody: "L'agent travaille dans {folder}. La conversation passe dans {project} quand le tour en cours se termine. Quelque chose qu'il a lancé tourne encore en arrière-plan : arrêtez-le à ce moment-là, ou laissez-le tourner là-bas jusqu'à ce que l'agent commence son prochain tour dans {project}.", - pending: 'Passe dans {project} après ce tour', - cancelMove: 'Annuler le déplacement', - monitorsTitle: 'Des moniteurs tournent encore', - backgroundTitle: 'Du travail tourne encore en arrière-plan', - backgroundBody: "L'agent fait encore tourner quelque chose dans {folder}. Arrêtez-le maintenant, ou laissez-le tourner là-bas jusqu'à ce que l'agent commence son prochain tour dans {project}.", - stopMonitors: 'Arrêter les moniteurs', - stopWork: "L'arrêter", - keep: 'Les garder', - dropHere: 'Déplacer la conversation vers {project}', - dragHint: 'Déposer sur un autre projet' - }, - - palette: { - placeholder: 'Cherchez une conversation, ou tapez une commande', - terminal: 'Afficher ou masquer le terminal', - threads: 'Conversations', - commands: 'Commandes', - empty: 'Rien ne correspond.', - hint: 'Entrée pour ouvrir, Échap pour fermer', - newThread: 'Nouvelle conversation', - addProject: 'Ajouter un projet', - importSession: 'Importer une session Claude Code', - settings: 'Ouvrir les réglages', - appearance: "Réglages d'apparence", - providers: 'Fournisseurs et comptes', - pair: 'Ajouter un appareil', - sidebar: 'Afficher ou masquer la barre latérale', - panel: 'Afficher ou masquer le panneau', - trace: 'Afficher la trace', - changes: 'Afficher les modifications', - files: 'Afficher les fichiers', - tasks: 'Afficher les tâches', - subagents: 'Afficher les sous-agents', - agents: 'Ouvrir les agents', - pin: 'Épingler cette conversation', - unpin: 'Désépingler cette conversation', - rename: 'Renommer cette conversation', - retitle: 'Regénérer le titre de cette conversation', - archive: 'Archiver cette conversation', - themeDark: 'Thème : sombre', - themeLight: 'Thème : clair', - themeSystem: 'Thème : système' - }, - - imports: { - title: 'Importer une session dans {project}', - intro: "Sessions Claude Code lancées dans ce dossier. L'une d'elles devient une conversation avec son historique, et le tour suivant la continue.", - loading: 'Recherche des sessions', - empty: 'Aucune session Claude Code pour ce dossier.', - imported: 'Importée', - importing: 'Import en cours', - }, - - agentShape: { - open: "Ce que voit l'agent", - title: "Ce que voit l'agent", - intro: 'Le prompt système et les outils de la dernière requête, tels que Claude Code les a notés.', - loading: 'Lecture du transcript', - failed: 'Lecture du transcript impossible : {error}', - noSession: 'Aucune requête pour l\'instant : envoyez un premier message.', - noTranscript: "Le transcript de la session n'est plus sur cette machine.", - unrecorded: 'Cette version de Claude Code ne note pas le détail des requêtes. Passez à la 2.1.300 ou plus récente.', - system: 'Prompt système', - tools: 'Outils', - block: 'Bloc {n}', - truncated: 'Coupé ici ; le bloc fait {chars} caractères.', - description: 'description {chars}', - omitted: '{n} de plus, comptés dans le total', - estimate: 'environ {tokens} tokens', - estimateHint: 'Caractères divisés par quatre : une estimation, pas le compte du fournisseur.', - chars: '{n} car.', - model: 'Modèle', - effort: 'Effort', - thinking: 'Réflexion', - maxTokens: 'Sortie max', - betas: 'Bêtas', - version: 'Claude Code', - sent: 'Envoyée', - }, - - media: { - sourceTooLarge: '{name} pèse {size} : Boite réduit les images de {max} au plus, elle a donc été laissée de côté.', - viewer: 'Visionneuse', - previous: 'Précédente', - next: 'Suivante', - position: '{index} sur {total}', - share: 'Partager ou enregistrer', - shareFailed: 'Impossible de partager {name}.', - shareReady: '{name} est prêt : touchez Partager à nouveau.', - answerFiles: 'Fichiers joints : {names}', - answerFileCount: '{count} fichiers' - }, - btw: { - fork: 'Fork', forking: 'Création du fork', - title: 'Question de côté', description: "Poser une question de côté sans interrompre l'agent", - loading: 'Réponse à votre question en cours', close: 'Fermer la réponse de côté', - needsThread: 'Ouvrez une conversation avant de poser une question de côté.', - questionRequired: 'Écrivez une question après /btw.', - textOnly: 'Les questions de côté acceptent seulement du texte. Retirez les pièces jointes et les références de prévisualisation.', - editing: 'Terminez ou annulez la modification du message avant de poser une question de côté.', - offline: 'Reconnectez cette machine avant de poser une question de côté.' - }, - - slash: { - label: 'Commandes', - agent: 'Agent', - app: 'Boite', - empty: 'Aucune commande ne correspond', - model: 'Changer de modèle', - effort: 'Changer le niveau de raisonnement', - mode: 'Changer le mode de permissions' - }, - - activity: { - goal: 'Objectif', - question: 'Question', - questionOf: '{index} sur {total}', - questionPrev: 'Question précédente', - questionNext: 'Question suivante', - questionFold: 'Replier la question', - questionUnfold: 'Déplier la question', - subagentAsks: 'Demandé par {title}', - subagentPermission: '{title} attend votre autorisation', - openSubagent: 'Ouvrir', - openSubagentHint: 'Ouvrir {title}', - loop: 'Boucle', - tasks: 'Tâches', - taskCount: '{done}/{total} tâches', - goalDescription: 'Continuer à travailler vers un objectif', - loopDescription: 'Répéter une tâche un nombre de fois ou selon un rythme', - goalUsage: 'Utilisez /goal suivi d\'un objectif.', - goal2: 'Objectif relu', - goal2Description: "Travailler vers un objectif jusqu'à ce qu'un relecteur neuf le valide", - goal2Usage: "Utilisez /goal2 suivi d'un objectif.", - reviewing: 'En relecture', - reviews: 'Relectures', - reviewRound: 'Relecture {round}', - reviewRoundOf: 'Relecture {round} sur {total}', - reviewInProgress: 'Relecture {round} en cours', - openReview: 'Ouvrir la conversation du relecteur', - pass: 'Validé', - partial: 'Partiel', - fail: 'Refusé', - missing: 'Sans verdict', - loopUsage: 'Utilisez /loop 2 prompt pour deux itérations, ou /loop 5m prompt pour un rythme. Entre 1 et 1000 itérations.', - intervalError: "L'intervalle d'une boucle doit tenir entre 1 seconde et 24 heures.", - active: 'Active', - paused: 'En pause', - complete: 'Terminée', - blocked: 'Attend votre réponse', - blockedHint: "Votre prochain message relance l'objectif.", - reply: 'Répondre', - pause: 'Mettre en pause', - resume: 'Reprendre', - remove: 'Retirer', - finish: 'Marquer terminée', - every: 'Toutes les {interval}', - iterations: '{count} exécutions', - iteration: 'Itération {count}', - iterationOf: 'Itération {count} sur {total}', - history: 'Historique des itérations', - noHistory: 'Aucune itération terminée pour le moment', - allTasksDone: 'Toutes les tâches sont terminées', - waitingTasks: 'En attente de la prochaine tâche', - stopped: 'Arrêtée', - running: 'En cours', - done: 'Terminée', - error: 'Échec', - pending: 'À faire', - in_progress: 'En cours', - completed: 'Terminée' - }, - - mention: { - label: 'Fichiers', - empty: 'Aucun fichier ne correspond', - more: '{count} de plus, continuez à taper' - }, - - notify: { - done: 'Terminé', - failed: 'Échec', - openThread: 'Ouvrir la conversation', - needsYou: 'Attend votre réponse', - connected: 'Les notifications fonctionnent' - }, - - permissionMode: { - default: 'Demander', - acceptEdits: 'Modifier librement', - bypassPermissions: 'Auto', - yolo: 'YOLO', - plan: 'Plan', - dontAsk: 'Tout refuser' - }, - - permissionModeLong: { - default: 'Lit librement, demande avant de modifier un fichier ou de lancer une commande', - acceptEdits: 'Modifie les fichiers sans demander, demande avant de lancer une commande', - bypassPermissions: 'Exécute tout sans demander, partout sur cet ordinateur', - yolo: 'Autorise tous les outils ; ignore les hooks Claude et Codex', - plan: 'Plan seulement, aucune modification', - dontAsk: 'Refuser tout ce qui demanderait une autorisation' - }, - - permissionModeCodex: { - default: 'Ce dossier', - defaultLong: 'Modifie et lance des commandes dans ce dossier sans demander, demande pour aller au-delà' - }, - - permissionModeAgy: { - default: 'Réglages agy', - defaultLong: 'Suit le réglage de permissions d\'agy et refuse ce qu\'il demanderait', - acceptEditsLong: 'Modifie les fichiers sans demander, refuse les commandes qui demandent une autorisation', - draft: 'avec les réglages de permissions d\'agy' - }, - - thread: { - recoveryTitle: 'Message conservé avant le redémarrage', - recoveryBody: "Ce message n'a pas été envoyé à l'agent. Reprenez-le avec son modèle et son compte enregistrés, ou retirez-le de la file.", - recoveryTarget: 'Sélection enregistrée : {model} · {account}', - recoveryResume: 'Reprendre le message', - recoveryDiscard: 'Retirer de la file', - retryCapacityTitle: 'Modèle saturé', - retryCapacityBody: 'Nouvel essai dans {time}, essai {attempt} sur {attempts}.', - retryCapacityCancel: 'Arrêter les essais', - retryLimitTitle: 'Limite d\'usage atteinte', - retryLimitBody: 'Reprise à {time}, une fois la limite réinitialisée, dans {in}.', - retryLimitCancel: 'Ne pas reprendre', - retryContinueTitle: 'Arrêté avant sa réponse', - retryContinueBody: 'Reprise automatique dans {time}, reprise {attempt} sur {attempts}.', - contextDetails: 'Contexte', - contextInput: 'Entrée', - contextCache: 'Entrée en cache', - contextOutput: 'Sortie', - contextUsed: 'Utilisé', - contextFree: 'Disponible', - contextNoBreakdown: 'Cet agent ne détaille pas sa consommation.', - contextNoReading: "Aucune mesure reçue de cet agent pour l'instant.", - contextMeasured: 'Dernière mesure', - cacheTitle: 'Cache de prompt', - cacheChipWarm: (left: string) => `Cache de prompt chaud, encore environ ${left}`, - cacheChipMaybe: (most: string) => `Cache de prompt peut-être encore chaud, ${most} au plus`, - cacheChipCold: 'Cache de prompt froid', - cacheCold: 'froid', - cacheLeft: (left: string) => `encore ${left}`, - cacheMaybe: (left: string) => `peut-être · ${left}`, - cacheSwitched: 'Modèle ou compte changé.', - cacheLifetime: 'Durée de vie', - cacheSourceReported: 'API', - cacheSourceDocumented: 'doc', - cacheRange: (lifetime: string, most: string) => `${lifetime} à ${most}`, - cacheReadLast: 'Lu au dernier tour', - minutes: (n: number) => `${n} min`, - hours: (n: number) => `${n} h`, - none: 'Aucune conversation ouverte', - noneBody: 'Choisissez-en une à gauche, ou lancez-en une nouvelle.', - trace: 'Trace', - panel: 'Panneau', - panelHint: 'Navigateur, fichiers et outils de cette conversation', - terminalHint: 'Terminal dans le dossier de cette conversation', - cwd: 'Répertoire de travail', - model: 'Modèle', - defaultModel: 'Modèle par défaut', - renamePlaceholder: 'Titre de la conversation', - start: 'Lancer une conversation', - inWorktree: 'dans un worktree', - inProject: 'dans', - using: 'avec', - onEffort: 'en raisonnement {effort}', - draftMode: { - default: 'avec demandes d\'autorisation', - acceptEdits: 'avec les modifications autorisées', - bypassPermissions: 'avec toutes les permissions', - yolo: 'sans demandes de permission pour les outils', - plan: 'en mode plan', - dontAsk: 'avec refus automatique des demandes d\'autorisation' - }, - changeProject: 'Changer de projet', - searchProjects: 'Rechercher un projet', - noProjectMatch: 'Aucun projet ne correspond à « {query} »', - branchHint: 'Travaille dans un worktree sur cette branche', - }, - - chat: { - accepted: 'Requête acceptée', - responseStarted: 'Activité reçue', - working: 'Travaille', - writing: 'Écrit', - stopped: 'Arrêté', - inputTokens: '{count} jetons en entrée', - outputTokens: '{count} jetons en sortie', - cacheTokens: '{read} lus depuis le cache, {write} écrits dedans', - workingFor: 'Travaille depuis {time}', - workedFor: 'A travaillé {time}', - compacting: 'Compaction en cours', - compactionElapsed: 'Temps écoulé : {time}', - turnOwnTime: 'Cette réponse seule : {time}', - compactedFor: 'Contexte compacté en {time}', - compactionFailed: 'Échec de la compaction', - compactionStopped: 'Compaction arrêtée', - progress: { starting: 'Démarrage de l\'agent', thinking: 'Réflexion en cours', compacting: 'Compaction du contexte', retrying: 'Nouvelle tentative', tool: 'Outil en cours', working: 'Activité reçue', waiting: 'En attente du fournisseur' }, - providerSignal: 'Signal du fournisseur il y a {time}', - noActivity: 'Aucune nouvelle activité depuis {time}', - newMessage: '1 nouveau message', - newMessages: '{count} nouveaux messages', - findPlaceholder: 'Chercher dans la conversation', - findNone: 'Aucun résultat', - findCount: '{at} sur {total}', - findPrevious: 'Résultat précédent (Maj+Entrée)', - findNext: 'Résultat suivant (Entrée)', - finishedAt: 'fini à {time}', - backgroundRunning: '{what} encore en cours', - forkOrigin: 'Dérivé de', forkReturn: 'Renvoyer les conclusions', forkSummary: "Conclusions pour la conversation d'origine", - forkReturned: "Conclusions envoyées à la conversation d'origine", forkSourceUnavailable: "Conversation d'origine indisponible", - forkNative: 'Contexte natif conservé', forkSeeded: 'Historique visible copié', - backgroundJoin: ', ', - backgroundOne: { shell: '{count} shell', agent: '{count} agent', monitor: '{count} moniteur', workflow: '{count} workflow', other: '{count} tâche' }, - backgroundMany: { shell: '{count} shells', agent: '{count} agents', monitor: '{count} moniteurs', workflow: '{count} workflows', other: '{count} tâches' }, - backgroundStop: 'Arrêter l’agent et ce qu’il exécute en arrière-plan', - backgroundChip: 'en arrière-plan', - toolLive: { command: 'Exécute {subject}', read: 'Lit {subject}', edit: 'Modifie {subject}', write: 'Écrit {subject}', search: 'Cherche {subject}', fetch: 'Récupère {subject}', web: 'Cherche sur le web {subject}', agent: 'Délègue {subject}', other: 'Utilise {subject}' }, - toolDone: { command: 'A exécuté {subject}', read: 'A lu {subject}', edit: 'A modifié {subject}', write: 'A écrit {subject}', search: 'A cherché {subject}', fetch: 'A récupéré {subject}', web: 'A cherché sur le web {subject}', agent: 'A délégué {subject}', other: 'A utilisé {subject}' }, - toolBare: { command: 'A exécuté une commande', read: 'A lu un fichier', edit: 'A modifié un fichier', write: 'A écrit un fichier', search: 'A cherché dans le code', fetch: 'A récupéré une page', web: 'A cherché sur le web', agent: 'A délégué une tâche', other: 'A utilisé un outil' }, - toolCommandWord: 'une commande', - toolRunOne: { command: '{count} commande exécutée', read: '{count} fichier lu', edit: '{count} fichier modifié', search: 'Une recherche dans le code', fetch: '{count} page récupérée', web: 'Une recherche sur le web', agent: '{count} tâche déléguée', other: '{count} outil utilisé', attempt: 'Tentative de {count} appel' }, - toolRunMany: { command: '{count} commandes exécutées', read: '{count} fichiers lus', edit: '{count} fichiers modifiés', search: '{count} recherches dans le code', fetch: '{count} pages récupérées', web: '{count} recherches sur le web', agent: '{count} tâches déléguées', other: '{count} outils utilisés', attempt: 'Tentative de {count} appels' }, - toolRunJoin: ', ', - toolRunLast: ' et ', - toolRunShow: 'Afficher chaque appel', - toolRunHide: 'Masquer les appels', - toolIssueOne: '{count} appel non abouti', - toolIssueMany: '{count} appels non aboutis', - system: 'Système', - toolInput: 'Entrée', - toolOutput: 'Sortie', - toolDocuments: 'Documents', - textOmitted: 'Ce message est trop long pour être affiché en entier. {count} caractères ne sont pas affichés.', - documentChip: { - diff: '{count} diff', - diffs: '{count} diffs', - doc: '{count} doc', - docs: '{count} docs' - }, - documentImage: "Ce que l'outil a produit", - imagePart: 'Image envoyée avec le message', - imageUnavailable: "Cette image n'a pas pu être chargée. Revenez dessus pour réessayer.", - diffHidden: '{count} lignes inchangées', - diffIgnoreWhitespace: 'Ignorer les changements d’espaces', - diffSideBySide: 'Ancien et nouveau côte à côte', - diffWhitespaceOnly: 'Seuls des espaces ont changé.', - diffShowAll: 'Afficher les {count} lignes', - diffAdded: '+{count}', - diffRemoved: '-{count}', - noOutput: 'Aucune sortie', - toolExitCode: 'Code de sortie {code}', - toolCommandFailed: 'Aucun diagnostic signalé. Consultez la sortie complète ci-dessous.', - showAll: 'Tout afficher', - toolStatus: { - running: 'en cours', - done: 'terminé', - error: 'erreur', - denied: 'refusé' - }, - thinking: 'Réflexion', - thinkingShow: 'Afficher le raisonnement', - thinkingHide: 'Masquer le raisonnement', - compaction: 'Contexte compacté, de {pre} à {post} jetons', - compactionNoPost: 'Contexte compacté depuis {pre} jetons', - compactionUnknown: 'Contexte compacté', - compactionManual: 'à la main', - moveExplained: "Déplacement expliqué à l'agent", - moveShow: "Voir ce qui a été dit à l'agent", - moveHide: "Masquer ce qui a été dit à l'agent", - movePending: "Déplacé vers {project} · l'agent sera prévenu à votre prochain message", - movePendingShow: "Voir ce qui sera dit à l'agent", - movePendingHide: "Masquer ce qui sera dit à l'agent", - movedByAgent: "Déplacé par l'agent vers {project}", - keepalive: 'Cache de prompt gardé au chaud', - keepaliveMany: 'Cache de prompt gardé au chaud {count} fois', - keepaliveRunning: 'Cache de prompt en cours de relance', - startedByAgent: "Lancé par l'agent de {title} dans {project}", - promptFromAgent: "Envoyé par l'agent de", - agentStartedThread: "L'agent a lancé {title} dans {project}", - openLinkedThread: 'Ouvrir ce thread', - hookBlocked: 'Un hook a bloqué ce message', - hookStopped: 'Un hook a arrêté le tour', - hookSays: '{label} : {message}', - permissionAsk: { - command: 'Lancer une commande', - edit: 'Modifier {file}', - editFiles: 'Modifier des fichiers', - editFolder: 'Écrire dans {folder} à partir de maintenant', - write: 'Créer ou remplacer {file}', - read: 'Lire {file}', - search: 'Chercher {subject} dans les fichiers', - fetch: 'Ouvrir {subject}', - web: 'Chercher {subject} sur le web', - agent: 'Lancer un agent auxiliaire', - other: 'Utiliser son outil {tool}' - }, - permissionTechnical: 'Détails techniques', - turnFiles: 'Fichiers modifiés', - showTurnDiff: 'Voir le diff', - hideTurnDiff: 'Masquer le diff', - allChanges: 'Tous les changements du dossier', - expandFileFolders: 'Déplier tous les dossiers', - collapseFileFolders: 'Replier tous les dossiers', - fileCreated: 'Nouveau', - fileChanged: 'Modifié', - fileDeleted: 'Supprimé', - openFile: 'Ouvrir {name}', - revealFile: 'Afficher dans le dossier', - revealFailed: "Le dossier n'a pas pu s'ouvrir : {reason}", - allow: 'Autoriser', - deny: 'Refuser', - allowed: 'Autorisé', - denied: 'Refusé', - questionHeading: 'Vous demande', - questionAsyncHeading: 'Question', - questionAsyncHint: 'L\'agent continue. Sans réponse, il poursuit avec un choix par défaut.', - questionAnswer: 'Répondre', - questionAnswered: 'Répondu', - questionCancelled: 'Cette question n’attend plus de réponse', - questionSkip: 'Passer', - questionReplying: 'Écrivez votre réponse dans le champ de message en bas, avec photos ou fichiers si besoin.', - questionWrite: 'Répondre par écrit', - questionDocked: 'Attend votre réponse au-dessus du champ de message', - questionOpen: 'Y répondre', - streaming: 'écrit', - jumpToLatest: 'Aller au dernier message', - loadingOlder: 'Chargement des messages précédents', - loadingNewer: 'Chargement des messages suivants', - opening: 'Chargement de la conversation', - openingProgress: '{received} / {total}', - partLoading: 'Chargement de {size}', - outline: 'Messages de cette conversation', - messageGroup: 'Parcourir {count} messages', - earlierMessages: 'Charger les messages précédents', - outlineEarlier: 'Afficher les messages précédents dans le rail', - outlineLater: 'Afficher les messages suivants dans le rail', - goToMessage: 'Aller au message {number} : {text}', - copied: 'Copié', - plan: 'Plan', - planDownload: 'Télécharger le .md', - planSave: 'Enregistrer dans le dossier', - planSaveHint: 'Écrire le plan dans un fichier markdown à la racine du dossier de ce thread', - planSaved: 'Enregistré sous {name}', - copyMessage: 'Copier le message', - editMessage: 'Modifier et renvoyer depuis ici', - retry: 'Relancer cette réponse', - fork: 'Bifurquer depuis ici', - forkHere: 'Bifurquer dans une nouvelle conversation', - forkWorktree: 'Bifurquer dans un nouveau worktree', - sentAt: 'Envoyé {time}', - error: 'Erreur' - }, - - agentMessages: { - heading: 'Discussions', - open: 'Ouvrir la discussion', back: 'Toutes les discussions', openThread: 'Ouvrir cette conversation', - empty: 'Aucune discussion avec un autre agent pour le moment.', - sentReceived: '{sent} envoyés, {received} reçus', - mine: 'Cet agent', - you: 'Vous', - result: 'Résultat', - about: 'Travaille sur', - replyTo: 'En réponse à', - more: '+{count} autres', - sentOne: 'Envoyé à', sentMany: '{count} messages envoyés à', - receivedOne: 'Reçu de', receivedMany: '{count} messages reçus de', - exchanged: 'Échange avec', - threadMissing: "Aucune machine connectée n'a cette conversation.", - relation: { - starter: 'A lancé cette conversation', started: 'Lancée d\'ici', parent: 'Parent', child: 'Sous-agent', resident: 'Veille sur le projet' - } - }, - coordination: { - options: 'Réglages avancés', - heading: 'Communication entre conversations', off: 'Désactivée', on: 'Activée', - summaryOff: 'Les autres agents ne peuvent ni trouver, ni lire, ni contacter cette conversation.', - summaryOn: 'Les agents de cette machine et des machines liées peuvent trouver cette conversation, la lire et lui écrire.', - ownerOnly: 'Seul le propriétaire peut modifier la coordination. Cet appareil peut consulter les contacts et les échanges.', - resources: 'Ressources et responsabilités', resourcesPlaceholder: "Ce que cet agent gère, peut expliquer ou doit éviter", - remote: 'Entre projets et machines', remoteHint: 'Permettre aux agents des autres projets et des machines liées de trouver cette conversation, de la lire et de lui écrire. Les deux conversations doivent activer ce choix.', - pause: 'Suspendre la coordination', resume: 'Reprendre la coordination', paused: 'Suspendue', - sends: '{used} envois sur {limit} cette heure', wakes: '{used} réveils sur {limit} cette heure', receives: "Les destinataires acceptent jusqu'à {limit} messages par heure.", - sent: '{used} envoyés cette heure', woken: '{used} réveils cette heure', - directory: 'Contacts autorisés', directoryEmpty: "Aucune conversation volontaire n'est disponible dans ce projet.", unavailable: 'Contacts indisponibles', - details: "Afficher l'échange", - machineNotConnected: 'Connectez cette machine dans les réglages pour ouvrir son fil.', - status: { - queued: "En attente d'envoi", received: 'Reçu par le cœur destinataire', delivered: "Transmis à l'agent destinataire", - uncertain: 'Transmission incertaine. Boite ne la réessaiera pas.', expired: 'Expiré', rejected: 'Refusé' - }, - bubbleStatus: { - queued: 'En attente', received: 'En attente', delivered: 'Envoyé', uncertain: 'Non confirmé', expired: 'Expiré', rejected: 'Échec' - } - }, - resident: { - lookedAfterBy: 'Suivi par {name}' - }, - contextMode: { - light: 'Léger', - neutral: 'Neutre', - custom: 'Sur mesure', - rules: 'règles', - heading: 'Contexte {label}', - without: 'Démarré sans : {parts}.', - instructions: "fichiers d'instructions", - guides: 'guides Boite et brain', - mcp: 'serveurs MCP', - hooks: 'hooks', - tools: 'Outils : {tools}.', - withRules: 'Ses règles : {rules}' - }, - delegation: { - off: 'Désactivés', - paused: 'En pause', - heading: 'Sous-agents', - nativeHeading: 'Lancés par le fournisseur', - nativeNoResult: 'Aucun résultat reçu.', - processHeading: 'Lancés depuis une commande', - processResult: 'La progression et le résultat sont dans la sortie de la commande.', - nativeStatus: { running: 'En cours', done: 'Terminé', error: 'Échec', stopped: 'Arrêté', unknown: 'État inconnu' }, - parent: 'Fil principal', - activeOne: '1 sous-agent actif', - activeMany: '{count} sous-agents actifs', - configure: 'Réglages', - enabled: 'Autoriser cette conversation à lancer des sous-agents', - enabledHint: "L'agent peut confier des parties d'une tâche à d'autres agents. Chacun travaille dans sa propre conversation et rend compte ici.", - anyModel: "Laisser l'agent choisir le modèle", - anyModelHint: "Pour chaque tâche, l'agent choisit un modèle et son niveau de réflexion, par exemple Codex pour une revue de code. Désactivé, les sous-agents utilisent le modèle de cette conversation ou les modèles ci-dessous.", - profiles: 'Modèles suggérés', - profilesHint: "L'agent les voit par leur nom. Ajoutez-en un pour suggérer un modèle et un niveau de réflexion pour un type de tâche.", - empty: "Aucun sous-agent pour l'instant. Demandez à l'agent de répartir le travail : chaque sous-agent apparaît ici et sa conversation s'ouvre en un clic.", - addProfile: 'Ajouter un modèle', - profileName: 'Modèle {count}', - profileLabel: 'Modèle du sous-agent', - removeProfile: 'Retirer ce modèle', - pause: 'Mettre les sous-agents en pause', - resume: 'Reprendre les sous-agents', - backToTeam: 'Retour aux sous-agents', - stopAll: 'Tout arrêter', - send: 'Envoyer au sous-agent', - startedOne: '1 sous-agent lancé', - startedMany: '{count} sous-agents lancés', - completed: '{done}/{total} terminés', - failed: '{count} en erreur', - stopped: '{count} arrêtés', - elapsed: 'Temps écoulé', - showTeam: 'Voir les sous-agents', - stoppedStatus: 'Arrêté', - failedStatus: 'Échec', - waitingStatus: 'En attente', - interruptedStatus: 'Interrompu', - interrupted: '{count} interrompus', - resumeAgent: 'Reprendre', - resumeAgentHint: 'Demander à ce sous-agent de vérifier comment son travail en arrière-plan s’est terminé et de finir sa tâche', - resumeAgentText: 'Ton travail en arrière-plan ({what}) s’est arrêté sans te réveiller : {reason}. Vérifie comment il s’est terminé, puis finis ta tâche.', - activeSection: 'En cours', - finishedSection: 'Terminés' - }, - - workflow: { - activeOne: '1 workflow', - activeMany: '{count} workflows', - status: { running: 'En cours', paused: 'En pause', done: 'Terminé', failed: 'En échec', stopped: 'Arrêté' }, - stepStatus: { waiting: 'En attente', running: 'En cours', done: 'Terminée', failed: 'En échec', skipped: 'Sautée', stopped: 'Arrêtée' }, - steps: '{done}/{total} étapes', - phase: 'Phase {n}', - pause: 'Pause', - resume: 'Reprendre', - stop: 'Arrêter', - retry: 'Relancer', - stopTitle: 'Arrêter « {name} » ?', - stopBody: "Les étapes en cours s'arrêtent et les suivantes ne démarrent pas. Les étapes finies gardent leurs résultats.", - forEach: 'Une par élément de {path}', - after: 'Après {steps}', - condition: 'Seulement si {path}', - task: 'Tâche', - result: 'Résultat', - output: 'Sortie', - items: 'Exécutions', - openThread: 'Ouvrir la conversation', - back: 'Retour au graphe', - close: 'Fermer le détail', - notStarted: 'Pas encore démarrée.', - skipped: "Sautée : sa condition n'était pas remplie.", - card: 'Workflow', - show: 'Voir le workflow', - tokens: '{tokens} jetons' - }, - - composer: { - editing: 'Modification d’un message envoyé : l’envoi le remplace, avec tout ce qui suit', - editingQueued: 'Envoyez ou retirez les messages en attente avant de remplacer un message envoyé.', - filesUnavailable: "Les messages ont été rembobinés, mais cette modification n'a pas de sauvegarde complète des fichiers. Le code n'a pas pu être restauré.", - editingCancel: 'Arrêter la modification (Échap)', - replying: 'Réponse à : {question}', - replyPlaceholder: 'Écrivez votre réponse', - replySend: 'Envoyer la réponse', - replyIgnore: 'Écrire un message', - replyIgnoreHint: 'Laisser la question en attente et écrire un message normal', - switchTitle: 'Passer une conversation de {tokens} jetons à {provider} ?', - switchBody: "{provider} démarre une nouvelle session et reçoit des extraits du début et des échanges les plus récents, environ 20 000 jetons. Le reste lui est inaccessible. Compacter avant ne change rien.", - switchConfirm: "Changer d'agent", - switchCancel: 'Rester avec {provider}', - cacheTitle: "Reconstruire le cache d'une conversation de {tokens} jetons ?", - cacheBody: "Ce changement peut faire retraiter toute la conversation hors cache au prochain tour, ce qui consomme plus de quota. Changer de modèle ou d'effort de raisonnement peut affecter le cache, tout comme activer le mode Fast de Claude pour la première fois.", - cacheConfirm: 'Changer', - cacheCancel: 'Garder le réglage', - options: 'Options du message', - placeholder: 'Message à {provider} dans {project}', - placeholderNoProject: "Message à l'agent", - placeholderNew: 'Que voulez-vous faire ?', - placeholderOffline: '{machine} est hors ligne : les messages attendent ici et partent à sa reconnexion', - send: 'Envoyer', - stop: 'Arrêter', - queued: 'Envoyé après le prochain outil, ou à la fin du tour', - queuedPaused: 'Retenu après un envoi refusé', - sendNow: 'Envoyer maintenant', - queueWaitForEnd: 'Les objectifs et les boucles attendent la fin du tour en cours. Votre commande reste en attente.', - queueTurnNotReady: "Ce client n'a pas encore reçu le tour en cours. Votre message reste en attente.", - queueNotAccepted: "Ce tour n'a pas accepté le message. Il reste en attente et sera envoyé à la fin du tour.", - sendNowHint: "Transmet les messages à l'agent en cours sans l'arrêter. Entrée dans le champ vide fait de même.", - retryQueuedHint: 'Renvoie tous les messages en attente ensemble. Entrée dans le champ vide fait de même.', - outboxKept: "Gardés sur cet appareil, envoyés dans l'ordre, une seule fois chacun", - outboxWaiting: 'En attente de {machine}', - outboxNext: 'Part dès que le fil est libre', - outboxSending: 'Envoi…', - outboxFailed: 'Non envoyé : {reason}', - outboxRetry: 'Renvoyer', - outboxSettings: "Le modèle et l'effort choisis à l'écriture n'ont pas pu être rétablis.", - removeQueued: 'Retirer ce message en attente', - picker: 'Fournisseur et modèle', - models: 'Modèles', - searchModels: 'Rechercher un modèle', - noModels: 'Aucun modèle ne correspond', - noModelsAvailable: "L'agent n'a renvoyé aucun modèle. Actualisez pour réessayer.", - showAllModels: 'Afficher les {count} modèles', - legacyModels: 'Anciens modèles', - refreshModels: 'Actualiser les modèles', - probing: "Lecture des modèles de l'agent", - notInstalled: 'Pas installé sur cette machine', - installInSettings: 'Installer dans les réglages', - effortTitle: 'Niveau de raisonnement', - speed: 'Vitesse de réponse', - effortDefault: 'Par défaut', - effortDefaultHint: "Laisser l'agent choisir son propre niveau pour ce modèle", - standardSpeed: 'Vitesse normale', - favorites: 'Favoris', - favorite: 'Ajouter aux favoris', - unfavorite: 'Retirer des favoris', - favoritesEmpty: 'Mettez une étoile à un modèle pour le retrouver ici.', - favoriteUnavailable: "Ce favori n'est plus proposé par le compte. Choisissez un autre modèle.", - compact: 'Compacter le contexte', - compactBusy: 'Attendez la fin du tour en cours', - compactUnavailable: "Cet agent n'a pas annoncé de compactage du contexte", - compactNoSession: 'Envoyez un message avant de compacter', - newBadge: 'nouveau', - lockedHint: 'Une conversation garde son fournisseur et son compte', - mode: 'Permissions', - model: 'Modèle', - unavailable: 'pas installé', - noAccount: 'aucun compte', - noProvider: 'Aucune IA connectée', - worktree: 'Worktree', - worktreeOff: 'Démarrer dans un worktree git sur sa propre branche', - worktreeOn: 'Démarre dans un worktree git sur sa propre branche', - attach: 'Joindre des fichiers', - editQueued: 'Modifier ce message en attente, ou appuyez sur Haut dans un champ vide', - attachReadError: '{name} est illisible.', - attachRemove: 'Retirer {name}', - attachAlt: 'Image jointe', - imageReference: '[Image {number}]', - imagePreview: 'Aperçu de {image}', - imagePreviewClose: "Fermer l'aperçu de l'image", - attachPending: 'Recharger pour récupérer', - attachMore: 'Afficher {count} de plus', - attachLess: 'Afficher moins', - attachUnnamed: "l'image collée", - attachFormat: '{name} est en {type}, or une image doit être de type {formats}.', - attachTooLarge: '{name} est trop lourde : une image pèse {max} au maximum.', - attachTooMany: 'Un tour porte {max} images au maximum, donc {name} a été laissée de côté.', - attachTotalTooLarge: "Les fichiers d'un tour pèsent {max} au maximum à eux tous, donc {name} a été laissé de côté.", - attachNoImages: "{provider} n'accepte pas les images : envoyez le message sans elles." - }, - - rightPanel: { - resize: 'Redimensionner le panneau, double-clic pour réinitialiser', - maximize: 'Agrandir le panneau', - restore: 'Restaurer le panneau', - newSurface: 'Ouvrir une surface', - scrollLeft: 'Faire défiler les onglets vers la gauche', - scrollRight: 'Faire défiler les onglets vers la droite', - closeTab: 'Fermer {title}', - closeOthers: 'Fermer les autres', - closeToRight: 'Fermer à droite', - closeAll: 'Tout fermer', - trace: 'Trace', - browser: 'Navigateur', - changes: 'Modifications', - files: 'Fichiers', - file: 'Fichier', - tasks: 'Tâches', - device: 'Appareil', - ownerOnly: "Seulement dans l'application du propriétaire", - launcher: 'Ouvrir une surface dans ce panneau', - untitled: 'Navigateur' - }, - - changes: { - clean: 'Copie de travail propre', - noBranch: 'Aucune branche', - ahead: '{count} en avance', - behind: '{count} en retard', - count: '{count} modifiés', - oneCount: '1 modifié', - staged: 'Indexé', - stagedShort: 'I', - binary: 'Fichier binaire, rien à montrer ligne par ligne', - truncated: "Ce fichier a été tronqué : le diff ci-dessous n'est pas le fichier entier.", - pick: 'Choisissez un fichier pour voir son diff', - loading: 'Lecture de la copie de travail', - renamedFrom: 'Renommé depuis {path}', - backToList: 'Toutes les modifications', - previousFile: 'Fichier précédent', - nextFile: 'Fichier suivant', - position: '{index} sur {count}', - status: { - added: 'Ajouté', - modified: 'Modifié', - deleted: 'Supprimé', - renamed: 'Renommé', - copied: 'Copié', - untracked: 'Non suivi', - conflict: 'Conflit' - } - }, - - tasks: { - goalSection: 'Objectif et boucle', - noActivity: "Aucun objectif ni boucle sur cette conversation", - nextRun: 'Prochain passage {time}', - iterations: '{count} itérations', - agentSection: "Tâches de l'agent", - noTasks: "L'agent n'a signalé aucune liste de tâches", - todoSection: 'Todos du projet', - noTodos: 'Aucun todo sur ce projet', - addPlaceholder: 'Ajouter un todo', - add: 'Ajouter', - awaiting: 'Vous attend', - awaitingHint: 'Cette carte attend votre confirmation', - markOpen: 'Remettre à faire', - markClaimed: 'Marquer comme vous attendant', - markDone: 'Confirmer que c\'est fait', - removeTodo: 'Supprimer ce todo', - collapse: 'Replier cette section', - expand: 'Déplier cette section', - watchSection: 'PR surveillées', - noWatches: 'Aucune PR surveillée', - watchHint: 'Boite lit la PR sur GitHub environ une fois par minute et réveille l’agent quand un check échoue, que les checks passent, qu’une review ou un commentaire arrive ou que la branche entre en conflit', - watchNotRead: 'Pas encore lue', - watchNoChecks: 'Aucun check', - watchChecks: '{total} checks', - watchRunning: '{count} en cours', - watchFailed: '{count} en échec', - watchConflict: 'En conflit avec {base}', - watchRateLimited: 'Limite de requêtes GitHub, prochaine lecture {time}', - watchUnreachable: 'GitHub injoignable, prochaine lecture {time}', - stopWatch: 'Arrêter la surveillance', - stopWatchOf: 'Ne plus surveiller #{number}' - }, - - files: { - tree: 'Fichiers du répertoire de travail', - noRoot: 'Aucun répertoire de travail', - filter: 'Filtrer', - filterHint: 'Restreindre les lignes déjà chargées', - reading: 'Lecture de ce dossier', - empty: 'Ce dossier est vide', - noMatch: 'Aucune ligne chargée ne correspond', - failed: 'Ce dossier est illisible. {reason}', - capped: 'Seules les {count} premières entrées de ce dossier sont listées.', - readFailed: 'Ce fichier est illisible. {reason}', - writeFailed: "Ce fichier n'a pas pu être enregistré. {reason}", - readingFile: 'Lecture de ce fichier', - noFile: 'Cet onglet ne porte aucun chemin', - truncated: "Ce fichier a été coupé à sa limite de taille : ce qui suit n'est pas le fichier entier et ne peut pas être enregistré.", - readOnly: "Enregistrer un fichier appartient au propriétaire, celui-ci s'ouvre donc en lecture seule.", - saving: 'Enregistrement', - unsaved: 'Modifications non enregistrées', - discardOne: 'Fermer {name} sans enregistrer ?', - discardMany: 'Fermer {count} fichiers sans enregistrer ?', - discardBody: 'Le texte saisi depuis le dernier enregistrement sera perdu.', - discardConfirm: 'Fermer sans enregistrer', - editorLabel: 'Contenu du fichier', - zoomIn: 'Agrandir', - zoomOut: 'Réduire', - fit: 'Ajuster', - actual: '1:1', - natural: '{width} x {height}', - picture: "L'image de ce fichier", - download: 'Télécharger ce fichier', - binary: 'Rien ici ne se lit comme du texte.' - }, - - browserTools: { - title: 'Outils de test du navigateur', desktop: 'Ordinateur', laptop: 'Portable', portrait: 'Portrait', landscape: 'Paysage', - system: 'Apparence du système', light: 'Page claire', dark: 'Page sombre', diagnostics: 'Diagnostics et actions de la page', - startRecording: 'Enregistrer cet onglet', stopRecording: 'Arrêter la vidéo', reviewRecording: 'Vidéo enregistrée', - clear: 'Effacer', pageEvents: 'Événements de la page', actions: 'Actions', noEvents: 'Aucun événement capturé.', noActions: 'Aucune action enregistrée pour le moment.', - omitted: '{count} événements anciens ou trop volumineux omis.', recorded: '{seconds} secondes · {fps} i/s · {mb} Mo · {format} sans son', - recordingError: 'Enregistrement arrêté après une erreur.', recordingLimit: 'Enregistrement arrêté de lui-même à la limite de {mb} Mo. La vidéo se lit jusqu’à ce point.', - frameRate: 'Enregistrer à {rate} images par seconde', codec: 'Encoder en {codec}', codecUnavailable: 'Cet ordinateur ne sait pas l’encoder', - unplayable: 'Cette vue ne peut pas lire la vidéo {codec}. Téléchargez-la pour la regarder dans un autre lecteur.', - download: 'Télécharger la vidéo', discard: 'Supprimer la vidéo', - }, - browser: { - resetViewport: 'Adapter la page à la fenêtre', - enlarge: 'Agrandir le navigateur', - restore: 'Réduire le panneau', - detach: 'Afficher en popup dans l’application', - dock: 'Ramener dans le panneau', - move: 'Déplacer le panneau flottant', - loading: 'Chargement de la page', - failed: "La page n'a pas pu être ouverte. {reason}", - invalidUrl: 'Saisissez une adresse HTTP ou HTTPS valide, ou une recherche.', - resetZoom: 'Réinitialiser le zoom de la page', - back: 'Précédent', - forward: 'Suivant', - reload: 'Recharger', - urlPlaceholder: 'Rechercher ou saisir une URL', - openExternal: 'Ouvrir dans le navigateur du système', - slotEmpty: "La page s'affiche ici dès que la coque fournit la vue web", - slotLinux: "Le navigateur intégré n'est pas encore disponible dans l'app Linux. Ouvrez la page dans votre navigateur." - }, - - browserProfiles: { - card: 'Navigateur intégré', - heading: 'Profils du navigateur', - hint: "Chaque profil garde ses propres cookies, son stockage et ses connexions dans le navigateur intégré, y compris après un redémarrage. Un onglet privé ne garde rien une fois le dernier onglet privé fermé.", - default: 'Par défaut', - private: 'Privé', - profile: 'Profil du navigateur : {name}', - newTabIn: 'Nouvel onglet dans {name}', - newPrivateTab: 'Nouvel onglet privé', - manage: 'Gérer les profils du navigateur', - isDefault: 'Ouvre les nouveaux onglets', - makeDefault: 'Y ouvrir les nouveaux onglets', - renameLabel: 'Nouveau nom pour {name}', - addLabel: 'Nom du nouveau profil', - addPlaceholder: 'Pro, Perso…', - add: 'Ajouter un profil', - invalidName: "Choisissez un nom de 1 à 40 caractères qu'aucun autre profil n'utilise, autre que Default ou Private.", - tooMany: '{count} profils de navigateur au plus.', - deleteTitle: 'Supprimer le profil {name} ?', - deleteBody: 'Ses onglets ouverts se ferment, et ses cookies, son stockage et ses connexions sont effacés de cet ordinateur. Impossible de revenir en arrière.', - deleteFailed: "Le profil a été retiré, mais ses données n'ont pas pu être effacées : {reason}", - copySignIns: 'Copier les connexions vers les agents', - copySignInsHint: 'Envoyer les cookies de {name} au navigateur de l’agent d’une machine, dans le même profil', - copied: '{count} cookies de {name} copiés vers {machine}.', - copiedNone: '{name} n’a aucun cookie à copier.', - copyFailed: 'Les connexions n’ont pas pu être copiées : {reason}' - }, - - terminal: { - title: 'Terminal', - hide: 'Masquer le terminal', - close: 'Fermer le shell', - resize: 'Redimensionner le terminal', - tab: 'Terminal {n}', - tabs: 'Terminaux', - new: 'Nouveau terminal', - splitRight: 'Diviser à droite', - splitDown: 'Diviser en bas', - closeTab: "Fermer l'onglet", - resizePanes: 'Redimensionner la division', - find: 'Chercher dans le terminal', - history: 'Commandes récentes', - historySearch: 'Chercher une commande', - historyEmpty: "Aucune commande dans l'historique de ce shell pour l'instant", - historyNone: 'Aucune commande ne correspond', - historyOld: 'Cette machine a une version de Boite trop ancienne pour les lister', - historyLoading: "Lecture de l'historique", - historyHint: 'Entrée la lance, Maj+Entrée la met au prompt', - running: 'Une commande tourne', - failed: 'La dernière commande a échoué (code {code})' - }, - - trace: { - exact: 'Tous les processus enfants suivis', - limited: 'Suivi des processus limité', - finished: 'Terminé', - parent: 'PID parent', - cpuTime: 'Temps CPU', - currentMemory: 'Mémoire actuelle', - active: 'actifs', - recorded: 'enregistrés', - exe: 'Exécutable', - pid: 'PID', - duration: 'Durée', - cpu: 'CPU', - memory: 'Mémoire maximale', - io: 'E/S', - exit: 'Sortie', - live: 'en cours', - empty: "Cette conversation n'a lancé aucun processus.", - }, - - protection: { - quiet: 'Tranquillité', - limits: 'Limites de ressources', - tasks: 'Gestionnaire de tâches', - windows: "La mise au second plan, le son et les limites de ressources s'appliquent sur un hôte Windows.", - }, - taskManager: { - title: 'Gestionnaire des tâches', scope: 'Processus actifs sur cette machine, regroupés par agent.', - agents: 'Agents', live: 'Actualisé toutes les 2 secondes', paused: 'Actualisation en pause', disconnected: 'Machine déconnectée', - loading: 'Lecture de la consommation des agents', unsupported: 'Mettez Boite à jour sur cette machine pour voir la consommation des agents.', - loadFailed: 'Le gestionnaire des tâches n’a pas pu se charger. Réessayez.', - search: 'Trouver un agent', noMatches: 'Aucun agent correspondant.', sortBy: 'Trier par consommation', - sort: { memory: 'Plus de mémoire', cpu: 'Plus de CPU', disk: 'Plus d’activité disque', network: 'Plus d’activité réseau' }, - disk: 'Disque', network: 'Réseau', read: 'Lecture', write: 'Écriture', download: 'Réception', upload: 'Envoi', - observed: 'Observé', unavailableHint: 'Aucune mesure disponible pour le moment, ou ce compteur est non pris en charge. Une valeur absente ne signifie pas une consommation nulle.', - partialHint: 'Ce total comprend seulement les agents dont la consommation peut être mesurée.', - diskHint: 'Lectures et écritures sur le stockage observées pendant que cette vue est ouverte. Les processus brefs peuvent échapper à la mesure. Il s’agit d’activité, pas de la taille du dossier.', - tcpHint: 'Réception et envoi TCP observés pendant que cette vue est ouverte. Les connexions brèves et le trafic UDP peuvent échapper à la mesure.', - stop: 'Arrêter les processus', stopConfirm: 'Arrêter tous les processus de cette conversation ?', stopping: 'Arrêt des processus', - }, - resources: { - memory: 'Mémoire', - resolved: (mb: number) => `${Math.round(mb)} Mo`, - auto: (mb: number) => `Auto : ${Math.round(mb)} Mo`, - agents: 'Agents / budget', - available: 'Disponible / réserve', - unknown: 'Indisponible', - state: 'État de la protection', - states: { ok: 'Mémoire suffisante', critical: 'Limite de mémoire dépassée' }, - killTitle: 'Processus arrêté pour libérer de la mémoire', - killCount: (count: number) => `${count} processus arrêtés pour libérer de la mémoire`, - stoppedProcesses: (count: number) => `Voir les ${count} processus arrêtés`, - memorySettings: 'Réglages mémoire', - protectionOff: 'Protection mémoire désactivée', - killReason: { - 'thread-quota': (limit: string) => `Cette conversation a dépassé sa limite mémoire de ${limit}.`, - budget: (limit: string) => `Les agents ont dépassé leur budget mémoire commun de ${limit}.`, - machine: (limit: string) => `La mémoire disponible est passée sous la réserve de ${limit}.`, - }, - threadCap: 'Cette conversation a atteint sa limite de mémoire. Boite a refusé de lui en accorder davantage.', - budget: "Les agents ont atteint leur budget mémoire commun. Boite a refusé de leur en accorder davantage.", - throttled: (limit: string) => `Les processus de cette conversation sont ralentis par leur limite mémoire de ${limit}. Ils se figent au lieu d'échouer.`, - empty: 'Rien ne tourne en ce moment.', - processes: 'processus', - killTree: "Tuer l'arborescence", - killConfirm: 'Tuer tous les processus de cette conversation ?', - killConfirmYes: 'Tuer', - }, - - install: { - action: 'Installer', - update: 'Mettre à jour', - repair: 'Réinstaller', - remove: 'Désinstaller', - removeTitle: 'Désinstaller {provider} ?', - removeBody: 'Les fichiers téléchargés par Boite partent avec. Vos comptes restent, et il pourra être réinstallé plus tard.', - removeConfirm: 'Désinstaller', - removeCancel: 'Garder', - absent: 'Pas installé, {size}', - downloading: 'Téléchargement, {percent}', - verifying: "Vérification de l'archive", - extracting: 'Décompression', - installed: 'Installé par Boite', - updateAvailable: 'Version {installed}, {available} disponible', - progress: 'Progression du téléchargement' - }, - - accounts: { - heading: 'Fournisseurs', - login: 'Se connecter', - loginStarting: 'Démarrage de la connexion', - loginOpen: 'Ouvrir la page de connexion', - loginHint: 'Terminez dans votre navigateur. Cette ligne passera prête toute seule.', - loginInputPlaceholder: 'Collez le code donné par la page, si elle en donne un', - loginRedirectPlaceholder: "Sur un autre appareil ? Collez l'adresse localhost sur laquelle la page se termine", - loginSend: 'Envoyer', - terminalHint: 'Répondez à ses questions dans le terminal, puis fermez-le une fois connecté.', - terminalDone: 'Fermer le terminal', - remove: 'Supprimer', - removeTitle: 'Supprimer {account} ?', - removeBody: "Cela déconnecte le compte de Boite et supprime ses fichiers.", - removeDefaultBody: "Boite cesse d'utiliser ce login. Il reste connecté sur votre ordinateur.", - removeThread: "1 conversation utilise encore ce compte. Son prochain message la fait passer sur un autre compte connecté du même agent, ce qui recharge tout son contexte.", - removeThreads: "{count} conversations utilisent encore ce compte. Le prochain message de chacune la fait passer sur un autre compte connecté du même agent, ce qui recharge tout son contexte.", - removed: 'Compte supprimé', - removedHint: "Le compte de cette conversation a été supprimé. Choisissez un modèle sur un autre compte pour continuer.", - status: { - unknown: 'inconnu', - ok: 'ok', - unauthenticated: 'non connecté', - error: 'erreur' - } - }, - - usage: { - heading: 'Consommation', - note: "Les coûts sont les jetons au tarif API public de chaque modèle, d'après models.dev. Un modèle sans tarif connu prend le coût rapporté par son agent. Sur un abonnement, ce n'est pas une facture.", - thread: 'Conversation', - input: 'Entrée', - output: 'Sortie', - cost: 'Estimation au tarif API', - total: 'Total', - empty: 'Aucun jeton dépensé pour le moment.', - intro: 'Dépensé via Boite. Les jetons comptent les lectures et les écritures de cache.', - range: 'Période', - days: '{days} jours', - metric: 'Mesure', - provider: 'Fournisseur', - allProviders: 'Tous les fournisseurs', - machine: 'Machine', - allMachines: 'Toutes les machines', - machinesOffline: 'Non comptées, déconnectées : {machines}', - offline: 'Non connectée', - unknownProvider: 'Fournisseur inconnu', - filterUnsupported: 'Cette machine ne filtre pas la consommation par fournisseur. Mettez Boite à jour dessus, puis réessayez.', - noActivity: 'Aucun tour', - noUsage: 'Non rapporté', - coverage: 'Jetons {reported}/{turns} tours · Coût {priced}/{turns}', - partialCost: "{count} tours n'ont pas de prix et sont laissés hors de l'estimation.", - rates: 'Tarif API public de {model}, par million de jetons : entrée {input}, sortie {output}, lecture du cache {cacheRead}, écriture du cache {cacheWrite}.', - cacheSplit: 'Cache : {read} lus, {write} écrits.', - cacheSplitCost: 'Cache : {read} lus ({readCost}), {write} écrits ({writeCost}).', - reportedCost: "Aucun tarif public ne correspond à ce modèle : le coût est celui que son agent a rapporté.", - noRates: "Aucun tarif public ne correspond à ce modèle et son agent n'a rapporté aucun coût.", - olderCore: "La machine qui a fait ces tours a une version de Boite antérieure aux tarifs publics des API : seul un coût rapporté par son agent apparaît ici. Mettez-la à jour dans Réglages, Machines et mises à jour pour les estimer.", - noTokens: "L'agent n'a rapporté aucun jeton pour ces tours : ils n'ont pas de coût.", - priceOf: 'prix de {model}', - metrics: { tokens: 'Jetons', cost: 'Estimation API', turns: 'Tours' }, - totalOf: { - tokens: 'Jetons, {days} derniers jours', - cost: 'Estimation au tarif API, {days} derniers jours', - turns: 'Tours, {days} derniers jours' - }, - summary: '{turns} tours · {tokens} jetons · {cost} au tarif API', - summaryNoPrice: '{turns} tours · {tokens} jetons · Aucun prix', - summaryMissing: '{turns} tours · Consommation non rapportée', - overview: "Vue d'ensemble", - chart: 'Par jour', - chartLabel: '{metric} par jour et par fournisseur. Utilisez les flèches pour lire chaque jour.', - share: '{percent} du total', - breakdown: 'Détail', - byModel: 'Par modèle', - byDay: 'Par jour', - model: 'Modèle', - day: 'Jour', - turns: 'Tours', - tokens: 'Jetons', - cache: 'Cache', - noPrice: 'Aucun prix', - defaultModel: 'Modèle par défaut', - other: 'Autre', - threads: 'Conversations les plus gourmandes', - noRankedThreads: 'Aucune conversation à classer pour cette mesure.', - threadTurns: '{turns} tours', - untitled: 'Conversation sans titre', - archived: 'Archivée', - emptyRange: "Aucun tour terminé sur les {days} derniers jours.", - unpriced: 'Aucun prix pour {providers} : ni tarif public, ni coût rapporté.', - unreported: "{count} tours n'ont rapporté aucune consommation. Ils comptent seulement comme des tours.", - limits: 'Limites', - refresh: 'Actualiser la consommation', - loading: 'Lecture de la consommation', - failed: 'Lecture de la consommation impossible : {error}', - }, - - controls: { - heading: 'Boutons', - headingHint: 'Un bouton masqué reste accessible depuis la palette de commandes et les raccourcis clavier.', - preset: 'Préréglage', - everyday: "L'essentiel", - developer: 'Tout', - header: 'En-tête de la conversation', - composer: 'Barre de saisie', - sidebar: 'Barre latérale', - panel: 'Menu du panneau latéral', - project: 'Nom du projet', - branch: 'Branche', - context: 'Jauge de contexte', - hide: 'Masquer ce bouton', - customize: 'Choisir les boutons', - allHidden: 'Toutes les cartes sont masquées sur cet appareil.' - }, - - settings: { - heading: 'Réglages', - modelDefaults: 'Modèles par défaut', - modelDefaultsHint: 'Pour les nouvelles conversations. Enregistré sur cet appareil.', - modelDefaultUnavailable: "{provider} ne propose pas le modèle par défaut {model} sur ce compte. Choisissez un modèle disponible dans le sélecteur, ou changez le défaut dans les réglages.", - tabs: { - home: 'Accueil', - general: 'Général', - machines: 'Machines et mises à jour', - advanced: 'Avancé', - appearance: 'Apparence', - keyboard: 'Clavier', - accounts: 'Fournisseurs', - plugins: 'Extensions', - usage: 'Consommation', - resources: 'Protection', - experiments: 'Expérimentations', - diagnostics: 'Diagnostic' - }, - back: 'Retour aux conversations', - search: 'Rechercher un réglage', - searchEmpty: 'Aucun réglage ne correspond', - homeGroups: ['Application', 'Agents', 'Consommation et limites', 'Système'], - providersReady: '{count} connectés', - providersOne: '1 connecté', - machinesCount: '{count} machines', - devicesCount: '{count} appareils appairés', - devicesOne: '1 appareil appairé', - connectProvider: 'Connecter un fournisseur', - conversations: 'Conversations', - groupWorkingThreads: 'Regrouper les conversations en cours', - groupWorkingThreadsHint: 'Replie les conversations en cours et le travail en arrière-plan dans Projets et Récent. Les épingles, brouillons, erreurs et demandes de réponse restent visibles. Cet appareil seulement.', - groupOtherProjects: 'Regrouper les projets inactifs', - groupOtherProjectsHint: 'Replie les projets sans conversation visible ni brouillon dans Projets inactifs, dans la vue Projets. Désactive pour garder tous les projets dans la liste. Cet appareil seulement.', - app: 'Application', - tourReplay: 'Revoir la présentation', - execution: 'Exécution des agents', - warmProcessMinutesHint: 'Durée pendant laquelle un agent reste chargé après un tour. 0 l\'arrête aussitôt.', - autoCompact: 'Compaction automatique', - autoCompactHint: "Boite demande à l'agent de compacter entre deux tours, jamais pendant. Chaque compaction compte dans l'usage.", - autoCompactWhen: 'Quand', - autoCompactCondition: 'Condition', - autoCompactRecommended: 'Recommandé', - autoCompactCacheExpiry: "Avant l'expiration du cache de prompt", - autoCompactCacheExpiryHint: "Une minute avant que le fournisseur n'oublie la conversation en cache, si rien ne s'est passé depuis. Lue au tarif du cache, et le message suivant part de la version courte.", - autoCompactBackground: "Pendant que l'agent attend un travail en arrière-plan", - autoCompactBackgroundHint: "Après une réponse qui a laissé une commande ou une surveillance en cours. La compaction occupe l'attente.", - autoCompactTurnEnd: "Dès que l'agent a terminé", - autoCompactTurnEndHint: "Deux secondes après une réponse qui n'a rien laissé en cours. Un message envoyé juste après attend la compaction.", - autoCompactThreshold: "Seulement au-dessus d'une taille", - autoCompactThresholdHint: "Les conversations plus petites sont laissées tranquilles, comme un modèle qui n'arrive jamais jusque-là.", - autoCompactThresholdOff: 'Désactivé : chaque conversation est compactée aux moments ci-dessus, quelle que soit sa taille.', - autoCompactUnit: 'jetons', - projects: 'Projets', - language: 'Langue', - languageSystem: 'Système', - languageNames: { en: 'English', fr: 'Français' }, - theme: 'Thème', - display: 'Affichage', - accent: "Couleur d'accent", - accentNames: ['Bleu', 'Violet', 'Rose', 'Orange', 'Ambre', 'Vert', 'Turquoise'], - colors: { - title: 'Couleurs', - hint: 'Les modes clair et sombre gardent leurs propres couleurs. Enregistré sur cet appareil.', - palette: 'Palette de couleurs', - palettes: { default: 'Origine', oled: 'OLED', catppuccin: 'Catppuccin', 'tokyo-night': 'Tokyo Night', nord: 'Nord', gruvbox: 'Gruvbox', amethyst: 'Amethyst' }, - customize: 'Personnaliser les couleurs', - roles: { background: 'Fond', frame: 'Barre latérale et cadre', surface: 'Panneaux et messages', foreground: 'Texte', accent: 'Accent' }, - edit: 'Choisir une couleur à modifier', - reset: 'Réinitialiser', - undo: 'Annuler', - field: '{color} : saturation et luminosité', - coordinates: 'Saturation {saturation} %, luminosité {brightness} %', - hue: 'Teinte', - hex: 'Hex', - invalidHex: 'Saisissez une couleur hexadécimale comme #7AA2F7.', - contrast: 'Contraste du texte', - contrastHint: 'Le contraste le plus faible du texte sur le fond, le cadre et les panneaux. La cible est de 4,5:1.', - contrastLow: 'Contraste faible du texte {ratio}:1', - improveContrast: 'Améliorer le contraste' - }, - themeSystem: 'Système', - themeDark: 'Sombre', - themeLight: 'Clair', - themeGrain: 'Grain', - reading: 'Lecture', - font: 'Police du texte', - fontSystem: 'Système', - fontSample: 'Relance les 12 tests en échec dans Il1/O0/rn.ts', - monoFont: 'Police du code', - monoSystem: 'Système', - zoom: "Taille de l'interface", - zoomHint: 'Ctrl+= et Ctrl+- partout dans l\'appli, Ctrl+0 pour revenir à 100 %.', - zoomOut: 'Plus petit', - zoomIn: 'Plus grand', - zoomReset: 'Revenir à 100 %', - zoomFailed: "Le zoom n'a pas changé : {error}", - experiments: { - intro: 'Ce n\'est pas fini et peut changer ou disparaître.' - }, - cacheKeepalive: 'Garder le cache de prompt au chaud', - cacheKeepaliveHint: "Pendant qu'une conversation attend ses agents ou ses workflows, Boite envoie un tour d'un mot peu avant que le fournisseur oublie le prompt en cache, pour que leur résultat soit lu au tarif du cache. Chaque relance compte dans l'usage. Une relance qui rate le cache met fin aux relances.", - cacheKeepaliveHours: 'Pendant au plus', - cacheKeepaliveHoursHint: "Compté depuis la fin du dernier tour de la conversation. Les relances s'arrêtent aussi quand les agents ont fini, quand vous écrivez ou arrêtez, et après autant de relances que coûterait un cache raté.", - cacheKeepaliveUnit: 'heures', - material: 'Matériau de la fenêtre', - workspace: 'Espace de travail', - startIn: "L'appli s'ouvre sur", - startDrafts: 'Brouillons', - startProject: 'Le dernier projet', - panelStart: "Le panneau latéral s'ouvre sur", - panelLauncher: 'Son menu', - panelFiles: 'Fichiers', - panelChanges: 'Modifications', - materialAcrylic: 'Acrylique', - materialMica: 'Mica', - materialSolid: 'Opaque', - chatWidth: 'Largeur du chat', - groupChanges: 'Regrouper les fichiers modifiés dans la conversation', - expandDiffs: 'Ouvrir les aperçus des changements par défaut', - chatWidthComfortable: 'Confortable', - chatWidthWide: 'Large', - chatWidthFull: 'Pleine', - terminalCursor: 'Curseur du terminal', - terminalCursorBar: 'Barre', - terminalCursorBlock: 'Bloc', - terminalCursorUnderline: 'Soulignement', - terminalGpuHint: "Défilement et affichage plus fluides. Désactivez-le si un terminal s'affiche mal sur cet appareil ; le changement vaut pour les terminaux ouverts ensuite.", - terminalGpu: 'Dessiner les terminaux avec le GPU', - connection: 'Connexion', - localCore: "Cette application tourne sur le cœur qu'elle a lancé sur cet ordinateur.", - pair: 'Appairer', - manual: 'URL du cœur et jeton', - coreUrl: 'URL du cœur', - token: 'Jeton', - connect: 'Connecter', - warmProcessMinutes: 'Minutes de processus gardé chaud', - listenOnLan: 'Accessible sur le réseau local', - listenOnLanHint: 'Permet à un téléphone du même réseau d\'atteindre cet ordinateur, à partir du prochain démarrage de Boite. Une option --host ou --lan l\'emporte.', - agentCpuCapPercent: 'Plafond CPU, %', - agentCpuCapHint: 'Pour tous les agents ensemble. 0 signifie aucun plafond. Windows uniquement.', - threadMemoryCapMb: 'Plafond mémoire par conversation, Mo', - threadMemoryCapHint: 'Pour une conversation et ses processus. 0 prend la moitié du budget des agents, qu\'il ne peut pas dépasser.', - agentMemoryBudgetPercent: 'Budget mémoire de tous les agents, %', - agentMemoryBudgetHint: 'Pour tous les agents ensemble, de 10 à 90 %. 60 % par défaut.', - memoryReserveMb: 'Mémoire réservée aux autres applications, Mo', - memoryReserveHint: 'Gardée libre pour vos autres applications. 0 garde 10 % de la RAM, au moins 3072 Mo. En dessous de 12288 Mo de RAM, il garde 25 %.', - memoryProtection: 'Protéger la mémoire disponible', - memoryProtectionHint: 'Au-delà d\'une limite, Boite arrête la plus grosse arborescence de processus lancée par un agent, jamais l\'agent lui-même. Désactivé, les plafonds mémoire Windows sautent aussi.', - background: 'Arrière-plan', - closeToTray: 'Garder Boite en marche quand la fenêtre se ferme', - closeToTrayHint: 'Réduit Boite dans la zone de notification et laisse les tours continuer. Quitter arrête toujours Boite.', - launchAtLogin: "Lancer Boite à l'ouverture de session", - launchAtLoginHint: "Boite démarre dans la zone de notification avec son cœur actif : cet ordinateur peut faire tourner des agents et répondre à vos autres appareils sans ouvrir sa fenêtre. S'applique à ce compte utilisateur.", - notifications: "Me prévenir quand une conversation se termine ou pose une question", - notificationsHint: 'Seulement pour une conversation que vous ne regardez pas. Cette machine seulement.', - notificationsDenied: 'Les notifications sont bloquées pour Boite sur ce système ; autorisez-les dans ses réglages.', - focusGuard: 'Garder les fenêtres des agents en arrière-plan', - muteAgents: 'Couper le son des agents', - reapOrphans: 'Arrêter ce que les agents laissent tourner', - reapOrphansHint: 'Dix secondes après un tour, un processus dont le parent est sorti est arrêté. Windows uniquement.', - asyncQuestions: 'Laisser les agents poser des questions sans s’arrêter', - asyncQuestionsHint: 'Apprend la commande boite ask aux agents au début d\'une session. Codex pose déjà ses questions ainsi.', - shortAnswers: 'Réponses courtes', - shortAnswersHint: 'Demande à chaque agent, à chaque message, le résultat d\'abord en quelques phrases simples, sans préambule, récapitulatif ni commentaire en cours de route. Les faits, les chiffres et les commandes restent.', - toolSummaries: 'Résumés des outils (Claude)', - toolSummariesHint: 'Claude décrit chaque série d\'appels d\'outils en une ligne au lieu de les compter. Chaque ligne est une requête à son petit modèle, Haiku, facturée au compte de la conversation. S\'applique au démarrage d\'un processus Claude.', - titleModel: 'Modèle des titres', - titleModelHint: 'Nomme une conversation après sa première réponse. Automatique prend l\'agent de la conversation sur son petit modèle.', - titleModelAuto: 'Automatique', - titleModelAutoHint: 'L’agent de la conversation, petit modèle', - titleModelSmall: 'petit', - numberRange: 'Un nombre entier de {min} à {max}.', - saved: 'Enregistré', - core: 'Cœur', - version: 'Version', - protocol: 'Protocole', - os: 'Système', - pid: 'PID', - dataDir: 'Répertoire de données', - endpoint: 'Point de connexion', - noCore: 'Pas connecté à un cœur.', - archived: { - doneRetentionLabel: 'Supprimer les conversations terminées après (jours)', - doneRetentionHint: 'À compter du passage en terminé ou de l\'archivage automatique après une fusion de PR. 3 jours par défaut ; 0 désactive la suppression. Les archives manuelles sont conservées. Les conversations supprimées restent récupérables pendant le délai configuré.', - deletesAt: 'Suppression : {date}', - heading: 'Conversations archivées', - mergedReason: 'Archivée après la fusion de la PR #{number}', - intro: 'L\'agent d\'une conversation archivée s\'arrête. Il repart au premier message après une restauration.', - show: 'Afficher les conversations archivées', - empty: 'Aucune conversation archivée sur cette machine.', - restore: 'Restaurer', - }, - deleted: { - heading: 'Conversations supprimées', - retentionLabel: 'Jours avant la suppression définitive', - retentionHint: 'À compter de la suppression. 0 conserve les conversations sans limite. Réduire le délai concerne aussi les conversations déjà supprimées.', - retentionError: 'Saisissez un nombre entier entre 0 et 3650 jours.', - purgesAt: 'Suppression définitive : {date}', - show: 'Afficher les conversations supprimées', - empty: 'Aucune conversation supprimée à restaurer sur cette machine.', - }, - worktrees: { - storage: 'Emplacement de stockage', - storageProject: 'Dans chaque projet', - storageShared: 'Un dossier commun', - storageProjectHint: 'Dans chaque projet, sous', - storageDirectory: 'Dossier sur cette machine', - storagePlaceholder: 'Chemin absolu du dossier', - storageBrowse: 'Choisir un dossier', - storageSharedHint: 'Chaque projet a son sous-dossier. Les projets de même nom restent séparés.', - storageNewOnly: 'Nouveaux worktrees seulement. Les autres restent où ils sont.', - storageInvalid: 'Saisissez un chemin absolu sur la machine qui exécute vos agents.', - storageSaveFailed: 'Impossible d\'enregistrer cet emplacement. Vérifiez le chemin et la connexion, puis réessayez.', - heading: 'Worktrees', - intro: 'Un worktree reste sur le disque une fois sa conversation archivée. En supprimer un efface le dossier et sa branche.', - show: 'Afficher les worktrees', - noProject: 'Aucun projet git sur cette machine.', - empty: 'Plus aucun worktree.', - sweep: 'Supprimer les propres ({count})', - remove: 'Supprimer', - dirty: 'modifications non commitées', - unmerged: 'commits sur aucune autre branche', - missing: 'dossier disparu', - heldBy: 'utilisé par « {title} »', - archivedThread: 'conversation archivée : « {title} »', - heldHint: 'Une conversation travaille ici : archivez-la d’abord', - forceTitle: 'Supprimer {name} quand même ?', - forceBody: 'Il contient des {reasons}. Le supprimer efface le dossier et sa branche, et ce travail est perdu.' - }, - }, - - keyboard: { - heading: 'Raccourcis', - intro: 'Cliquez sur un raccourci, puis appuyez sur les touches voulues. Le changement s\'applique aussitôt sur cette machine.', - search: 'Filtrer les commandes', - groups: { general: 'Général', surfaces: 'Panneau latéral', thread: 'Conversation et message', jump: 'Aller à une conversation', theme: 'Thème', terminal: 'Terminal' }, - record: 'Appuyez sur les touches', - change: 'Changer le raccourci', - reset: 'Revenir au défaut', - resetAll: 'Tout réinitialiser', - clear: 'Retirer le raccourci', - conflict: '{chord} lance déjà "{command}". Le déplacer ici ?', - replace: 'Le déplacer ici', - needsModifier: "Tenez Ctrl, Alt ou Win avec la touche. Une touche seule, ou avec Maj seulement, c'est de la frappe.", - noMatch: 'Aucune commande ne correspond.', - file: 'Fichier des raccourcis', - openFile: 'Ouvrir le fichier', - copyPath: 'Copier le chemin', - openFailed: 'Impossible d’ouvrir le fichier : {reason}', - fileHint: - 'Des identifiants de commande associés à des raccourcis comme "mod+shift+k", ou null pour retirer une touche. mod vaut Ctrl ici et Cmd sur un Mac. Les entrées absentes gardent leur valeur par défaut.', - id: 'Identifiant', - none: 'Non défini', - custom: 'modifié', - problems: 'Refusé', - problemsHint: 'Ces lignes du fichier n\'ont pas été appliquées. Corrigez-les et enregistrez.', - commands: { - palette: 'Ouvrir la palette de commandes', - browser: 'Ouvrir la surface navigateur', - changes: 'Ouvrir la surface des modifications', - files: "Ouvrir l'arbre des fichiers", - tasks: 'Ouvrir la surface des tâches', - closeSurface: 'Fermer la surface active', - stash: 'Mettre le message de côté, ou le reprendre', - sendAndDraft: 'Envoyer et ouvrir une nouvelle conversation', - pin: 'Épingler ou désépingler cette conversation', - reopenThread: 'Rouvrir la dernière conversation archivée', - copyAnswer: 'Copier la dernière réponse', - find: 'Chercher dans cette conversation', - terminalNew: 'Nouveau terminal', - terminalSplit: 'Diviser le terminal à droite', - terminalSplitVertical: 'Diviser le terminal en bas', - terminalClose: 'Fermer le terminal', - terminalFind: 'Chercher dans le terminal', - terminalHistory: 'Commandes récentes du shell', - terminalPreviousCommand: 'Aller à la commande précédente', - terminalNextCommand: 'Aller à la commande suivante', - thread: 'Aller à la conversation {n}' - } - }, - - experiments: { - recordingIndicators: { title: 'Actions visibles dans les vidéos', hint: 'Ajouter des repères de clics et les touches de navigation aux enregistrements. Le texte saisi et les mots de passe ne sont jamais affichés comme libellés de touches.' }, - openChatLinks: { title: 'Ouvrir les liens du chat dans leurs applications', hint: 'Sur ce bureau, un clic sur un fichier ou dossier local ouvre son application, y compris les raccourcis et les fichiers hors du projet. Seuls les liens de cet ordinateur sont ouverts.' }, - goal2: { title: 'Objectifs relus', hint: "Ajoute /goal2 : quand l'agent annonce l'objectif atteint, une nouvelle conversation qui n'a pas vu son travail le vérifie et répond validé, partiel ou refusé. Tout ce qui n'est pas validé repart à l'agent, jusqu'à cinq relectures" }, - whip: { - title: 'Fouet', - hint: 'Un fouet animé suit le pointeur ou le doigt. Un geste vif le fait claquer, un clic ou un appui le lâche', - action: 'Sortir le fouet', - drop: 'Lâcher le fouet' - }, - previewComments: { title: 'Commentaires sur les aperçus', hint: 'Sélectionner un élément dans le navigateur intégré et l’ajouter au brouillon de la conversation' }, - themeGrain: { - title: 'Thème Grain', - hint: 'Un fond grainé qui passe du gris clair en haut à gauche au noir en bas à droite' - }, - sessionImport: { - title: 'Import de session Claude Code', - hint: 'Transformer une session lancée dans le terminal en conversation, depuis le menu du projet et la palette' - }, - promptCache: { - title: 'Minuteur du cache de prompt', - hint: 'Une horloge à côté de la jauge de contexte, qui décompte le temps pendant lequel le provider garde la conversation en cache' - }, - residentAgents: { - title: 'Agents', - hint: 'Des agents résidents avec leur propre brain, leurs missions et leurs routines, depuis la barre latérale. Les agents déjà créés continuent de tourner quand ceci est désactivé' - }, - edgeScrollbars: { - title: 'Barres de défilement au bord', - hint: 'Les barres de défilement ne prennent plus de place. Chacune apparaît quand le pointeur s’approche du bord de sa zone et reste déplaçable. Souris et stylet uniquement' - }, - pressPreload: { - title: 'Chargement à l’appui', - hint: 'Une conversation ou une page commence à charger dès que vous appuyez dessus, avant de relâcher le clic ou de lever le doigt' - } - }, - - /** Finished features, on by default, each switched from the Settings page it belongs to. */ - features: { - chatArtifacts: { title: 'Fichiers et aperçus dans le chat', hint: 'Ouvrir les liens de fichiers et afficher les fichiers envoyés par un agent dans la conversation' }, - }, - - quotas: { - machineAccounts: 'Limites des comptes de {machine}', - outdated: 'Mettez à jour Boite sur cette machine pour voir ses limites sur cet appareil.', - usingCredits: 'Crédits utilisés', - reorder: 'Réordonner {name}. Glissez ou utilisez les flèches du clavier.', - bankedReset: '{count} reset en réserve', - observed: 'Relevé du {time}', - remainingUnder: 'Moins de {percent} % restants', - bankedResets: '{count} resets en réserve', - resetExpires: 'Prochain reset expirant {time}', - useReset: 'Utiliser un reset', - resetUsing: 'Reset en cours', - resetConfirmTitle: 'Utiliser un reset pour {account} ?', - resetConfirmBody: 'Cette action consomme le reset disponible le plus proche de son expiration sur ce compte {provider} et réinitialise ses limites actuelles. Elle est irréversible.', - resetConfirm: 'Utiliser un reset', - resetApplied: 'Reset appliqué. Limites actualisées.', - resetNothing: "Aucune limite à réinitialiser. Aucun reset n'a été consommé.", - resetNoCredit: "Aucun reset en réserve n'est disponible pour ce compte.", - resetAlreadyUsed: 'Ce reset a déjà été utilisé. Limites actualisées.', - resetRefreshFailed: "Reset appliqué, mais les nouvelles limites n'ont pas pu être lues. Actualisez pour vérifier.", - resetFailed: "Impossible d'utiliser le reset : {error}", - budgetRemaining: 'Budget mensuel restant', - budgetCompact: 'Budget mensuel', - creditCompact: 'Crédits', - windowWeeklyCompact: 'Semaine', - windowMonthlyCompact: 'Mois', - creditRemaining: 'Crédits restants', - creditBalance: '{count} crédits', - - trayShow: 'Afficher', - trayQuit: 'Quitter', - names: { muse: 'Muse Code', claude: 'Claude', codex: 'Codex', antigravity: 'Antigravity', grok: 'Grok', opencode: 'OpenCode Go' }, - noReading: 'Indisponible', - cliSource: 'connexion en ligne de commande agy', - connect: 'Connecter un compte', - noReset: 'Heure de réinitialisation non communiquée', - heading: 'Quotas des fournisseurs', - intro: "Limites d'abonnement communiquées par vos fournisseurs. C'est distinct des jetons dépensés dans Boite.", - remaining: '{percent} % restants', - resets: 'Repart {time}', - windowHours: '{hours} heures', - windowWeekly: 'Hebdomadaire', - windowMonthly: 'Mensuelle', - windowCredits: 'Crédits', - checked: 'Mis à jour {time}', - stale: 'Dernière lecture réussie', - empty: "Connectez un fournisseur pour voir ses limites d'abonnement.", - unsupported: "Ce fournisseur n'expose pas ses quotas d'abonnement ici.", - disabled: 'Le suivi des quotas est désactivé pour ce compte.', - loading: 'Lecture des limites du fournisseur', - slowHint: "Antigravity peut mettre jusqu'à deux minutes à répondre.", - tracked: 'Comptes suivis', - trackedHint: 'Boite n\'interroge que les comptes activés ici.', - autoResume: 'Reprendre les conversations après une limite d\'usage', - autoResumeHint: 'Une conversation arrêtée par une limite d\'usage reprend seule une minute après la réinitialisation, une fois par fenêtre. Stop, un nouveau message ou un autre modèle l\'annule.', - providers: 'Gérer les fournisseurs', - unavailable: 'Aucune lecture de quota disponible.', - refresh: 'Actualiser les quotas', - glance: 'Limites des comptes', - allLimits: 'Page des limites', - readFailed: 'Lecture des limites impossible : {error}', - quit: 'Quitter Boite', - }, - harnessUpdates: { - heading: 'Mises à jour des agents', - intro: 'Chaque machine vérifie ses agents toutes les six heures.', - auto: 'Mettre les agents à jour automatiquement', - autoHint: "Un agent se met à jour sans que personne ne soit connecté : une conversation en cours se met en pause après son appel d'outil et reprend une fois la mise à jour faite", - checking: 'Vérification', - available: (name: string, version: string) => `${name} ${version} est disponible`, - availableShort: 'Mise à jour disponible', - installed: (version: string) => `Version installée : ${version}`, - on: (machine: string) => `Sur ${machine}`, - update: 'Mettre à jour', skip: 'Ignorer', - updating: (name: string) => `Mise à jour de ${name}`, - waiting: (count: number) => count === 1 ? "Attend la pause d'une conversation" : `Attend la pause de ${count} conversations`, - failed: (name: string) => `La mise à jour de ${name} a échoué`, - upToDate: 'À jour', unknown: 'Vérifie lui-même', runUpdater: 'Lancer sa mise à jour', - skipped: (version: string) => `${version} ignorée`, - unskip: 'Proposer à nouveau', none: "Aucun agent sur cette machine ne possède de programme de mise à jour que Boite peut lancer.", - route: { managed: 'Installé par Boite', self: 'Votre installation' } - }, - providerSettings: { - heading: 'Fournisseurs', - intro: 'Un agent déjà connecté sur cet ordinateur est repris tout seul.', - step: { - installing: 'Installation', - install: 'Pas installé', - repair: 'Des fichiers installés manquent', - manual: 'Pas installé', - 'signing-in': 'Connexion', - 'sign-in': 'Pas connecté', - external: 'Pas connecté', - ready: 'Prêt' - }, - accountsCount: '{count} comptes', - thenSignIn: 'La connexion démarre à la fin du téléchargement.', - manualHint: '{provider} vient avec son propre installateur. Lancez-le, puis revenez : Boite le trouve tout seul.', - externalHint: '{provider} se connecte depuis sa propre application. Connectez-vous là-bas, puis vérifiez à nouveau.', - setup: "Instructions d'installation", - refresh: 'Vérifier à nouveau', - details: 'Détails', - isolated: 'Compte Boite', - default: 'Votre login en ligne de commande', - reconnect: 'Se reconnecter', - check: 'Vérifier la connexion', - connectionOk: 'Connecté', - checking: 'Vérification', - accountName: 'Nom du compte', - revealEmail: 'Afficher le mail', - deviceHint: 'Saisissez ce code sur la page de connexion. Boite attend la fin de la connexion.', - addAccount: 'Ajouter un autre compte', - useCli: 'Utiliser mon login en ligne de commande', - executable: 'Lancé depuis', - accounts: 'Comptes', - installation: 'Installation', - managedCopy: 'Copie de Boite', - addHeading: 'Ajouter un fournisseur', - offHeading: 'Désactivés', - off: 'Désactivé', - experimental: 'Expérimental', - /** The switch's accessible name: `{provider}` is the provider's own name. */ - enable: 'Utiliser {provider}', - enableHint: "Désactivé, Boite ne lance rien de ce fournisseur et aucune liste ne le propose. Ses comptes et ses conversations sont conservés.", - /** A thread whose provider is off: the composer's chip and the sentence behind it. */ - threadOff: '{provider} est désactivé. Activez-le dans Réglages > Fournisseurs pour continuer cette conversation.', - defaultModel: 'Modèle par défaut', - autoUpdate: 'Mises à jour automatiques', - checkUpdates: 'Rechercher les mises à jour', - updating: 'Mise à jour en cours' - }, - plugins: { - heading: 'Extensions', - revealUnknownMachine: "Le fil ou l'agent qu'une application demande d'afficher est sur une machine à laquelle cette fenêtre n'est pas connectée. Ajoutez-la dans Réglages, Machines et mises à jour.", - revealMachineOffline: "Le fil ou l'agent qu'une application demande d'afficher est sur {machine}, qui n'est pas connectée pour l'instant.", - intro: 'Une extension est un programme qui tourne sur la machine hébergeant votre cœur, vérifié contre le SHA-256 publié par son manifeste.', - installedHeading: 'Installées', - recommendedHeading: 'Recommandées', - install: 'Installer', - update: 'Mettre à jour vers {version}', - reinstall: 'Réinstaller', - remove: 'Désinstaller', - stateInstalled: 'Installée', - stateInstalling: 'Téléchargement et vérification', - stateError: "L'installation a échoué", - stateRejected: 'Refusée par Boite', - updateAvailable: '{version} disponible', - source: 'Code source', - atCommit: '{ref} au commit {commit}', - pools: 'Pools de comptes', - empty: 'Aucun compte enregistré dans ce pool.', - add: 'Enregistrer le login CLI actuel', - switch: 'Utiliser ce compte', - forget: 'Oublier le compte', - active: 'Actif', - removeTitle: 'Désinstaller {name} ?', - removeBody: 'Supprime le dossier de l\'extension dans le répertoire de données de Boite. Ce que le programme a enregistré ailleurs, ses logins par exemple, reste en place.', - switchTitle: 'Changer le login CLI ?', - switchBody: "Cela change le login CLI par défaut du fournisseur pour les tours suivants, y compris hors de Boite. Les comptes Boite isolés gardent le leur.", - forgetTitle: 'Oublier ce login enregistré ?', - forgetBody: "Retire ce login du pool de l'extension.", - cliScope: 'Des logins CLI, distincts des comptes Boite connectés dans Fournisseurs.', - addHeading: 'Ajouter depuis une URL git', - addHint: 'L\'URL https d\'un dépôt git avec un boite-plugin.json à sa racine. Boite montre ce qu\'il téléchargerait avant d\'installer quoi que ce soit.', - urlLabel: 'URL du dépôt', - urlPlaceholder: 'https://github.com/owner/repo', - refLabel: 'Branche, tag ou commit', - refPlaceholder: 'Branche par défaut', - inspect: 'Aperçu', - inspecting: 'Lecture', - previewSource: 'Source', - previewReplaces: 'Remplace', - previewDownloads: 'Télécharge', - previewDigest: 'SHA-256', - previewRuns: 'Exécute', - previewPools: 'Pools de comptes', - previewApp: 'Application de bureau', - previewAppBody: "Ouvre sa propre fenêtre et obtient un accès complet à Boite grâce au jeton du propriétaire : elle peut lire et piloter chaque fil, comme cette application.", - app: 'Application de bureau', - appRunning: 'En marche', - appStarting: 'Redémarrage', - appStopped: 'Arrêtée', - appOff: "Arrêtée · ne redémarre que si vous la lancez", - appCrashed: 'Plantée avec le code de sortie {code}', - appCrashedSignal: 'Plantée', - appUnavailable: 'Indisponible', - appStart: 'Lancer', - appStop: 'Arrêter', - appRestart: 'Redémarrer', - startWithBoite: 'Démarrer avec Boite', - gateway: 'Passerelle', - gatewayRunning: '{version} sur 127.0.0.1:{port}', - gatewayStarting: 'Démarrage', - gatewayUpdating: 'Mise à jour', - gatewayCrashed: 'Plantée', - gatewayData: 'Ses connexions et sa base sont dans {dir}, et y restent si on la désinstalle.', - removeGatewayBody: 'Supprime le programme du dossier de données de Boite. Ses connexions, ses réglages et sa base restent dans {dir}, prêts pour la prochaine installation.', - previewGateway: 'Passerelle', - previewGatewayBody: 'Tourne en arrière-plan sur cette machine, sur un port local, sans accès à Boite. Le proxy d\'abonnements peut pointer dessus.', - trust: "Ce programme tourne avec vos permissions sur la machine qui héberge votre cœur. Ne l'installez que si vous faites confiance à qui le publie.", - confirmAdd: 'Installer {name}', - refusedTitle: 'Boite a refusé cette extension', - refusedFile: 'Fichier', - refusedField: 'Champ', - refusedExpected: 'Attendu', - checkUpdates: 'Rechercher des mises à jour', - checkingUpdates: 'Recherche de mises à jour', - upToDate: 'À jour · vérifiée {time}', - updateFound: 'Trouvée sur {source}, {ref} au commit {commit} · vérifiée {time}', - updateIgnored: 'Pas proposée : {reason}', - updateFailed: 'Impossible de rechercher les mises à jour : {error}. Boite réessaiera plus tard.', - noticeTitle: '{name} {version} est disponible', - noticeBody: 'Son téléchargement est vérifié contre le SHA-256 publié par sa version.', - noticeUpdate: 'Mettre à jour', - noticeLater: 'Plus tard', - noticeLabel: "Mise à jour d'extension", - }, - - time: { - now: 'à l\'instant' - }, - - errors: { - draftStorage: "Cet appareil n'a pas pu enregistrer votre brouillon. Gardez cette fenêtre ouverte et copiez votre texte avant de la fermer.", - draftAttachment: "Une pièce jointe n'a pas pu être lue depuis le stockage. Rechargez pour la récupérer, ou retirez-la avant l'envoi.", - prefix: 'Erreur', - activityUnsupported: "Ce cœur ne gère pas les objectifs ni les boucles. Mettez Boite à jour sur {machine}, puis reconnectez-vous. Votre commande n'a pas été envoyée.", - pullRequestUnsupported: "Mettez Boite à jour sur la machine qui héberge cette conversation pour afficher sa pull request. Ce cœur ne sait pas encore les chercher.", - noEndpoint: "Aucun point de connexion n'a pu être résolu.", - coreStart: "Le cœur de cet ordinateur n'a pas démarré : {reason}", - connect: 'Connexion au cœur impossible.', - clipboard: 'Le presse-papiers a refusé le texte.', - revoked: "Cet appareil a été retiré du groupe. Rajoutez-le avec un nouveau code depuis l'une de vos machines.", - unpaired: "Cet appareil n'est pas encore dans le groupe de cette machine. Ajoutez-le avec le code d'Ajouter un appareil.", - olderCore: "Boite sur {machine} est trop ancien pour cette action. Mettez-le à jour dans Réglages, Machines et mises à jour, puis réessayez.", - olderCoreHere: "Cette machine a une version de Boite trop ancienne pour cette action. Mettez-la à jour dans Réglages, Machines et mises à jour, puis réessayez.", - notConnected: "{machine} n'est pas connectée. Boite se reconnecte tout seul dès qu'elle répond.", - notConnectedHere: "Cette machine n'est pas connectée. Boite se reconnecte tout seul dès qu'elle répond.", - pairingLink: "Ce n'est pas un lien pour ajouter un appareil. Copiez le lien affiché par Ajouter un appareil sur l'autre machine ; il commence par http et contient ?grant=." - }, - - units: { - bytes: 'o', - kilobytes: 'ko', - megabytes: 'Mo', - gigabytes: 'Go', - milliseconds: 'ms', - seconds: 's', - minutes: 'min', - hours: 'h', - tokens: 'jetons' - }, - - effortLevels: { - none: 'Aucun', - off: 'Désactivé', - minimal: 'Minimal', - low: 'Faible', - medium: 'Moyen', - high: 'Élevé', - xhigh: 'Xhigh', - max: 'Max', - ultra: 'Ultra', - ultrathink: 'Ultrathink', - fast: 'Rapide' - }, - - parts: { - loading: 'Chargement', - failed: "Cette partie de Boite n'a pas pu se charger.", - navigationFailed: "La navigation n'a pas pu se charger.", - dictationFailed: "La dictée n'a pas pu se charger. Réessayer" - }, - - common: { - none: 'aucun', - unknown: 'inconnu', - yes: 'oui', - no: 'non', - close: 'Fermer', - moreInfo: 'À propos : {topic}', - refresh: 'Actualiser', - dismiss: 'Ignorer', - cancel: 'Annuler', - retry: 'Réessayer', - save: 'Enregistrer', - rename: 'Renommer', - delete: 'Supprimer', - copy: 'Copier', - open: 'Ouvrir', - }, - - onboarding: { - privacy: { - title: 'VENDEZ VOTRE ÂME À BOITE !', - intro: 'Quelques stats anonymes nous disent quoi corriger en premier. Vos messages, fichiers et comptes ne quittent jamais votre machine.', - question: 'Marché conclu ?', - video: 'Mème Trade Offer', - basic: 'NON ! Juste les compteurs de base', - basicDefault: '(par défaut)', - basicHint: 'Des compteurs anonymes, aucun profil.', - deal: 'MARCHÉ CONCLU', - dealHint: 'Aussi les modèles et réglages utilisés. Merci !', - details: "De base : installations actives sous un identifiant aléatoire haché, et lancements, conversations et résultats des tours sous des identifiants qui changent chaque jour. Aucun profil. Marché conclu ajoute les modèles publics choisis, le niveau de raisonnement, les modes de vitesse et de permission, les durées et les volumes de jetons, sous un identifiant aléatoire stable ; les modèles privés comptent comme « autre ». L'application marche pareil dans tous les cas, et les réglages le changent quand vous voulez.", - optOut: 'Si vous ne voulez VRAIMENT pas aider Boite :', - offLabel: 'tout désactiver :c', - doc: 'Ce qui est collecté' - }, - demo: { - pause: 'Pause', play: 'Rejouer', resume: 'Reprendre', - record: 'Cliquez sur le micro', speak: 'Dictez votre message', review: "Relisez avant d'envoyer", - listening: 'Écoute en cours', draft: 'Votre brouillon', localLabel: 'Sur cet ordinateur', - chooseAgent: "Choisissez l'agent suivant", handoff: 'Codex reprend la conversation', checked: 'Libellés vérifiés. Contraste amélioré.', nextMessage: 'Écrire à Codex', - before: 'Avant', after: 'Après', buttonBefore: 'Envoyer', buttonAfter: 'Envoyer le message', changedFile: 'ContactForm.svelte', changesReady: 'Deux lignes modifiées', - background: 'En cours en arrière-plan', writing: 'Préparer le lancement de demain', notes: 'Notes', noteOne: 'Relire le formulaire de contact', noteTwo: 'Publier le portfolio', - taskResult: 'Formulaire prêt', loginResult: 'Connexion réparée', testsResult: 'Tests réussis', hoverTray: "Survolez l'icône Boite", - conversation: "Changer d'agent", voice: 'Dicter', panel: 'Voir les modifications', - task: 'Créer mon portfolio', secondTask: 'Réparer la connexion', thirdTask: 'Écrire les tests', working: 'En cours', request: 'Ajoute un formulaire de contact', answer: 'Le formulaire est prêt.', followup: "Vérifie maintenant l'accessibilité", continued: 'Même conversation. Même historique.', - voiceWords: 'Rends les boutons plus lisibles', voiceHint: 'Parlez, relisez le brouillon, puis envoyez.', - changes: 'Modifications', reviewed: 'Relisez les changements à côté du chat.', - usageLabel: 'Claude', window: 'Limite sur 5 h', used: '24 % utilisés', reset: 'Réinitialisation dans 2 h 10', trayHint: "Survolez l'icône Boite pour voir vos limites sans ouvrir l'application.", - desktop: 'Votre ordinateur', phone: 'Votre téléphone', synced: 'La même conversation', reachHint: "Après un appairage par QR code, suivez vos agents sur votre téléphone. Votre ordinateur continue de les faire tourner.", - notification: 'Formulaire de contact terminé', quietBody: "Vos agents travaillent sans prendre votre écran ni vos haut-parleurs.", - installVoice: 'Installer la dictée locale', downloadVoice: 'Environ 200 Mo. Le son reste sur cet ordinateur.', installingVoice: 'Téléchargement du moteur vocal', cancelVoice: 'Annuler le téléchargement', readyVoice: 'La dictée est prête. Utilisez le micro du champ de message.', - }, - label: 'Prise en main', - skip: 'Passer', - back: 'Retour', - next: 'Suivant', - done: 'À moi de boiter', - step: 'Étape {index} sur {total}', - progress: "Aller à l'étape {index} : {title}", - replay: 'Revoir la présentation', - changeLater: 'Tout ça se retrouve dans les réglages.', - - profile: { - title: "D'abord, lequel êtes-vous ?", - everyday: "Je ne suis pas dev ! Ne m'embrouillez pas avec du code et des commandes !", - everydayHint: "Une nouvelle conversation attend dans Documents/Boite jusqu'à ce que vous lui donniez un dossier, et l'agent demande avant d'agir.", - developer: 'Je suis dev, mettez-moi la totale.', - developerHint: "L'appli s'ouvre sur votre dernier projet, les conversations montrent le terminal et la trace des processus, le panneau s'ouvre sur les modifications.", - later: "Rien n'est retiré, dans un cas comme dans l'autre. Réglages, Apparence affiche ou masque chaque bouton, un par un." - }, - - welcome: { - title: 'Boite fait tourner vos agents', - body: 'Une conversation par tâche. Les agents continuent pendant que vous faites autre chose.' - }, - - agents: { - title: 'Une conversation qui suit votre travail', - body: "Changez d'agent, dictez un message, relisez le résultat. Sans changer de conversation.", - }, - - voice: { - title: 'Parler au lieu de taper', - body: "Appuyez sur le micro et parlez. Vous relisez le texte avant qu'il parte.", - open: 'Réglages de la voix', - reading: 'Lecture du moteur vocal' - }, - - panel: { - title: 'Le panneau à côté de la conversation', - body: 'Modifications, fichiers, tâches et navigateur, à une touche.', - open: 'Raccourcis clavier', - }, - - usage: { - title: "Voir ce qu'il vous reste", - body: "Voyez quel abonnement se remplit et quand il se réinitialise, même avec Boite en arrière-plan.", - monitor: 'Lire les limites de {account}', - connect: 'Connecter un fournisseur', - deviceHint: 'Les limites sont lues sur la machine qui héberge le cœur.' - }, - - reach: { - onPhoneTitle: "Votre ordinateur fait le travail", - onPhoneBody: "Ce téléphone affiche les conversations de votre ordinateur. Les agents tournent là-bas et continuent quand vous rangez le téléphone.", - onPhoneSettings: "Les notifications et l'application sur l'écran d'accueil sont dans les Réglages.", - title: 'Éloignez-vous. Gardez le fil.', - phone: 'Sur votre téléphone', - phoneBody: 'Scannez un QR code et votre téléphone retrouve les conversations de toutes vos machines, avec les notifications.', - pair: 'Ajouter un appareil', - machines: 'Un autre Boite', - machinesBody: 'Pilotez depuis cette application un Boite qui tourne sur un serveur ou un autre ordinateur.', - connect: 'Connecter une machine', - deviceHint: "L'appairage et les machines se gèrent depuis l'application où tourne le cœur." - }, - - quiet: { - title: 'Des agents qui ne vous coupent pas', - deviceHint: 'Le focus et le son sont des réglages de la machine qui héberge le cœur.' - }, - - project: { - title: 'Ouvrir votre premier projet', - body: 'Le dossier dans lequel un agent travaille.', - } - } + ...subscriptionProxy, + ...settingRequest, + ...agentSettingRequest, + ...remoteBrowser, + ...agentBrowser, + ...devicePanel, + ...agents, + ...previewComments, + ...artifacts, + ...brain, + ...hooks, + ...diagnostics, + ...telemetry, + ...speech, + ...phone, + ...mobile, + ...machines, + ...group, + ...app, + ...titlebar, + ...serverUpdate, + ...desktopUpdate, + ...appUpdate, + ...updateAll, + ...connection, + ...threadStatus, + ...drafts, + ...firstRun, + ...connect, + ...drop, + ...sidebar, + ...threadMove, + ...palette, + ...imports, + ...agentShape, + ...media, + ...btw, + ...slash, + ...activity, + ...mention, + ...notify, + ...permissionMode, + ...permissionModeLong, + ...permissionModeCodex, + ...permissionModeAgy, + ...thread, + ...chat, + ...agentMessages, + ...coordination, + ...resident, + ...contextMode, + ...delegation, + ...workflow, + ...composer, + ...rightPanel, + ...changes, + ...tasks, + ...files, + ...browserTools, + ...browser, + ...browserProfiles, + ...terminal, + ...trace, + ...protection, + ...taskManager, + ...resources, + ...install, + ...accounts, + ...usage, + ...controls, + ...settings, + ...keyboard, + ...experiments, + ...features, + ...quotas, + ...harnessUpdates, + ...providerSettings, + ...plugins, + ...time, + ...errors, + ...units, + ...effortLevels, + ...parts, + ...common, + ...onboarding, }; diff --git a/packages/ui/src/lib/strings/en/accounts.ts b/packages/ui/src/lib/strings/en/accounts.ts new file mode 100644 index 000000000..2abd6f9a5 --- /dev/null +++ b/packages/ui/src/lib/strings/en/accounts.ts @@ -0,0 +1,29 @@ +export const strings = { + + accounts: { + heading: 'Providers', + login: 'Sign in', + loginStarting: 'Starting the sign-in', + loginOpen: 'Open the sign-in page', + loginHint: 'Finish in your browser. This row turns ready on its own.', + loginInputPlaceholder: 'Paste the code the page gives you, if it gives one', + loginRedirectPlaceholder: 'On another device? Paste the localhost address the page ends on', + loginSend: 'Send', + terminalHint: 'Answer its questions in the terminal, then close it once you are signed in.', + terminalDone: 'Close the terminal', + remove: 'Remove', + removeTitle: 'Remove {account}?', + removeBody: 'This signs the account out of Boite and deletes its files.', + removeDefaultBody: 'Boite stops using this login. It stays signed in on your computer.', + removeThread: '1 conversation still uses this account. Its next message moves it to another signed-in account of the same agent, which reloads its whole context.', + removeThreads: '{count} conversations still use this account. The next message in each moves it to another signed-in account of the same agent, which reloads its whole context.', + removed: 'Account removed', + removedHint: 'The account of this conversation was removed. Choose a model on another account to continue.', + status: { + unknown: 'unknown', + ok: 'ok', + unauthenticated: 'not signed in', + error: 'error' + } + } +} as const; diff --git a/packages/ui/src/lib/strings/en/activity.ts b/packages/ui/src/lib/strings/en/activity.ts new file mode 100644 index 000000000..2405c9d31 --- /dev/null +++ b/packages/ui/src/lib/strings/en/activity.ts @@ -0,0 +1,67 @@ +export const strings = { + + activity: { + goal: 'Goal', + /** An agent's question it did not stop for (`boite ask`), docked above the composer like a goal. */ + question: 'Question', + questionOf: '{index} of {total}', + questionPrev: 'Previous question', + questionNext: 'Next question', + questionFold: 'Fold the question', + questionUnfold: 'Open the question', + /** A sub-agent's question or permission, docked in its parent: whose it is, and the way into its conversation. */ + subagentAsks: 'Asked by {title}', + subagentPermission: '{title} waits for your permission', + openSubagent: 'Open', + openSubagentHint: 'Open {title}', + loop: 'Loop', + tasks: 'Tasks', + taskCount: '{done}/{total} tasks', + goalDescription: 'Keep working toward an objective', + loopDescription: 'Repeat a task a set number of times or on a schedule', + goalUsage: 'Use /goal followed by an objective.', + /** `/goal2`, the Reviewed goals experiment: a fresh reviewer must pass the work. */ + goal2: 'Reviewed goal', + goal2Description: 'Work toward an objective until a fresh reviewer passes it', + goal2Usage: 'Use /goal2 followed by an objective.', + /** The goal's agent claimed completion and a reviewer conversation is checking it. */ + reviewing: 'Reviewing', + reviews: 'Reviews', + reviewRound: 'Review {round}', + reviewRoundOf: 'Review {round} of {total}', + reviewInProgress: 'Review {round} in progress', + openReview: 'Open the reviewer conversation', + pass: 'Pass', + partial: 'Partial', + fail: 'Fail', + missing: 'No verdict', + loopUsage: 'Use /loop 2 prompt for two iterations, or /loop 5m prompt for a schedule. Choose 1 to 1000 iterations.', + intervalError: 'Loop intervals must be between 1 second and 24 hours.', + active: 'Active', + paused: 'Paused', + complete: 'Complete', + /** A goal whose agent stopped on a blocker and waits for the user's answer. */ + blocked: 'Waiting for your answer', + blockedHint: 'Your next message resumes the goal.', + reply: 'Reply', + pause: 'Pause', + resume: 'Resume', + remove: 'Remove', + finish: 'Mark complete', + every: 'Every {interval}', + iterations: '{count} runs', + iteration: 'Iteration {count}', + iterationOf: 'Iteration {count} of {total}', + history: 'Iteration history', + noHistory: 'No completed iterations yet', + allTasksDone: 'All tasks completed', + waitingTasks: 'Waiting for next task', + stopped: 'Stopped', + running: 'Running', + done: 'Done', + error: 'Failed', + pending: 'Pending', + in_progress: 'In progress', + completed: 'Completed' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/agentBrowser.ts b/packages/ui/src/lib/strings/en/agentBrowser.ts new file mode 100644 index 000000000..43c1932b7 --- /dev/null +++ b/packages/ui/src/lib/strings/en/agentBrowser.ts @@ -0,0 +1,10 @@ +export const strings = { + /** The browser the conversation's agent drives on the machine that runs it, watched from any client. */ + agentBrowser: { + cover: 'This agent controls a browser on {machine}', coverHint: 'Nothing streams to this device until you show it.', + show: 'Show', hide: 'Hide', hideHint: 'Stop watching and cover the view again', + none: 'The agent has no browser open', noneHint: 'It appears here when the agent opens a page.', + unavailable: 'No agent browser on {machine}', tabs: 'The agent’s tabs', untitled: 'Untitled page', + newTab: 'New tab', closeTab: 'Close {name}', newTabHint: 'Open a page in this conversation’s browser on {machine}. The agent sees it and can use it.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/agentMessages.ts b/packages/ui/src/lib/strings/en/agentMessages.ts new file mode 100644 index 000000000..5b7aed0c8 --- /dev/null +++ b/packages/ui/src/lib/strings/en/agentMessages.ts @@ -0,0 +1,29 @@ +export const strings = { + + agentMessages: { + heading: 'Discussions', + open: 'Open the discussion', back: 'All discussions', openThread: 'Open this conversation', + empty: 'No discussion with another agent yet.', + /** The two halves of a discussion's count, for screen readers and hover. */ + sentReceived: '{sent} sent, {received} received', + /** A letter of this thread's own agent, in a preview. */ + mine: 'This agent', + /** A delegation prompt the user typed, on this thread's side. */ + you: 'You', + result: 'Result', + about: 'Works on', + replyTo: 'In reply to', + /** A burst touched more discussions than the timeline lists. */ + more: '+{count} more', + /** What a timeline row says above the other agent's name: which way the burst went. */ + sentOne: 'Sent to', sentMany: '{count} messages sent to', + receivedOne: 'Received from', receivedMany: '{count} messages received from', + exchanged: 'Exchange with', + /** A thread link in an answer that no connected machine or contact holds. */ + threadMissing: 'No connected machine has this conversation.', + /** Who started or delegated whom. Where a conversation runs is said by its project and machine, not a tag. */ + relation: { + starter: 'Started this thread', started: 'Started from here', parent: 'Parent', child: 'Subagent', resident: 'Looks after the project' + } + } +} as const; diff --git a/packages/ui/src/lib/strings/en/agentSettingRequest.ts b/packages/ui/src/lib/strings/en/agentSettingRequest.ts new file mode 100644 index 000000000..508f05ca5 --- /dev/null +++ b/packages/ui/src/lib/strings/en/agentSettingRequest.ts @@ -0,0 +1,35 @@ +export const strings = { + /** A card asking the user to change one setting of a persistent agent (`agents.settings.request`). */ + agentSettingRequest: { + heading: 'Suggested change to {name}', + apply: 'Apply', + show: 'Open its settings', + refuse: 'Refuse', + guestHint: 'Only the computer running Boite can change an agent\'s settings.', + projectsHint: 'Untick a project to leave it out.', + entrust: 'Entrust', + takeBack: 'Take back', + loadFailed: 'This agent setting card did not load.', + to: 'to', + applied: 'Applied', + changed: 'Set to {value} instead', + refused: 'Refused', + closed: 'Closed without an answer', + on: 'On', + off: 'Off', + none: 'None', + noProjects: 'No project', + defaultEffort: 'Default', + minutes: '{count} min', + /** A plain question a persistent agent asked without stopping, shown in its conversation. */ + send: 'Send', + keys: { + projects: 'Projects', + model: 'Model', + effort: 'Reasoning effort', + autoMemory: 'Update memory after each task', + subagents: 'Subagents', + maxRunMinutes: 'Maximum minutes per execution', + }, + } +} as const; diff --git a/packages/ui/src/lib/strings/en/agentShape.ts b/packages/ui/src/lib/strings/en/agentShape.ts new file mode 100644 index 000000000..d74b95b64 --- /dev/null +++ b/packages/ui/src/lib/strings/en/agentShape.ts @@ -0,0 +1,30 @@ +export const strings = { + + /** What a Claude thread's last request started with, from the context meter. */ + agentShape: { + open: 'What the agent sees', + title: 'What the agent sees', + intro: 'The system prompt and tools of the last request, as Claude Code recorded it.', + loading: 'Reading the transcript', + failed: 'Could not read the transcript: {error}', + noSession: 'No request yet: send a first message.', + noTranscript: 'The session transcript is not on this machine any more.', + unrecorded: 'This Claude Code version records no request details. Update it to 2.1.300 or later.', + system: 'System prompt', + tools: 'Tools', + block: 'Block {n}', + truncated: 'Cut here; the block holds {chars} characters.', + description: 'description {chars}', + omitted: '{n} more, counted in the total', + estimate: 'about {tokens} tokens', + estimateHint: 'Characters divided by four: an estimate, not the provider\'s count.', + chars: '{n} chars', + model: 'Model', + effort: 'Effort', + thinking: 'Thinking', + maxTokens: 'Max output', + betas: 'Betas', + version: 'Claude Code', + sent: 'Sent', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/agents.ts b/packages/ui/src/lib/strings/en/agents.ts new file mode 100644 index 000000000..46f18a13f --- /dev/null +++ b/packages/ui/src/lib/strings/en/agents.ts @@ -0,0 +1,109 @@ +export const strings = { + agents: { + runtime: "Models and limits", + brain: "Brain", + routines: "Routines", + defaultModel: "Default model", + allowedModels: "Allowed models for this agent", + subagents: "Subagent models", + enableSubagents: "Allow subagents", + addModel: "Add model", + removeRoute: "Remove this model", + compactEvery: "Compact after this many turns", + runMinutes: "Maximum minutes per execution", + runtimeHint: 'The default model must be allowed. Removing access stops the work that uses it and pauses it for review.', + brainHint: 'Instructions and skills persist across models. Shared contexts keep their own memory.', + compactNow: "Compact this context", + routineHint: "Your agents do these on their own, even with this window closed. A task still running is never started twice.", + runNow: "Run now", + schedule: "Schedule", + once: "Once", + interval: "At an interval", + daily: "Daily", + everyMinutes: "Every N minutes", + timezone: "Time zone", + accountAccess: "Account access", + accountAccessHint: 'Which agents may use each account, subagents included. Removing access stops the executions that use it.', + allAgents: "All agents, including future agents", + heading: 'Agents', intro: 'Agents with their own roles, memory and missions. Conversation subagents appear in Subagents.', back: 'Back to chat', + profiles: 'Agents', groups: 'Groups', teams: 'Teams', missions: 'Missions', attention: 'Needs attention', + list: 'All agents', create: 'New', edit: 'Edit', + search: 'Search', empty: 'Nothing here yet. Create your first entry.', select: 'Choose an entry to see its activity.', + name: 'Name', domain: 'Role', instructions: 'Instructions', identityHint: 'The identity and memory stay when you change its model.', + model: 'Model and account', noAccount: 'Add a provider account in Settings before creating an agent.', permissions: 'Permissions', + ask: 'Ask before actions', edits: 'Allow edits', plan: 'Plan only', full: 'Full access', deny: 'Do not ask', + noApprovals: 'This provider has no interactive tool approval channel. Its launch permissions apply; business decisions remain available.', + tools: 'Collaboration tools', messages: 'Messages', memory: 'Memory', artifacts: 'Results', decisions: 'Decisions', + members: 'Members', responsibility: 'Responsibility', description: 'Description', project: 'Project', noProject: 'No project', + team: 'Team', noTeam: 'No team', group: 'Discussion group', noGroup: 'No associated group', + mode: 'Who responds', mentions: 'Selected recipients', round: 'One response per member', autonomous: 'Take turns automatically', + maxTurns: 'Maximum turns', perAgent: 'Turns per agent', modeHint: 'Each new message starts an allowance. Replies keep the same allowance. Automatic exchanges stop at either limit.', + paused: 'Paused', pause: 'Pause', resume: 'Resume', archive: 'Archive', active: 'Active', archived: 'Archived', + archiveTitle: 'Archive "{name}"?', archiveBody: 'Archiving stops this agent’s work and removes it from the active list. Its profile and history remain available through search. Type its current name exactly to confirm.', archiveName: 'Agent name', + objective: 'Objective', expectedResult: 'Expected result', minutes: 'Total execution minutes', tokens: 'Token limit, blank for none', + finishMission: 'Finish mission', reopenMission: 'Reopen mission', + conversation: 'Chat', activity: 'Activity', send: 'Send', message: 'Write a message', recipients: 'Recipients', + user: 'You', noWork: 'No execution yet.', loadEarlier: 'Load earlier', + openRun: 'Open execution', requested: 'Requested', executed: 'Executed', noUsage: 'Usage not reported', + pending: 'Queued', running: 'Working', waiting: 'Waiting for a decision', done: 'Done', cancelled: 'Cancelled', interrupted: 'Interrupted', error: 'Failed', review: 'Ready for review', open: 'Open', assigned: 'Assigned', accepted: 'Accepted', + included: 'Included in execution', processed: 'Processed', failed: 'Failed', limited: 'Exchange limit reached', + reconcile: 'Review and resume', reconcileHint: 'Inspect the execution and files first. Describe what is already done and what can continue.', reconcileNote: 'What did you verify?', + answer: 'Answer', answerPlaceholder: 'Your decision and instructions', + task: 'Task', tasks: 'Tasks', addTask: 'Add task', acquire: 'Assign', dependencies: 'Wait for tasks', approve: 'Accept result', revise: 'Request changes', reject: 'Reject', feedback: 'Feedback for the next attempt', + result: 'Result', verification: 'Verification reported by the agent', workspace: 'Workspace', copyPath: 'Copy path', + resources: 'Resources', addResource: 'Add resource', directory: 'Directory', url: 'URL', value: 'Value', read: 'Read', write: 'Write', + resourceHint: 'Access declarations guide the agent. The provider’s permissions still control filesystem actions.', + addMemory: 'Add memory', text: 'Text', scope: 'Scope', sources: 'Sources', ownerMemory: 'Added by you', + searchKnowledge: 'Search memory and resources', expiresAt: 'Expires at, optional', expired: 'Expired', expireMemory: 'Expire now', + engine: 'Background work', concurrency: 'Concurrent agents', engineHint: 'Closing this page leaves work running on the connected core.', + idle: 'Available', offline: 'Agents could not be loaded from this machine. Reconnect or update its core.', retry: 'Retry', + stale: 'Connection lost. This is the last received state. Work may still be running on the host.', + history: 'History', + minutesUnit: 'minutes', status: 'Status', settings: 'Settings', overview: 'Overview', createAction: 'Create', rolePlaceholder: 'Research, implementation or review', + newTitle: { profile: 'New agent', group: 'New group', team: 'New team', mission: 'New mission' }, + summary: '{running} working · {pending} queued', + engineSettings: 'Engine settings', engineSettingsHint: 'How much background work this core runs, and which accounts its agents may use.', + advanced: 'More options', identity: 'Identity', noMembers: 'Create an agent first.', + brainFiles: 'Brain files', procedures: 'Procedures', proceduresHint: 'AGENTS.md in the brain folder. Reusable ways of working.', notes: 'Notes', notesHint: 'MEMORY.md in the brain folder. What this agent keeps between conversations.', + contexts: 'Conversation contexts', memories: 'Memories', toEveryone: 'Everyone', + missionMembers: 'Agents on this mission', dangerZone: 'Stop the engine', dangerHint: 'Stops every agent on this core and disconnects its clients.', + stopEngine: 'Stop this core', stopEngineBody: 'Stops all work on this core and disconnects its clients. Started work will need review before resuming. Reopen Boite on the host to start the core again.', + createAgent: 'Create an agent', noAgents: 'No agents yet', noAgentsOn: 'No agents on {machine} yet', more: 'More', noMatch: 'Nothing matches', + noMessagesYet: 'No messages yet', noMissions: 'No missions yet', previewFrom: '{name}: {text}', unread: '{count} unread', + backToChat: 'Back to the conversation', backTo: 'Back to {name}', editRoles: 'Edit roles', startChat: 'Start the conversation', + asks: '{name} asks you', + planned: 'Planned tasks', plannedResult: 'Planned task', + entrustTo: 'Entrust to an agent', entrustToName: 'Entrust to {name}', entrustedTo: 'Entrusted to {name}', takeBack: 'Take it back', + threadEvent: { entrusted: '{name} took over "{title}"', done: 'Done · {title}', blocked: 'Needs you · {title}', stopped: 'Stopped · {title}' }, + openThread: 'Open the thread', deliveryFailed: '{name} did not get it', + messageTo: 'Message {name}', sayHello: 'Say hello to {name}, or plan a task it will do on its own.', + liveWorking: '{name} is working', liveWaiting: '{name} needs you', seeThread: 'Open its thread', + welcomeHint: 'An agent keeps its name, its memory and its tasks. Talk to it, or plan work it does on its own.', + runsHeading: 'Where it runs', runsHint: 'Its conversations, memory and planned tasks stay on this machine. The models come from that machine’s accounts.', + machine: 'Machine', modelLabel: 'Model', machineOffline: 'Offline', machineNotOwner: 'Owner access needed', + machineUnreachable: '{name} is not connected. Reconnect it, then create the agent again.', whatItDoesHint: 'Its standing instructions. You can change them later in its settings.', + namePlaceholder: 'Mira, Scout, Pixel', whatItDoes: 'What it does', instructionsPlaceholder: 'For example: every morning, read the new issues and sum them up in five lines.', + ownedBy: '{name} runs this thread', ownedHint: 'Talk to {name} in its conversation. This thread shows what it does.', openAgent: 'Open {name}', + projects: 'Projects', projectsHint: 'It follows their threads, starts new ones, answers their questions and tells you what matters.', noProjects: 'Open a project first.', looksAfter: 'Looks after {names}', + seeWork: 'See its work', aboutProjects: 'About your projects', memoryUpdated: 'Memory updated', memoryUpdating: 'Updating memory', + autoMemory: 'Update memory after each task', autoMemoryHint: 'A small helper rewrites its notes from what you asked and what it answered.', + brainFolder: 'Brain folder', brainFolderHint: 'Where every agent keeps its instructions and notes on this machine. Changing it moves them all.', brainDefault: 'Default, in Boite’s data folder', + brainChange: 'Change', brainUseDefault: 'Use default', brainMove: 'Move brains here', brainPath: 'Folder on this machine', brainBrowse: 'Browse', brainParent: 'Parent folder', brainUseFolder: 'Use this folder', + robot: { + family: 'Style', shape: 'Shape', color: 'Color', eyes: 'Eyes', top: 'Accessory', shuffle: 'Shuffle', customize: 'Customize', done: 'Done', + option: '{part} {n}', families: { jelly: 'Jelly', bubble: 'Bubble', capsule: 'Capsule', retro: 'Retro' }, + }, + when: { + who: 'Agent', pickAgent: 'Choose an agent', what: 'What should it do?', whatPlaceholder: 'For example: sum up the new messages and tell me what needs an answer.', + when: 'When?', modes: { once: 'Once', daily: 'Every day', days: 'Some days', interval: 'Repeatedly' }, + inAnHour: 'In an hour', thisEvening: 'This evening, 18:00', tomorrowMorning: 'Tomorrow, 09:00', dateAndTime: 'Date and time', + otherInterval: 'Every', minutesUnit: 'minutes', at: 'At', inThePast: 'This date has passed. Pick a time to come.', pickDays: 'Pick at least one day.', + next: 'Next: {when}', name: 'Name', schedule: 'Plan it', newRoutine: 'Plan a task', scheduleThis: 'Plan this message as a task', scheduleShort: 'Plan', + everyMinutes: 'Every {count} minutes', everyHour: 'Every hour', everyHours: 'Every {count} hours', everyDay: 'Every day', everyDays: 'Every {count} days', + minutesShort: '{count} min', hoursShort: '{count} h', + onceAt: 'Once, {date}', daily: 'Every day at {time}', weekdays: 'Weekdays at {time}', weekend: 'Weekends at {time}', onDays: 'Every {days} at {time}', + today: 'today at {time}', tomorrow: 'tomorrow at {time}', dateAt: '{date} at {time}', + lastRun: 'Last run {when}: {state}', stillRunning: 'Its last run has not finished.', + }, + } +} as const; diff --git a/packages/ui/src/lib/strings/en/app.ts b/packages/ui/src/lib/strings/en/app.ts new file mode 100644 index 000000000..161b14047 --- /dev/null +++ b/packages/ui/src/lib/strings/en/app.ts @@ -0,0 +1,17 @@ +export const strings = { + app: { + name: 'Boite', + /** What a nightly build calls itself: window title and tray, never the title bar. The settings name its track Boite Nightly. */ + nightlyName: 'boite (de nuit)', + /** The tag the title bar shows when the core is the dev install. */ + channelDev: 'Dev', + channelDevTitle: 'The dev channel, on its own data directory', + loading: 'Starting', + /** While the core compacts its journal before serving, once after an update. */ + compacting: 'Freeing disk space in the history. This happens once and can take a few minutes.', + noEndpointTitle: 'No core to connect to', + noEndpointBody: + 'On one of your machines, open Settings, Machines, Add a device, and scan or type its code here.', + openSettings: 'Open settings' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/appUpdate.ts b/packages/ui/src/lib/strings/en/appUpdate.ts new file mode 100644 index 000000000..26495b36d --- /dev/null +++ b/packages/ui/src/lib/strings/en/appUpdate.ts @@ -0,0 +1,34 @@ +export const strings = { + appUpdate: { + released: 'Released {time}', + changelog: 'Changelog on GitHub', + heading: 'App updates', + intro: 'Choose the releases this desktop app follows. Stable and nightly share this installation, conversations and settings.', + unsupported: 'Automatic app updates are available in installed builds: the Windows x64 installer, the macOS app, and the Linux .deb or AppImage.', + channel: 'Release channel', + stable: 'Boite', + nightly: 'Boite Nightly', + nightlyHint: 'Nightly is experimental and may contain unfinished changes. You can return to Boite even when its version number is lower.', + installedVersion: 'Installed version', + idle: 'No update check has run yet.', + checking: 'Checking for updates', + available: 'Version {version} is available.', + downloading: 'Downloading {received} of {total}', + downloaded: 'Downloaded {received}', + ready: 'Version {version} is ready to install.', + waiting: 'Waiting for work to finish', + installing: 'Agents finish their current step, then the installer starts', + current: 'Boite is up to date.', + failed: 'The update failed.', + lastChecked: 'Checked at {time}', + progress: 'App update download', + notes: 'Release notes', + retained: 'Changing channels keeps your conversations and settings.', + check: 'Check for updates', + checkingAction: 'Checking', + install: 'Install and restart', + readyAction: 'Update', + installTitle: 'Install the Boite update?', + installBody: 'Agents finish their current tool call, 30 seconds at most. Boite then updates, restarts and resumes them.', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/artifacts.ts b/packages/ui/src/lib/strings/en/artifacts.ts new file mode 100644 index 000000000..278590ff4 --- /dev/null +++ b/packages/ui/src/lib/strings/en/artifacts.ts @@ -0,0 +1,3 @@ +export const strings = { + artifacts: { viewReplay: 'Play again', viewExpand: 'View full size', viewCollapse: 'Close full size', loadImage: 'Load image', mediaFailed: 'This media could not be previewed. Try again or download the file to open it.', videoFailed: 'This device cannot play this video. Download it to watch it in another app.', saving: 'Saving', zoomIn: 'Zoom in', zoomOut: 'Zoom out', fitImage: 'Fit image', openLocal: 'Open this file in its default application.', preview: 'Preview', download: 'Download', close: 'Close preview', closeImage: 'Close image', enlarge: 'View full size', saved: 'Saved in Downloads', loading: 'Loading file', unavailable: 'No inline preview for this file. Download it to open it.', failed: 'Could not open this file', ambiguous: (name: string, paths: string) => `${name} names several files: ${paths}`, ownerOnly: 'Local file links need an owner connection. Ask the agent to attach the file to share it with this device.' } +} as const; diff --git a/packages/ui/src/lib/strings/en/brain.ts b/packages/ui/src/lib/strings/en/brain.ts new file mode 100644 index 000000000..151cdb836 --- /dev/null +++ b/packages/ui/src/lib/strings/en/brain.ts @@ -0,0 +1,23 @@ +export const strings = { + + brain: { + heading: 'Brain', description: 'Your agents share the same instructions and skills.', + folder: 'Brain folder', pathHint: 'Folder path', save: 'Connect folder', browse: 'Browse', useFolder: 'Choose this folder', + enabled: 'Use with agents', disconnect: 'Disconnect', sync: 'Synchronize', working: 'Working', + empty: 'Connect your brain', emptyHint: 'Choose the folder with your instructions and skills.', + change: 'Change folder', + autoPull: 'Pull automatically', onStartup: 'At startup', periodic: 'Periodically', minutes: 'min', + globalInstructions: 'Global AGENTS.md', globalDetails: 'Connected harnesses', + globalHint: 'Uses this brain in every project, including outside Boite. Existing files are backed up and restored when disabled.', + boiteGuide: 'Boite guide', boiteGuideHint: 'A note after AGENTS.md when an agent session starts: the boite command and the panel. About 300 tokens.', + linked: 'Linked', existing: 'Already linked', blocked: 'Needs attention', + interval: 'Pull interval in minutes', intervalHint: 'Choose a whole number from 1 to 1440 minutes.', + detected: 'Contents', instructions: 'Instructions', skill: 'Skills', plugin: 'Plugins', + pluginHint: 'Detected here. Install plugins through their agent.', + details: 'Sync details', noGit: 'Local folder. Connect a Git repository to sync between computers.', + noUpstream: 'Set an upstream branch in Git to enable sync.', dirty: 'Commit your changes in Git before syncing.', + counts: '{ahead} to send, {behind} to receive', lastSync: 'Last synchronized', never: 'Not synchronized yet', + upToDate: 'Up to date', noEntries: 'Nothing detected in this category.', + missing: 'Update Boite on this machine to connect a brain.', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/browser.ts b/packages/ui/src/lib/strings/en/browser.ts new file mode 100644 index 000000000..d89ce926e --- /dev/null +++ b/packages/ui/src/lib/strings/en/browser.ts @@ -0,0 +1,21 @@ +export const strings = { + browser: { + resetViewport: 'Fit page to window', + enlarge: 'Enlarge browser', + restore: 'Restore panel', + detach: 'Float inside the app', + dock: 'Return to panel', + move: 'Move the floating panel', + loading: 'Loading page', + failed: 'The page could not be opened. {reason}', + invalidUrl: 'Enter a valid HTTP or HTTPS address, or a search query.', + resetZoom: 'Reset page zoom', + back: 'Back', + forward: 'Forward', + reload: 'Reload', + urlPlaceholder: 'Search or enter URL', + openExternal: 'Open in the system browser', + slotEmpty: 'The page opens here once the shell provides the webview', + slotLinux: 'The built-in browser is not available in the Linux app yet. Open the page in your own browser.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/browserProfiles.ts b/packages/ui/src/lib/strings/en/browserProfiles.ts new file mode 100644 index 000000000..b88ce7751 --- /dev/null +++ b/packages/ui/src/lib/strings/en/browserProfiles.ts @@ -0,0 +1,31 @@ +export const strings = { + + browserProfiles: { + /** The card in Settings, General: agent control and the profiles below it. */ + card: 'Built-in browser', + heading: 'Browser profiles', + hint: 'Each profile keeps its own cookies, storage and sign-ins in the built-in browser, and keeps them after a restart. A private tab keeps nothing once the last private tab closes.', + default: 'Default', + private: 'Private', + profile: 'Browser profile: {name}', + newTabIn: 'New tab in {name}', + newPrivateTab: 'New private tab', + manage: 'Manage browser profiles', + isDefault: 'Opens new tabs', + makeDefault: 'Open new tabs here', + renameLabel: 'New name for {name}', + addLabel: 'New profile name', + addPlaceholder: 'Work, Personal…', + add: 'Add profile', + invalidName: 'Choose a name of 1 to 40 characters that no other profile uses, other than Default or Private.', + tooMany: 'At most {count} browser profiles.', + deleteTitle: 'Delete the profile {name}?', + deleteBody: 'Its open tabs close, and its cookies, storage and sign-ins are erased from this computer. This cannot be undone.', + deleteFailed: 'The profile was removed, but its data could not be erased: {reason}', + copySignIns: 'Copy sign-ins to agents', + copySignInsHint: 'Send the cookies of {name} to the agent browser of a machine, into the same profile', + copied: '{count} cookies of {name} copied to {machine}.', + copiedNone: '{name} holds no cookie to copy.', + copyFailed: 'The sign-ins could not be copied: {reason}' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/browserTools.ts b/packages/ui/src/lib/strings/en/browserTools.ts new file mode 100644 index 000000000..1e5f61515 --- /dev/null +++ b/packages/ui/src/lib/strings/en/browserTools.ts @@ -0,0 +1,14 @@ +export const strings = { + + browserTools: { + title: 'Browser testing tools', desktop: 'Desktop', laptop: 'Laptop', portrait: 'Portrait', landscape: 'Landscape', + system: 'Follow system appearance', light: 'Light page', dark: 'Dark page', diagnostics: 'Page diagnostics and actions', + startRecording: 'Record this browser tab', stopRecording: 'Stop recording', reviewRecording: 'Recorded video', + clear: 'Clear', pageEvents: 'Page events', actions: 'Actions', noEvents: 'No page events captured.', noActions: 'No actions recorded yet.', + omitted: '{count} older or oversized events omitted.', recorded: '{seconds} seconds · {fps} fps · {mb} MB · silent {format}', + recordingError: 'Recording stopped after an error.', recordingLimit: 'Recording stopped by itself at the {mb} MB size limit. The video plays up to that point.', + frameRate: 'Record at {rate} frames per second', codec: 'Encode as {codec}', codecUnavailable: 'This computer cannot encode it', + unplayable: 'This view cannot play {codec} video. Download it to watch it in another player.', + download: 'Download video', discard: 'Discard video', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/btw.ts b/packages/ui/src/lib/strings/en/btw.ts new file mode 100644 index 000000000..c24e682e3 --- /dev/null +++ b/packages/ui/src/lib/strings/en/btw.ts @@ -0,0 +1,13 @@ +export const strings = { + /** The composer's slash menu: the agent's own commands over Boite's. */ + btw: { + fork: 'Fork', forking: 'Forking', + title: 'Side question', description: 'Ask a side question without interrupting the agent', + loading: 'Answering your side question', close: 'Close the side answer', + needsThread: 'Open a conversation before asking a side question.', + questionRequired: 'Type a question after /btw.', + textOnly: 'Side questions take text only. Remove attachments and preview references first.', + editing: 'Finish or cancel the message edit before asking a side question.', + offline: 'Reconnect this machine before asking a side question.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/changes.ts b/packages/ui/src/lib/strings/en/changes.ts new file mode 100644 index 000000000..5061739fc --- /dev/null +++ b/packages/ui/src/lib/strings/en/changes.ts @@ -0,0 +1,33 @@ +export const strings = { + + /** The changes surface: `git status` of the thread's working directory. */ + changes: { + clean: 'Working tree clean', + noBranch: 'No branch', + ahead: '{count} ahead', + behind: '{count} behind', + count: '{count} changed', + oneCount: '1 changed', + staged: 'Staged', + stagedShort: 'S', + binary: 'Binary file, nothing to show line by line', + truncated: 'This file was cut short: the diff below is not the whole file.', + pick: 'Pick a file to see its diff', + loading: 'Reading the working tree', + renamedFrom: 'Renamed from {path}', + /** A narrow panel shows the list or one diff: the way back, and the steps between files. */ + backToList: 'All changes', + previousFile: 'Previous file', + nextFile: 'Next file', + position: '{index} of {count}', + status: { + added: 'Added', + modified: 'Modified', + deleted: 'Deleted', + renamed: 'Renamed', + copied: 'Copied', + untracked: 'Untracked', + conflict: 'Conflict' + } + } +} as const; diff --git a/packages/ui/src/lib/strings/en/chat.ts b/packages/ui/src/lib/strings/en/chat.ts new file mode 100644 index 000000000..b32392305 --- /dev/null +++ b/packages/ui/src/lib/strings/en/chat.ts @@ -0,0 +1,211 @@ +export const strings = { + + chat: { + accepted: 'Request accepted', + responseStarted: 'Agent activity received', + working: 'Working', + writing: 'Writing', + stopped: 'Stopped', + inputTokens: '{count} input tokens', + outputTokens: '{count} output tokens', + cacheTokens: '{read} read from the cache, {write} written to it', + /** The footer of a turn: `Working 12s`, then `Worked 4m 41s`. */ + workingFor: 'Working for {time}', + workedFor: 'Worked for {time}', + compacting: 'Compacting conversation', + compactionElapsed: 'Elapsed: {time}', + turnOwnTime: 'This reply alone: {time}', + compactedFor: 'Compacted in {time}', + compactionFailed: 'Compaction failed', + compactionStopped: 'Compaction stopped', + progress: { starting: 'Starting agent', thinking: 'Thinking', compacting: 'Compacting context', retrying: 'Retrying request', tool: 'Running tool', working: 'Receiving activity', waiting: 'Waiting for provider' }, + providerSignal: 'Provider signal {time} ago', + noActivity: 'No new activity for {time}', + /** The jump button once something arrived below the reader. */ + newMessage: '1 new message', + newMessages: '{count} new messages', + findPlaceholder: 'Find in thread', + findNone: 'No match', + findCount: '{at} of {total}', + findPrevious: 'Previous match (Shift+Enter)', + findNext: 'Next match (Enter)', + finishedAt: 'done {time}', + /** `{what}` is a list of `backgroundOne` and `backgroundMany` joined by `backgroundJoin`. */ + backgroundRunning: '{what} still running', + forkOrigin: 'Forked from', forkReturn: 'Send conclusions back', forkSummary: 'Conclusions for the original conversation', + forkReturned: 'Conclusions sent to the original conversation', forkSourceUnavailable: 'Original conversation unavailable', + forkNative: 'Native context preserved', forkSeeded: 'Visible history copied', + backgroundJoin: ', ', + backgroundOne: { shell: '{count} shell', agent: '{count} agent', monitor: '{count} monitor', workflow: '{count} workflow', other: '{count} task' }, + backgroundMany: { shell: '{count} shells', agent: '{count} agents', monitor: '{count} monitors', workflow: '{count} workflows', other: '{count} tasks' }, + backgroundStop: 'Stop the agent and what it runs in the background', + /** The chip on a tool card whose work went on in the background. */ + backgroundChip: 'in the background', + /** A call's line while it runs and once it ran; `{subject}` is a file name, a pattern, a host or a program. */ + toolLive: { command: 'Running {subject}', read: 'Reading {subject}', edit: 'Editing {subject}', write: 'Writing {subject}', search: 'Searching {subject}', fetch: 'Fetching {subject}', web: 'Searching the web for {subject}', agent: 'Delegating {subject}', other: 'Running {subject}' }, + toolDone: { command: 'Ran {subject}', read: 'Read {subject}', edit: 'Edited {subject}', write: 'Wrote {subject}', search: 'Searched {subject}', fetch: 'Fetched {subject}', web: 'Searched the web for {subject}', agent: 'Delegated {subject}', other: 'Used {subject}' }, + /** The line of a call whose input names nothing to show. */ + toolBare: { command: 'Ran a command', read: 'Read a file', edit: 'Edited a file', write: 'Wrote a file', search: 'Searched the code', fetch: 'Fetched a page', web: 'Searched the web', agent: 'Delegated a task', other: 'Used a tool' }, + /** `Running {subject}` for a command whose program has no name. */ + toolCommandWord: 'a command', + /** A folded run of calls: one clause per kind, joined into one sentence. */ + toolRunOne: { command: 'Ran {count} command', read: 'Read {count} file', edit: 'Changed {count} file', search: 'Searched the code once', fetch: 'Fetched {count} page', web: 'Searched the web once', agent: 'Delegated {count} task', other: 'Used {count} tool', attempt: 'Attempted {count} call' }, + toolRunMany: { command: 'Ran {count} commands', read: 'Read {count} files', edit: 'Changed {count} files', search: 'Searched the code {count} times', fetch: 'Fetched {count} pages', web: 'Searched the web {count} times', agent: 'Delegated {count} tasks', other: 'Used {count} tools', attempt: 'Attempted {count} calls' }, + toolRunJoin: ', ', + toolRunLast: ' and ', + toolRunShow: 'Show each call', + toolRunHide: 'Hide the calls', + toolIssueOne: '{count} unsuccessful call', + toolIssueMany: '{count} unsuccessful calls', + system: 'System', + toolInput: 'Input', + toolOutput: 'Output', + toolDocuments: 'Documents', + /** Under a text the core cut because its message was too heavy to send whole. */ + textOmitted: 'This message is too long to show in full. {count} characters are left out.', + /** The chip on a folded card: `1 diff`, `2 diffs`, `1 doc`, `3 docs`. */ + documentChip: { + diff: '{count} diff', + diffs: '{count} diffs', + doc: '{count} doc', + docs: '{count} docs' + }, + documentImage: 'What the tool produced', + /** An image the user sent with the prompt, when it came with no name. */ + imagePart: 'Image sent with the prompt', + /** A picture whose bytes could not be fetched from its machine. */ + imageUnavailable: 'This picture could not be loaded. Scroll back to it to retry.', + diffHidden: '{count} unchanged lines', + diffIgnoreWhitespace: 'Ignore whitespace changes', + diffSideBySide: 'Old and new side by side', + /** A diff whose every change was spaces, with whitespace ignored. */ + diffWhitespaceOnly: 'Only whitespace changed.', + diffShowAll: 'Show all {count} lines', + diffAdded: '+{count}', + diffRemoved: '-{count}', + noOutput: 'No output', + toolExitCode: 'Exit code {code}', + toolCommandFailed: 'No diagnostic was reported. See the full output below.', + /** Under an expanded tool input that opened cut to six lines. */ + showAll: 'Show all', + toolStatus: { + running: 'running', + done: 'done', + error: 'error', + denied: 'denied' + }, + thinking: 'Thinking', + thinkingShow: 'Show the reasoning', + thinkingHide: 'Hide the reasoning', + /** The divider a compaction draws: `{pre}` and `{post}` formatted token counts. */ + compaction: 'Context compacted, {pre} to {post} tokens', + compactionNoPost: 'Context compacted from {pre} tokens', + compactionUnknown: 'Context compacted', + compactionManual: 'by hand', + /** The accent line on the first message after a move: the agent was told where it works now. */ + moveExplained: 'Move explained to the agent', + moveShow: 'Show what the agent was told', + moveHide: 'Hide what the agent was told', + /** At the foot of a thread the user moved: the note rides on the next prompt, not sent yet. */ + movePending: 'Moved to {project} · the agent will be told with your next message', + movePendingShow: 'Show what the agent will be told', + movePendingHide: 'Hide what the agent will be told', + /** A line of the timeline: the agent moved its own thread with `boite thread move`. */ + movedByAgent: 'Moved by the agent to {project}', + /** A line of the timeline: Boite pinged the cached prompt while the thread waits on its agents. */ + keepalive: 'Prompt cache kept warm', + keepaliveMany: 'Prompt cache kept warm {count} times', + keepaliveRunning: 'Keeping the prompt cache warm', + /** Above the first prompt of a thread another thread's agent started with `boite thread new`. */ + startedByAgent: 'Started by the agent of {title} in {project}', + /** Above the first prompt of a thread another agent started: the prompt is that agent's, not the user's. */ + promptFromAgent: 'Sent by the agent of', + /** A line of the starting thread's timeline: its agent started another thread. */ + agentStartedThread: 'The agent started {title} in {project}', + openLinkedThread: 'Open this thread', + /** One of the user's own hooks ended the turn (`part.type === 'hook'`). */ + hookBlocked: 'A hook blocked this message', + hookStopped: 'A hook stopped the turn', + /** The line above with what the hook said: `{label}` one of the two, `{message}` its words. */ + hookSays: '{label}: {message}', + /** What the agent asks to do, one sentence per kind of tool (`lib/tool-summary.ts`). */ + permissionAsk: { + command: 'Run a command', + edit: 'Change {file}', + editFiles: 'Change files', + editFolder: 'Write files in {folder} from now on', + write: 'Create or replace {file}', + read: 'Read {file}', + search: 'Search the files for {subject}', + fetch: 'Open {subject}', + web: 'Search the web for {subject}', + agent: 'Start a helper agent', + other: 'Use its {tool} tool' + }, + permissionTechnical: 'Technical details', + turnFiles: 'Changed files', + showTurnDiff: 'Show diff', + hideTurnDiff: 'Hide diff', + /** Opens the Changes panel: everything the working tree changed since its last commit, all turns together. */ + allChanges: 'All changes in this folder', + expandFileFolders: 'Expand all folders', + collapseFileFolders: 'Collapse all folders', + fileCreated: 'New', + fileChanged: 'Changed', + fileDeleted: 'Deleted', + openFile: 'Open {name}', + revealFile: 'Show in folder', + revealFailed: 'The folder could not be opened: {reason}', + allow: 'Allow', + deny: 'Deny', + allowed: 'Allowed', + denied: 'Denied', + questionHeading: 'Asks you', + questionAsyncHeading: 'Question', + questionAsyncHint: 'The agent keeps working. Without an answer it goes on with a default.', + questionAnswer: 'Answer', + questionAnswered: 'Answered', + questionCancelled: 'No longer waiting for an answer', + questionSkip: 'Skip', + /** The composer is the free field of the question; the card points there. */ + questionReplying: 'Write your answer in the message box below, with any photos or files.', + questionWrite: 'Answer in writing', + /** The timeline's line for a question waiting in the dock above the composer. */ + questionDocked: 'Waiting for your answer above the message box', + questionOpen: 'Answer it', + streaming: 'writing', + jumpToLatest: 'Jump to latest', + /** The one line at the top of the timeline while an older page is being fetched. */ + loadingOlder: 'Loading earlier messages', + loadingNewer: 'Loading later messages', + /** The view a thread shows while its page downloads. */ + opening: 'Loading the conversation', + /** Received against the total, both already formatted: "1.2 MB / 3.4 MB". */ + openingProgress: '{received} / {total}', + /** What a picture left on the core says until it is fetched: "Loading 240 kB". */ + partLoading: 'Loading {size}', + outline: 'Messages in this conversation', + messageGroup: 'Browse {count} messages', + earlierMessages: 'Load earlier messages', + /** The rail's own chevrons: they move its window over the conversation without moving the timeline. */ + outlineEarlier: 'Show earlier messages in the rail', + outlineLater: 'Show later messages in the rail', + goToMessage: 'Go to message {number}: {text}', + copied: 'Copied', + /** The card an agent's proposed plan is read in, before it starts editing. */ + plan: 'Plan', + planDownload: 'Download .md', + planSave: 'Save to folder', + planSaveHint: "Write the plan as a markdown file at the root of this thread's folder", + planSaved: 'Saved as {name}', + copyMessage: 'Copy message', + editMessage: 'Edit and send again from here', + retry: 'Retry this answer', + fork: 'Fork from here', + forkHere: 'Fork into a new thread', + forkWorktree: 'Fork into a new worktree', + /** A prompt's exact time, under its bubble: `Sent Tue 28 Sep 2026, 10:31:05`. */ + sentAt: 'Sent {time}', + error: 'Error' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/common.ts b/packages/ui/src/lib/strings/en/common.ts new file mode 100644 index 000000000..11c8e3cd0 --- /dev/null +++ b/packages/ui/src/lib/strings/en/common.ts @@ -0,0 +1,21 @@ +export const strings = { + + common: { + none: 'none', + unknown: 'unknown', + yes: 'yes', + no: 'no', + close: 'Close', + moreInfo: 'About {topic}', + refresh: 'Refresh', + dismiss: 'Dismiss', + cancel: 'Cancel', + /** Verbs every view shares. A status word stays with its subject: French agrees it in gender. */ + retry: 'Try again', + save: 'Save', + rename: 'Rename', + delete: 'Delete', + copy: 'Copy', + open: 'Open', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/composer.ts b/packages/ui/src/lib/strings/en/composer.ts new file mode 100644 index 000000000..a7fbdae95 --- /dev/null +++ b/packages/ui/src/lib/strings/en/composer.ts @@ -0,0 +1,106 @@ +export const strings = { + + composer: { + /** Above the box while a sent message is being edited. */ + editing: 'Editing a sent message: sending replaces it and everything after it', + editingQueued: 'Send or remove the queued messages before replacing a sent message.', + filesUnavailable: 'The messages were rewound, but this edit has no complete file backup. The code could not be restored.', + editingCancel: 'Stop editing (Esc)', + /** A question waits for a written answer: the box writes it, the row above names the question. */ + replying: 'Answering: {question}', + replyPlaceholder: 'Write your answer', + replySend: 'Send the answer', + replyIgnore: 'Write a message', + replyIgnoreHint: 'Leave the question waiting and write an ordinary message', + switchTitle: 'Switch a {tokens} token thread to {provider}?', + switchBody: '{provider} starts a new session and receives excerpts of the opening and of the most recent exchanges, about 20k tokens. The rest is lost to it. Compacting first changes nothing.', + switchConfirm: 'Switch', + switchCancel: 'Stay on {provider}', + cacheTitle: 'Rebuild the cache of a {tokens} token thread?', + cacheBody: 'This change may make the provider process the whole conversation again uncached on the next turn, using more quota. Changing a model or reasoning effort can affect the cache, as can enabling Claude Fast mode for the first time.', + cacheConfirm: 'Change', + cacheCancel: 'Keep as is', + options: 'Message options', + placeholder: 'Message {provider} in {project}', + placeholderNoProject: 'Message the agent', + /** A new conversation, before anything is sent. */ + placeholderNew: 'What do you want to do?', + placeholderOffline: '{machine} is offline: messages wait here and go out when it reconnects', + send: 'Send', + stop: 'Stop', + queued: 'Sent after the next tool, or when the turn ends', + /** A refused send held the pending messages until the user sends again. */ + queuedPaused: 'Held after a refused send', + sendNow: 'Send now', + queueWaitForEnd: 'Goals and loops wait for the current turn to finish. Your command remains queued.', + queueTurnNotReady: 'The running turn has not reached this client yet. Your message remains queued.', + queueNotAccepted: 'This turn did not accept the queued input. It remains queued and will be sent when the turn ends.', + sendNowHint: 'Sends pending messages to the running agent without stopping it. Enter in the empty composer does the same.', + retryQueuedHint: 'Sends all pending messages together again. Enter in the empty composer does the same.', + /** The outbox: messages written while the machine was away, kept on this device. */ + outboxKept: 'Kept on this device and sent in order, once each', + outboxWaiting: 'Waiting for {machine}', + outboxNext: 'Goes out when the thread is free', + outboxSending: 'Sending…', + outboxFailed: 'Not sent: {reason}', + outboxRetry: 'Send again', + outboxSettings: 'The model and effort it was written with could not be set again.', + removeQueued: 'Remove this pending message', + picker: 'Provider and model', + models: 'Models', + searchModels: 'Search models', + noModels: 'No model matches', + noModelsAvailable: 'The agent has not listed any models. Refresh to retry.', + showAllModels: 'Show all {count} models', + legacyModels: 'Legacy models', + refreshModels: 'Refresh models', + probing: "Reading the agent's models", + notInstalled: 'Not installed on this machine', + installInSettings: 'Install in Settings', + effortTitle: 'Reasoning effort', + speed: 'Response speed', + /** The reasoning chip of a model whose agent keeps its own level until one is picked. */ + effortDefault: 'Default', + effortDefaultHint: 'Let the agent pick its own level for this model', + standardSpeed: 'Standard speed', + favorites: 'Favorites', + favorite: 'Add to favorites', + unfavorite: 'Remove from favorites', + favoritesEmpty: 'Star a model to find it here.', + favoriteUnavailable: 'This favorite is no longer offered by the account. Choose another model.', + compact: 'Compact context', + compactBusy: 'Wait for the current turn to finish', + compactUnavailable: 'This agent has not advertised context compaction', + compactNoSession: 'Send a message before compacting', + newBadge: 'new', + lockedHint: 'A thread keeps its provider and account', + mode: 'Permissions', + model: 'Model', + unavailable: 'not installed', + noAccount: 'no account', + noProvider: 'No AI connected', + /** The draft's worktree switch, off and on. */ + worktree: 'Worktree', + worktreeOff: 'Start in a git worktree on its own branch', + worktreeOn: 'Starts in a git worktree on its own branch', + /** Images sent with the prompt: the strip above the box and what refuses one. */ + attach: 'Attach files', + editQueued: 'Edit this pending message, or press Up in the empty composer', + attachReadError: '{name} could not be read.', + attachRemove: 'Remove {name}', + attachAlt: 'Attached file', + imageReference: '[Image {number}]', + imagePreview: 'Preview {image}', + imagePreviewClose: 'Close image preview', + attachPending: 'Reload to recover', + /** The last tile of a full strip: how many more it hides, and folding them back. */ + attachMore: 'Show {count} more', + attachLess: 'Show fewer', + attachUnnamed: 'the attachment', + attachFormat: '{name} is {type}, and an image must be one of {formats}.', + attachTooLarge: '{name} is too big: a file may weigh {max} at most.', + attachTooMany: 'A turn carries at most {max} files, so {name} was left out.', + attachTotalTooLarge: 'The files of one turn weigh {max} at most together, so {name} was left out.', + attachNoImages: '{provider} takes no images: send the prompt without them.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/connect.ts b/packages/ui/src/lib/strings/en/connect.ts new file mode 100644 index 000000000..405d425f6 --- /dev/null +++ b/packages/ui/src/lib/strings/en/connect.ts @@ -0,0 +1,29 @@ +export const strings = { + + /** The guided connection: from "nothing works yet" to a composer on a signed-in agent. */ + connect: { + button: 'Connect an AI', + title: 'Connect an AI', + intro: 'Boite works with an AI subscription you already have. Pick it, Boite installs what it needs and opens its sign-in page.', + more: 'Other agents', + plan: { + claude: 'Uses a Claude Pro or Max plan', + codex: 'Uses a ChatGPT plan' + }, + otherPlan: 'Uses your {provider} account', + install: 'Install {provider}', + installNote: 'Downloads {size}. Nothing else changes on this computer.', + signIn: 'Sign in to {provider}', + signInNote: 'Your browser opens the sign-in page. Come back here once it says you are connected.', + manual: '{provider} installs with its own installer. Run it, then check again here.', + manualOpen: 'Open the installer page', + external: '{provider} signs in from its own window. Sign in there, then check again.', + checkAgain: 'Check again', + ready: '{provider} is connected.', + use: 'Use {provider}', + back: 'Back', + device: 'No AI is connected on the computer running Boite yet. Connect one from that computer.', + reconnect: 'Sign in again', + signedOut: '{provider} is signed out' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/connection.ts b/packages/ui/src/lib/strings/en/connection.ts new file mode 100644 index 000000000..9bcde059d --- /dev/null +++ b/packages/ui/src/lib/strings/en/connection.ts @@ -0,0 +1,23 @@ +export const strings = { + + connection: { + machine: 'Machine', + local: 'This PC', + current: 'Current machine', + machines: 'machines connected', + issues: 'need attention', + manage: 'Manage machines', + folder: 'Folder', + browse: 'Browse folders', + nativeBrowse: 'Choose in Windows Explorer', + parent: 'Parent folder', + emptyFolder: 'No subfolders', + unavailable: 'Could not connect', + idle: 'Not connected', + connecting: 'Connecting', + ready: 'Connected', + closed: 'Disconnected', + offline: 'Offline', + label: 'Connection' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/contextMode.ts b/packages/ui/src/lib/strings/en/contextMode.ts new file mode 100644 index 000000000..3f9c61217 --- /dev/null +++ b/packages/ui/src/lib/strings/en/contextMode.ts @@ -0,0 +1,16 @@ +export const strings = { + contextMode: { + light: 'Light', + neutral: 'Neutral', + custom: 'Custom', + rules: 'rules', + heading: '{label} context', + without: 'Started without: {parts}.', + instructions: 'instruction files', + guides: 'Boite and brain guides', + mcp: 'MCP servers', + hooks: 'hooks', + tools: 'Tools: {tools}.', + withRules: 'Its rules: {rules}' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/controls.ts b/packages/ui/src/lib/strings/en/controls.ts new file mode 100644 index 000000000..a6653a557 --- /dev/null +++ b/packages/ui/src/lib/strings/en/controls.ts @@ -0,0 +1,21 @@ +export const strings = { + + /** The buttons a device shows or puts away, on the Appearance page and in a button's right click. */ + controls: { + heading: 'Buttons', + headingHint: 'A hidden button stays reachable from the command palette and the keyboard shortcuts.', + preset: 'Preset', + everyday: 'Essentials', + developer: 'Everything', + header: 'Conversation header', + composer: 'Message bar', + sidebar: 'Sidebar', + panel: 'Side panel menu', + project: 'Project name', + branch: 'Branch', + context: 'Context gauge', + hide: 'Hide this button', + customize: 'Choose the buttons', + allHidden: 'Every card is hidden on this device.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/coordination.ts b/packages/ui/src/lib/strings/en/coordination.ts new file mode 100644 index 000000000..a93baf081 --- /dev/null +++ b/packages/ui/src/lib/strings/en/coordination.ts @@ -0,0 +1,24 @@ +export const strings = { + coordination: { + options: 'Advanced settings', + heading: 'Communication between conversations', off: 'Off', on: 'On', + summaryOff: 'Other agents cannot find, read or contact this thread.', + summaryOn: 'Other agents here and on linked machines can find this thread, read its conversation and message it.', + ownerOnly: 'Only the owner can change coordination. This device can inspect the directory and exchanges.', + resources: 'Resources and responsibilities', resourcesPlaceholder: 'What this agent owns, can answer, or should avoid', + remote: 'Across projects and machines', remoteHint: 'Let agents in other projects and on linked machines find this thread, read its conversation and message it. Both threads must enable this.', + pause: 'Pause coordination', resume: 'Resume coordination', paused: 'Paused', + sends: '{used} of {limit} sends this hour', wakes: '{used} of {limit} wake turns this hour', receives: 'Recipients accept up to {limit} letters each hour.', + sent: '{used} sent this hour', woken: '{used} wake turns this hour', + directory: 'Authorized contacts', directoryEmpty: 'No opted-in thread in this project is available.', unavailable: 'Unavailable contacts', + details: 'Show exchange', + machineNotConnected: 'Connect to this machine in Settings to open its thread.', + status: { + queued: 'Queued in the outbox', received: 'Received by the recipient core', delivered: 'Submitted to the recipient agent', + uncertain: 'Submission outcome uncertain. Boite will not replay it.', expired: 'Expired', rejected: 'Rejected' + }, + bubbleStatus: { + queued: 'Waiting', received: 'Waiting', delivered: 'Sent', uncertain: 'Unconfirmed', expired: 'Expired', rejected: 'Failed' + } + } +} as const; diff --git a/packages/ui/src/lib/strings/en/delegation.ts b/packages/ui/src/lib/strings/en/delegation.ts new file mode 100644 index 000000000..a63573aa4 --- /dev/null +++ b/packages/ui/src/lib/strings/en/delegation.ts @@ -0,0 +1,52 @@ +export const strings = { + delegation: { + off: 'Off', + paused: 'Paused', + heading: 'Subagents', + nativeHeading: 'Started by the provider', + nativeNoResult: 'No result reported yet.', + processHeading: 'Started from a command', + processResult: 'Progress and results are in the command output.', + nativeStatus: { running: 'Working', done: 'Completed', error: 'Failed', stopped: 'Stopped', unknown: 'Status unknown' }, + parent: 'Parent thread', + activeOne: '1 active subagent', + activeMany: '{count} active subagents', + configure: 'Settings', + enabled: 'Let this conversation start subagents', + enabledHint: 'The agent can hand parts of a task to other agents. Each one works in its own conversation and reports back here.', + anyModel: 'Let the agent choose the model', + anyModelHint: 'For each task the agent picks a model and how hard it thinks, for example Codex for a code review. Off, subagents use this conversation\'s model or the models below.', + profiles: 'Suggested models', + profilesHint: 'The agent sees these by name. Add one to suggest a model and reasoning level for a kind of task.', + empty: 'No subagents yet. Ask the agent to split the work: each subagent appears here and its conversation opens with one click.', + addProfile: 'Add a model', + profileName: 'Model {count}', + profileLabel: 'Model for the subagent', + removeProfile: 'Remove this model', + pause: 'Pause subagents', + resume: 'Resume subagents', + backToTeam: 'Back to the subagents', + stopAll: 'Stop all', + send: 'Send to the subagent', + startedOne: 'Started 1 subagent', + startedMany: 'Started {count} subagents', + completed: '{done}/{total} completed', + failed: '{count} failed', + stopped: '{count} stopped', + elapsed: 'Elapsed time', + showTeam: 'View subagents', + stoppedStatus: 'Stopped', + failedStatus: 'Failed', + /** Its turn ended on its own background work, which wakes it: not finished. */ + waitingStatus: 'Waiting', + /** That work ended without waking it: its result never came. */ + interruptedStatus: 'Interrupted', + interrupted: '{count} interrupted', + resumeAgent: 'Resume', + resumeAgentHint: 'Ask this subagent to check how its background work ended and finish its task', + /** What Resume sends the interrupted subagent. */ + resumeAgentText: 'Your background work ({what}) ended without waking you: {reason}. Check how it ended, then finish your task.', + activeSection: 'Active', + finishedSection: 'Finished' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/desktopUpdate.ts b/packages/ui/src/lib/strings/en/desktopUpdate.ts new file mode 100644 index 000000000..285119b41 --- /dev/null +++ b/packages/ui/src/lib/strings/en/desktopUpdate.ts @@ -0,0 +1,13 @@ +export const strings = { + desktopUpdate: { + heading: 'Desktop app', + progress: 'Desktop app update download', + ready: 'Downloaded, ready to install', + installing: 'Installing, Boite restarts there', + current: 'Up to date', + elsewhere: 'Install this update on that machine itself: its installer may ask for a confirmation.', + confirmTitle: 'Update Boite on {machine}?', + confirmBody: 'Version {version} installs once the agents there are done. Boite restarts on {machine} and running work resumes.', + requested: 'Another machine is installing Boite {version} here. Boite restarts in a moment.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/devicePanel.ts b/packages/ui/src/lib/strings/en/devicePanel.ts new file mode 100644 index 000000000..ab8e58502 --- /dev/null +++ b/packages/ui/src/lib/strings/en/devicePanel.ts @@ -0,0 +1,18 @@ +export const strings = { + /** The Device panel: simulators and emulators an agent opened with `boite device`. */ + devicePanel: { + cover: 'This agent controls a device on {machine}', coverHint: 'Nothing streams to this device until you show it.', + show: 'Show', hide: 'Hide', hideHint: 'Stop watching and cover the view again', + empty: 'No device is open in this conversation. An agent opens one with boite device open, or open one here.', + onMachine: 'On this machine', add: 'Open a device', + none: 'No simulators or emulators were found on this machine.', + starting: 'Starting {name}. This can take a minute.', failed: '{name} did not start', + stopped: 'Off', running: 'Running', booting: 'Starting', + viewOnly: 'View only. The agent drives iOS Simulators with xcrun simctl.', + back: 'Back', home: 'Home', recents: 'Recent apps', rotate: 'Rotate', power: 'Power button', enter: 'Enter', backspace: 'Delete the previous character', + text: 'Type into the focused field', send: 'Send the text', ascii: 'Only printable ASCII characters reach the device.', + screenshot: 'Save a screenshot', close: 'Close {name}', shutdown: 'Power off', + interact: 'Tap or swipe the device screen', interactDesk: 'The device screen: click it, then type', + deskHint: 'Click the screen, then type. Drag to swipe, scroll with the wheel, Esc goes back. Paste types the clipboard; copying from the device is not available.', image: 'Live view of {name}', gesture: 'Tap to touch. Drag to swipe.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/diagnostics.ts b/packages/ui/src/lib/strings/en/diagnostics.ts new file mode 100644 index 000000000..c2219b6b5 --- /dev/null +++ b/packages/ui/src/lib/strings/en/diagnostics.ts @@ -0,0 +1,72 @@ +export const strings = { + diagnostics: { + heading: 'Diagnostics', + intro: 'What this machine recorded about its own problems. An export or an issue is anonymized: paths, names and addresses become placeholders, thread ids stay.', + status: 'Status', + loading: 'Reading the logs', + version: 'Boite', + system: 'System', + uptime: 'Core running for', + lastDay: 'Last 24 hours', + levels: { debug: 'debug', info: 'info', warn: 'warnings', error: 'errors' }, + files: 'Log files', + noFiles: 'No log file yet', + problems: 'Problems', + problemsHint: 'Warnings and errors of the last 24 hours, grouped by kind, errors first.', + problemsEmpty: 'No warning or error in the last 24 hours.', + times: '{count}×', + lastSeen: 'last {time}', + latest: 'Latest message', + threads: 'Threads it touched', + missingThread: 'not on this machine', + log: 'Log', + level: 'Level', + atLeast: { debug: 'Everything', info: 'Info and above', warn: 'Warnings and errors', error: 'Errors only' }, + origin: 'Source', + origins: { all: 'All sources', core: 'Core', shell: 'Desktop app', ui: 'Interface' }, + thread: 'Thread', + allThreads: 'All threads', + search: 'Search the log', + period: 'Period', + periods: { '15m': 'Last 15 minutes', '1h': 'Last hour', '24h': 'Last 24 hours', '7d': 'Last 7 days' }, + loadOlder: 'Load older', + noOlder: 'Nothing older in this period.', + empty: 'No record matches these filters.', + live: 'New records appear while this page is open.', + duration: 'Duration', + details: 'Details', + ids: 'Ids', + agent: 'Agent', + parent: 'Started by', + turn: 'Turn', + request: 'Request', + run: 'Run', + export: 'Export anonymized logs', + exportHint: 'The last 24 hours in one text file a developer can read: environment, grouped problems, threads and the timeline.', + exportDone: 'Saved {name} ({size}).', + exportDownloaded: 'Downloaded {name} ({size}).', + report: 'Report a problem', + reportHint: 'Drafts an issue on GitHub for Boite with your description and, if you agree, the anonymized logs.', + title: 'Title', + titlePlaceholder: 'What went wrong, in a few words', + description: 'What happened', + descriptionPlaceholder: 'What you did, what you expected and what you saw', + includeLogs: 'Include anonymized logs', + preview: 'Preview', + previewHint: 'This is the text the issue will contain.', + previewUpdate: 'Update the preview', + submit: 'Create the issue', + openGithub: 'Open on GitHub', + ghMissing: 'GitHub CLI is not set up on this machine, so the issue opens in your browser. Drag {file} into it to attach the full logs.', + ghMissingNoFile: 'GitHub CLI is not set up on this machine, so the issue opens in your browser.', + opened: 'The issue opened in your browser.', + created: 'Issue created:', + cancel: 'Cancel', + close: 'Close', + agentAccess: 'Agents can read anonymized logs', + agentAccessHint: 'An agent can check whether Boite itself failed and draft an issue about it. It reads the anonymized view only, never the files on disk.', + failed: 'Diagnostics did not load: {reason}', + outdated: 'Boite on {machine} is older than these diagnostics: its log shows below, but the status, the export and the report need an update. Update it in Settings, Machines and updates.', + outdatedHere: 'Boite on this machine is older than these diagnostics: its log shows below, but the status, the export and the report need an update. Update it in Settings, Machines and updates.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/drafts.ts b/packages/ui/src/lib/strings/en/drafts.ts new file mode 100644 index 000000000..60d058aed --- /dev/null +++ b/packages/ui/src/lib/strings/en/drafts.ts @@ -0,0 +1,13 @@ +export const strings = { + + drafts: { + name: 'Drafts', + hint: 'A folder of its own for each conversation, in Documents/Boite', + openFolder: 'Work in a folder of mine', + pickFolder: 'Open a folder', + incognito: 'Incognito', + incognitoStart: 'Start this conversation incognito: nothing of it is kept once you leave it', + incognitoStop: 'Start an ordinary conversation instead', + incognitoOn: 'Incognito: this conversation and its folder are erased when you leave it' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/drop.ts b/packages/ui/src/lib/strings/en/drop.ts new file mode 100644 index 000000000..50c0e71c1 --- /dev/null +++ b/packages/ui/src/lib/strings/en/drop.ts @@ -0,0 +1,10 @@ +export const strings = { + + drop: { + title: 'Drop to add a project', + body: 'Each folder becomes a project. Files go to the message.', + attachTitle: 'Drop to attach', + attachBody: 'The files go to the message you are writing.', + noComposer: 'Open a conversation to attach the dropped files.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/effortLevels.ts b/packages/ui/src/lib/strings/en/effortLevels.ts new file mode 100644 index 000000000..1c8567bc3 --- /dev/null +++ b/packages/ui/src/lib/strings/en/effortLevels.ts @@ -0,0 +1,17 @@ +export const strings = { + + /** Effort and speed levels by the id the providers share; another id keeps the provider's own word. */ + effortLevels: { + none: 'None', + off: 'Off', + minimal: 'Minimal', + low: 'Low', + medium: 'Medium', + high: 'High', + xhigh: 'Xhigh', + max: 'Max', + ultra: 'Ultra', + ultrathink: 'Ultrathink', + fast: 'Fast' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/errors.ts b/packages/ui/src/lib/strings/en/errors.ts new file mode 100644 index 000000000..ca700d68e --- /dev/null +++ b/packages/ui/src/lib/strings/en/errors.ts @@ -0,0 +1,21 @@ +export const strings = { + + errors: { + draftStorage: 'This device could not save your draft. Keep this window open and copy your text before closing it.', + draftAttachment: 'An attachment could not be read from storage. Reload to recover it, or remove it before sending.', + prefix: 'Error', + activityUnsupported: 'This core does not support goals or loops. Update Boite on {machine}, then reconnect. Your command has not been sent.', + pullRequestUnsupported: 'Update Boite on the machine hosting this thread to show its pull request. This core does not support pull request lookup yet.', + noEndpoint: 'No core endpoint could be resolved.', + coreStart: 'The core on this computer did not start: {reason}', + connect: 'Could not connect to the core.', + clipboard: 'The clipboard refused the text.', + revoked: 'This device was removed from the group. Add it again with a new code from one of your machines.', + unpaired: 'This device is not in this machine\'s group yet. Add it with the code from Add a device.', + olderCore: 'Boite on {machine} is too old for this. Update it in Settings, Machines and updates, then try again.', + olderCoreHere: 'This machine runs a Boite too old for this. Update it in Settings, Machines and updates, then try again.', + notConnected: '{machine} is not connected. Boite reconnects by itself once it answers again.', + notConnectedHere: 'This machine is not connected. Boite reconnects by itself once it answers again.', + pairingLink: 'That is not a link to add a device. Copy the link shown by Add a device on the other machine; it starts with http and contains ?grant=.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/experiments.ts b/packages/ui/src/lib/strings/en/experiments.ts new file mode 100644 index 000000000..df6458cac --- /dev/null +++ b/packages/ui/src/lib/strings/en/experiments.ts @@ -0,0 +1,39 @@ +export const strings = { + + experiments: { + recordingIndicators: { title: 'Show actions in recordings', hint: 'Add click markers and navigation keys to browser recordings. Typed text and password input are never shown as key labels.' }, + openChatLinks: { title: 'Open chat links in their apps', hint: 'On this desktop, clicking a local file or folder opens its default app, including shortcuts and files outside the project. Only links from this computer are opened.' }, + whip: { + title: 'Whip', + hint: 'An animated whip follows your pointer or finger. Flick to crack it, click or tap to drop it', + action: 'Throw the whip', + drop: 'Drop the whip' + }, + previewComments: { title: 'Preview comments', hint: 'Select an element in the integrated browser and add feedback to the conversation draft' }, + goal2: { title: 'Reviewed goals', hint: 'Adds /goal2: when the agent says the goal is done, a new conversation that has not seen its work checks it and answers pass, partial or fail. Anything but a pass goes back to the agent, for up to five reviews' }, + themeGrain: { + title: 'Grain theme', + hint: 'A grainy ground fading from light grey at the top left to black at the bottom right' + }, + sessionImport: { + title: 'Claude Code session import', + hint: 'Turn a session started in the terminal into a thread, from the project menu and the palette' + }, + promptCache: { + title: 'Prompt cache timer', + hint: 'A clock beside the context meter counting down how long the provider keeps the conversation cached' + }, + residentAgents: { + title: 'Agents', + hint: 'Resident agents with their own brain, missions and routines, from the sidebar. Agents you already made keep running while this is off' + }, + edgeScrollbars: { + title: 'Edge scrollbars', + hint: 'Scrollbars take no room. Each one fades in as the pointer nears the edge of its area and can still be dragged. Mouse and pen only' + }, + pressPreload: { + title: 'Load on press', + hint: 'A thread or page starts loading when you press it, before you release the click or lift your finger' + } + } +} as const; diff --git a/packages/ui/src/lib/strings/en/features.ts b/packages/ui/src/lib/strings/en/features.ts new file mode 100644 index 000000000..31870395d --- /dev/null +++ b/packages/ui/src/lib/strings/en/features.ts @@ -0,0 +1,7 @@ +export const strings = { + + /** Finished features, on by default, each switched from the Settings page it belongs to. */ + features: { + chatArtifacts: { title: 'Chat files and previews', hint: 'Open file links and preview files returned by an agent in the conversation' }, + } +} as const; diff --git a/packages/ui/src/lib/strings/en/files.ts b/packages/ui/src/lib/strings/en/files.ts new file mode 100644 index 000000000..d37093c0a --- /dev/null +++ b/packages/ui/src/lib/strings/en/files.ts @@ -0,0 +1,37 @@ +export const strings = { + + /** The file tree and the file viewer, the two surfaces reading the working directory. */ + files: { + tree: 'Files of the working directory', + noRoot: 'No working directory', + filter: 'Filter', + filterHint: 'Narrow the rows already loaded', + reading: 'Reading this directory', + empty: 'This directory is empty', + noMatch: 'No loaded row matches', + failed: 'This directory could not be read. {reason}', + capped: 'Only the first {count} entries of this directory are listed.', + // The viewer and the editor, one file per tab. + readFailed: 'This file could not be read. {reason}', + writeFailed: 'This file could not be saved. {reason}', + readingFile: 'Reading this file', + noFile: 'This tab carries no path', + truncated: 'This file was cut short at its size limit, so what is below is not the whole file and cannot be saved.', + readOnly: "Saving a file is the owner's, so this one opens read only.", + saving: 'Saving', + unsaved: 'Unsaved changes', + discardOne: 'Close {name} without saving?', + discardMany: 'Close {count} files without saving?', + discardBody: 'What was typed since the last save is lost.', + discardConfirm: 'Close without saving', + editorLabel: 'File contents', + zoomIn: 'Zoom in', + zoomOut: 'Zoom out', + fit: 'Fit', + actual: '1:1', + natural: '{width} x {height}', + picture: 'The picture of this file', + download: 'Download this file', + binary: 'Nothing here reads as text.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/firstRun.ts b/packages/ui/src/lib/strings/en/firstRun.ts new file mode 100644 index 000000000..6996c5e6b --- /dev/null +++ b/packages/ui/src/lib/strings/en/firstRun.ts @@ -0,0 +1,7 @@ +export const strings = { + firstRun: { + heading: 'Open a project', + deviceBody: 'Projects are opened from the app the core runs in. The ones it holds show up here.', + pathPlaceholder: 'Absolute path of the folder', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/group.ts b/packages/ui/src/lib/strings/en/group.ts new file mode 100644 index 000000000..cabe2fb7f --- /dev/null +++ b/packages/ui/src/lib/strings/en/group.ts @@ -0,0 +1,55 @@ +export const strings = { + group: { + rename: 'Rename group', merging: 'Grouping connected machines', + otherGroup: '{machine} belongs to {group}. Leave that group on the machine to move it here.', + heading: 'Group', + intro: 'Your machines and your phones, in one group. Each machine reaches the others on its own, and a phone added on any of them reaches all of them. Every machine of a group has full control of the others: group only what is yours.', + name: 'Group name', namePlaceholder: 'Home', + join: 'Join', joining: 'Joining', joinLabel: 'Link from another machine', + joinHint: 'On a machine of the group, open Settings, Machines, Add a device, copy the link and paste it here. This machine joins that group, and its phones come along.', + members: 'Machines and devices of the group', self: 'This machine', connected: 'Connected', connecting: 'Connecting', + relayed: 'Connected through {machine}', through: 'Through {machine}', + unreachable: 'Not reachable from here. It may be off, asleep or on another network.', + insecure: 'That machine gives no address this device may use, and no machine of the group can carry it there. Give it a public HTTPS address.', + loopback: 'This machine listens only on itself, so the others cannot reach it. Connect it to Tailscale, or turn on listening on the network.', + copied: 'Copied', + addDevice: 'Add a device', adding: 'Preparing', + addOlder: 'Boite on {machine} is too old to add a device. Update it under Updates on this page, then come back.', + scanTitle: 'Scan with your phone', + addHint: 'Open Boite on the phone and tap Scan the code. On a computer, copy the link and paste it under Join.', + qr: 'The link that adds a device, as a QR code', + copyLink: 'Copy link', link: 'Link for a computer', + expires: 'Works once, until {time}', + device: 'Device', phone: 'Phone', browser: 'Browser', devicesList: 'Devices of the group', + deviceAdded: 'Added on {machine}', + deviceLegacy: 'Moves to the group key the next time it connects', + thisDevice: 'This device', + revokeDevice: 'Remove from the group', revokeDeviceTitle: 'Remove {device} from the group?', + revokeDeviceBody: 'Every machine of the group refuses it from now on, including those that are off, as soon as they come back. Adding it again takes a new code.', + remove: 'Remove from the group', removeTitle: 'Remove {machine} from the group?', + removeBody: 'The other machines stop trusting it as each one hears of it. A machine that is off learns it when it comes back. The phones added on it stay in the group.', + leave: 'Leave the group', leaveTitle: 'Leave {group}?', + leaveBody: 'This machine forgets the group. Phones added on this machine leave the group with it.', + syncAll: 'Keep the settings of every machine like {source}', + syncAllHint: 'Copies limits, process guards, agent updates, keybindings and brain switches to every machine of the group, now and after each change, while this window is open. Network access and sign-ins stay on each machine.', + lanHint: 'A phone reaches this link once the local network is on and Boite has restarted.', + reach: { + loopback: 'This link only works on this computer: Boite does not listen on the local network yet.', + button: 'Make it reachable on the local network', + restarting: 'Boite is restarting to listen on the local network. Running agents finish their current step and carry on.', + failed: 'Boite did not come back on the local network. Quit it from the tray, open it again, then add the phone.' + }, + firewall: { + blocked: 'Windows Firewall blocks Boite on this {networks} network, so a phone cannot reach this computer.', + unset: 'Windows Firewall has not allowed Boite on this {networks} network yet, so a phone cannot reach this computer.', + allow: 'Allow in Windows Firewall', + allowing: 'Waiting for Windows', + allowed: 'Windows Firewall now lets phones reach Boite on every network.', + cancelled: 'Nothing changed: Windows needs an administrator to accept its prompt.', + failed: 'Windows refused the change. Allow boite-core.exe in Windows Firewall settings.', + unreadable: 'Windows Firewall could not be read. If a phone cannot connect, allow boite-core.exe there.', + networks: { public: 'public', private: 'private', domain: 'work' }, + and: ' and ' + } + } +} as const; diff --git a/packages/ui/src/lib/strings/en/harnessUpdates.ts b/packages/ui/src/lib/strings/en/harnessUpdates.ts new file mode 100644 index 000000000..e38ac2d42 --- /dev/null +++ b/packages/ui/src/lib/strings/en/harnessUpdates.ts @@ -0,0 +1,21 @@ +export const strings = { + harnessUpdates: { + heading: 'Agent updates', + intro: 'Each machine checks its own agents every six hours.', + auto: 'Update agents automatically', + autoHint: 'An agent updates with nobody connected: a running thread pauses after its tool call and goes on once the update is done', + checking: 'Checking', + available: (name: string, version: string) => `${name} ${version} is available`, + availableShort: 'Update available', + installed: (version: string) => `Installed: ${version}`, + on: (machine: string) => `On ${machine}`, + update: 'Update', skip: 'Skip', + updating: (name: string) => `Updating ${name}`, + waiting: (count: number) => count === 1 ? 'Waits for a thread to pause' : `Waits for ${count} threads to pause`, + failed: (name: string) => `${name} did not update`, + upToDate: 'Up to date', unknown: 'Checks by itself', runUpdater: 'Run its updater', + skipped: (version: string) => `${version} skipped`, + unskip: 'Offer it again', none: 'No agent on this machine has an updater Boite can run.', + route: { managed: 'Installed by Boite', self: 'Your own install' } + } +} as const; diff --git a/packages/ui/src/lib/strings/en/hooks.ts b/packages/ui/src/lib/strings/en/hooks.ts new file mode 100644 index 000000000..0ffdb2194 --- /dev/null +++ b/packages/ui/src/lib/strings/en/hooks.ts @@ -0,0 +1,33 @@ +export const strings = { + /** The Hooks card under the brain (`HooksCard.svelte`): each agent's own hooks and the runs that did not pass. */ + hooks: { + heading: 'Hooks', + description: 'The scripts your agents run on their own events. The counts and the recent list start when Boite starts on this machine.', + refresh: 'Refresh hooks', + missing: 'Update Boite on this machine to see your hooks.', + hookOne: '{count} hook', hookMany: '{count} hooks', + moduleOne: '{count} module', moduleMany: '{count} modules', + none: 'No hooks yet', + notFound: 'Nothing found', + runOne: '{count} run', runMany: '{count} runs', + noRuns: 'No runs yet', + blockedOne: '{count} blocked', blockedMany: '{count} blocked', + failedOne: '{count} failed', failedMany: '{count} failed', + skippedOne: '{count} skipped', skippedMany: '{count} skipped', + sourceMissing: 'Not found', + sourceError: 'Unreadable', + unreadable: 'Could not read {path}', + configOnly: 'Boite reads this configuration but does not see each run.', + sharedOne: 'Shared with {count} separate account', sharedMany: 'Shared with {count} separate accounts', + /** A separate account without a file the provider's own profile has: `{account}` its label. */ + accountMisses: '{account} does not get {path}', + /** The providers without hooks, joined into one list: `{names}`. */ + withoutOne: '{names} does not run hooks.', withoutMany: '{names} do not run hooks.', + recent: 'Recent', + since: 'Since {time}', + quiet: 'Nothing blocked or failed.', + /** Under the newest runs, the older ones folded: `{count}` of them. */ + more: 'Show {count} more', + outcomes: { blocked: 'Blocked', failed: 'Failed', stopped: 'Stopped', skipped: 'Skipped' }, + } +} as const; diff --git a/packages/ui/src/lib/strings/en/imports.ts b/packages/ui/src/lib/strings/en/imports.ts new file mode 100644 index 000000000..addc25827 --- /dev/null +++ b/packages/ui/src/lib/strings/en/imports.ts @@ -0,0 +1,12 @@ +export const strings = { + + /** The import dialog: the sessions Claude Code kept for a project's folder. */ + imports: { + title: 'Import a session into {project}', + intro: 'Claude Code sessions started in this folder. One becomes a thread with its history, and the next turn continues it.', + loading: 'Looking for sessions', + empty: 'No Claude Code session for this folder.', + imported: 'Imported', + importing: 'Importing', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/install.ts b/packages/ui/src/lib/strings/en/install.ts new file mode 100644 index 000000000..d666240a3 --- /dev/null +++ b/packages/ui/src/lib/strings/en/install.ts @@ -0,0 +1,21 @@ +export const strings = { + + /** A provider whose files Boite downloads itself. */ + install: { + action: 'Install', + update: 'Update', + repair: 'Reinstall', + remove: 'Uninstall', + removeTitle: 'Uninstall {provider}?', + removeBody: 'The files Boite downloaded go with it. Your accounts stay, and it can be installed again later.', + removeConfirm: 'Uninstall', + removeCancel: 'Keep', + absent: 'Not installed · {size}', + downloading: 'Downloading · {percent}', + verifying: 'Checking the download', + extracting: 'Unpacking', + installed: 'Installed by Boite', + updateAvailable: 'Version {installed} · {available} is available', + progress: 'Download progress' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/keyboard.ts b/packages/ui/src/lib/strings/en/keyboard.ts new file mode 100644 index 000000000..42ea226bb --- /dev/null +++ b/packages/ui/src/lib/strings/en/keyboard.ts @@ -0,0 +1,54 @@ +export const strings = { + + /** The Keyboard settings page: the table, the file, and the commands the palette has no word for. */ + keyboard: { + heading: 'Shortcuts', + intro: 'Click a shortcut, then press the keys you want instead. It applies at once on this machine.', + search: 'Filter commands', + groups: { general: 'General', surfaces: 'Side panel', thread: 'Thread and composer', jump: 'Go to a thread', theme: 'Theme', terminal: 'Terminal' }, + record: 'Press the keys', + change: 'Change the shortcut', + reset: 'Back to the default', + resetAll: 'Reset all', + clear: 'Remove the shortcut', + conflict: '{chord} already runs "{command}". Move it here?', + replace: 'Move it here', + needsModifier: 'Hold Ctrl, Alt or Win with the key. A key alone, or with Shift only, is typing.', + noMatch: 'No command matches.', + file: 'Shortcuts file', + openFile: 'Open the file', + copyPath: 'Copy the path', + openFailed: 'Could not open the file: {reason}', + fileHint: + 'Command ids to chords such as "mod+shift+k", or null to take a key away. mod is Ctrl here and Cmd on a Mac. Entries you leave out keep their default.', + id: 'Id', + none: 'Not set', + custom: 'changed', + problems: 'Refused', + problemsHint: 'These lines of the file were not applied. Fix them and save.', + commands: { + palette: 'Open the command palette', + browser: 'Open the browser surface', + changes: 'Open the changes surface', + files: 'Open the file tree', + tasks: 'Open the tasks surface', + closeSurface: 'Close the active surface', + stash: 'Stash the composer text, or take it back', + sendAndDraft: 'Send and open a new draft', + pin: 'Pin or unpin this thread', + reopenThread: 'Reopen the thread archived last', + copyAnswer: 'Copy the last answer', + find: 'Find in this thread', + terminalNew: 'New terminal', + terminalSplit: 'Split the terminal right', + terminalSplitVertical: 'Split the terminal down', + terminalClose: 'Close the terminal', + terminalFind: 'Find in the terminal', + terminalHistory: 'Recent commands of the shell', + terminalPreviousCommand: 'Go to the previous command', + terminalNextCommand: 'Go to the next command', + /** `Go to thread 3`: the third row of the sidebar, as drawn. */ + thread: 'Go to thread {n}' + } + } +} as const; diff --git a/packages/ui/src/lib/strings/en/machines.ts b/packages/ui/src/lib/strings/en/machines.ts new file mode 100644 index 000000000..764fee9e2 --- /dev/null +++ b/packages/ui/src/lib/strings/en/machines.ts @@ -0,0 +1,45 @@ +export const strings = { + machines: { + heading: 'Machines', local: 'This PC', projects: 'Projects', recent: 'Recent', + intro: 'Each machine runs its own agents on its own files.', + details: 'Details', address: 'Address', updateFailed: 'Update failed', + label: 'Machine name', icon: 'Machine icon', icons: { desktop: 'Desktop', laptop: 'Laptop', server: 'Server', rack: 'Server rack', cloud: 'Cloud', cpu: 'Processor' }, link: 'Link from another machine', add: 'Add machine', adding: 'Connecting', connect: 'Connect', + addHint: 'On a machine of your group, open Settings, Machines, Add a device, copy the link and paste it here.', + scan: 'Scan the code', pasteLink: 'Paste the link', scanTitle: 'Scan the code', + linkInsecure: 'This link leads to a plain HTTP address, which the app cannot open. Add the device from a machine that has an HTTPS address.', + scanHint: 'On any of your machines, open Settings, Machines, Add a device and point the camera at its QR code.', + scanStarting: 'Starting the camera', scanClose: 'Close the camera', + scanErrors: { + insecure: 'The camera needs an HTTPS address. Open Boite through its HTTPS address, or paste the link.', + denied: 'Camera access was refused. Allow it for this site in the browser settings, or paste the link.', + missing: 'No camera was found on this device. Paste the link instead.', + failed: 'The camera could not start. Paste the link instead.' + }, + linkReaches: 'This link connects to {host}.', linkReplaces: 'This link connects to {host}, listed here as {machine}. It replaces the key held for it.', linkConnected: 'This link is for {host}, already connected here as {machine}.', + remove: 'Remove machine', removeTitle: 'Remove {machine}?', + removeBody: 'Boite forgets this machine on this device.', + open: 'Open machine', invalidUrl: 'Machine URL must be an HTTP or HTTPS URL without credentials, query or fragment.', + duplicate: 'This machine is already connected.', refreshPr: 'Refresh pull request', + manual: 'Connect with URL and token', + filter: 'Filter machines', all: 'All machines', + main: 'Main machine', mainLast: 'Last one used', + mainHint: 'Boite opens on this machine. When it is off, Boite opens on another one.', + mainLastHint: 'Boite opens on the machine you used last.', + timeout: 'Machine did not answer within 12 seconds. Check its address and browser origins, then reconnect.', + linkTitle: 'Connect to {host}?', + linkBody: 'The link you opened points this page at a machine this device has never connected to. That machine will see everything you type here. Connect only if you made this link or trust who sent it. Cancel keeps your current machine.', + linkConfirm: 'Connect', + browserOrigins: 'Allowed browser origins', + browserOriginsHint: 'Machines of the group, and the addresses they are served at, are allowed on their own. Add here only a page served by something outside the group. One exact http(s) origin per line.', + settings: 'Settings for this machine', settingsHint: 'Resource limits, process retention, agent updates and worktree storage apply only to this machine.', + synced: 'Common preferences synchronized with {source}.', + syncBrainAbsent: 'Connect a brain folder on this machine to copy its switches too.', + syncKeysAbsent: 'One of the two machines runs a Boite without keybindings, so they stayed as they were.', + syncStopped: 'The copy stopped at {stage}: {error}. What came before it stays copied.', + syncStages: { settings: 'the settings', keybindings: 'the keybindings', brain: 'the brain switches' }, + syncProviders: 'Still to sign in on this machine: {providers}. Sign-ins never leave the machine they were made on.', + syncOpenProviders: 'Open its providers', + threadFailed: 'A thread failed here: {thread}', + agentLinkAddressRequired: 'Update both machines to relay through the app, or set a reachable HTTPS address for {machine} in General settings.', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/media.ts b/packages/ui/src/lib/strings/en/media.ts new file mode 100644 index 000000000..c7c87a8ca --- /dev/null +++ b/packages/ui/src/lib/strings/en/media.ts @@ -0,0 +1,16 @@ +export const strings = { + + /** Images on their way to an agent, files given with an answer, and the full-screen viewer. */ + media: { + sourceTooLarge: '{name} weighs {size}: Boite shrinks images of {max} at most, so it was left out.', + viewer: 'Media viewer', + previous: 'Previous', + next: 'Next', + position: '{index} of {total}', + share: 'Share or save', + shareFailed: '{name} could not be shared.', + shareReady: '{name} is ready: tap Share again.', + answerFiles: 'Files given: {names}', + answerFileCount: '{count} files' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/mention.ts b/packages/ui/src/lib/strings/en/mention.ts new file mode 100644 index 000000000..7f61b37a1 --- /dev/null +++ b/packages/ui/src/lib/strings/en/mention.ts @@ -0,0 +1,9 @@ +export const strings = { + + /** The composer's `@` menu: the project's files, the picked one written in as `@path`. */ + mention: { + label: 'Files', + empty: 'No file matches', + more: '{count} more, keep typing' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/mobile.ts b/packages/ui/src/lib/strings/en/mobile.ts new file mode 100644 index 000000000..090369954 --- /dev/null +++ b/packages/ui/src/lib/strings/en/mobile.ts @@ -0,0 +1,38 @@ +export const strings = { + mobile: { + clearSearch: 'Clear search', + /** Recent's filter: the conversations whose agent or sub-agents are still at work. */ + atWork: '{count} at work', atWorkFilter: 'Show only the conversations at work', + draftTitle: 'What would you like to do?', draftHint: 'Start a task, ask a question, or pick up your work.', + pairTitle: 'Your Boite, on your phone', + pairBody: 'On any of your machines, open Settings, Machines, Add a device, then scan its QR code here.', + pairStep: 'On your computer: Settings, Machines, Add a device.', + pairInstalled: 'Just installed Boite? Add it here too: the iPhone app keeps its connection separately from the browser.', + pairPrivacy: 'The QR code works once and expires within minutes. This phone then reaches every machine of the group.', + scanInApp: 'Use the Scan button here rather than the Camera app: the camera opens the link in Safari, which keeps a connection of its own.', + pairingRequired: 'Add this app to your group', + offlineTitle: 'Waiting for your computer', + offlineBody: 'Keep Boite running on your computer and check your connection. If you use Tailscale, keep it connected on both devices.', + localNetworkTitle: 'Allow the local network', + localNetworkBody: 'Your machines are on your local network, and your browser blocks Boite from it. In the browser that runs the app (Chrome or another), open Settings, Site settings, Local network and allow {host}. On Android, also allow Nearby devices for that browser in its app settings.', + troubleshoot: 'Troubleshoot', troubleshootSteps: 'Connection checks', + checkLocalNetwork: 'Local network', + checkLocalNetworkHint: 'In the browser that runs the app: Settings, Site settings, Local network, allow {host}.', + localNetworkStates: { granted: 'Allowed', denied: 'Blocked', prompt: 'Not answered' }, + checkNearby: 'Nearby devices', + checkNearbyHint: 'In Android settings: Apps, the browser that runs the app, Permissions, Nearby devices, Allow. Android asks for it on top of Local network.', + checkReach: 'Reach {host}', checkOpen: 'Open', + checkReachHint: 'Open it in a browser tab. If the page does not load, this phone cannot reach the machine: check Tailscale, the VPN or the Wi-Fi.', + localNetworkAsk: 'When your browser asks, allow Boite to reach devices on your local network: your machines are there.', + emptyTitle: 'Start a conversation', + emptyBody: 'Send a task to an agent. Your conversations will appear here.', + emptyActivity: 'All caught up', + computer: 'Connected computer', + pairAgain: 'Add again', + settingsDevice: 'This phone', + settingsPhone: 'App & notifications', + settingsBack: 'Back to settings', + navigation: 'Navigation', threads: 'Conversations', activity: 'Activity', project: 'Choose project', + search: 'Find a conversation', noActivity: 'No agents need your attention.', noThreads: 'No matching conversations.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/notify.ts b/packages/ui/src/lib/strings/en/notify.ts new file mode 100644 index 000000000..e15fea109 --- /dev/null +++ b/packages/ui/src/lib/strings/en/notify.ts @@ -0,0 +1,10 @@ +export const strings = { + + notify: { + done: 'Done', + failed: 'Failed', + openThread: 'Open thread', + needsYou: 'Needs your answer', + connected: 'Notifications are connected' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/onboarding.ts b/packages/ui/src/lib/strings/en/onboarding.ts new file mode 100644 index 000000000..b9c235c1f --- /dev/null +++ b/packages/ui/src/lib/strings/en/onboarding.ts @@ -0,0 +1,124 @@ +export const strings = { + + /** + * The tour a machine sees once, the first time Boite opens on it. Every + * screen carries the switch for what it explains, so reading it and setting + * it up are the same pass. + */ + onboarding: { + /** Boite Legacy's trade offer: picking a row is the tour's last click. */ + privacy: { + title: 'SELL YOUR SOUL TO BOITE!', + intro: 'A few anonymous stats tell us what to fix first. Your messages, files and accounts never leave your machine.', + question: 'Deal?', + video: 'Trade offer meme', + basic: 'NO! Just the basic counters', + basicDefault: '(default)', + basicHint: 'Anonymous counters, no profile.', + deal: 'DEAL', + dealHint: 'Also the models and settings you use. Thank you!', + details: 'Basic: active installs under a hashed random ID, and launches, conversations and turn outcomes under IDs that change every day. No profile. Deal adds the public models you pick, reasoning level, speed and permission modes, durations and token volumes, under a stable random ID; private models count as "other". The app works the same either way, and Settings changes it anytime.', + optOut: "If you REALLY don't want to help Boite:", + offLabel: 'turn everything off :c', + doc: 'What is collected' + }, + demo: { + pause: 'Pause', play: 'Replay', resume: 'Resume', + record: 'Click the microphone', speak: 'Speak your message', review: 'Review before sending', + listening: 'Listening', draft: 'Your draft', localLabel: 'On this computer', + chooseAgent: 'Choose the next agent', handoff: 'Codex picks up the conversation', checked: 'Labels checked. Contrast improved.', nextMessage: 'Message Codex', + before: 'Before', after: 'After', buttonBefore: 'Send', buttonAfter: 'Send message', changedFile: 'ContactForm.svelte', changesReady: 'Two lines changed', + background: 'Working in the background', writing: "Preparing tomorrow's launch", notes: 'Notes', noteOne: 'Review the new contact form', noteTwo: 'Publish the portfolio', + taskResult: 'Contact form ready', loginResult: 'Login fixed', testsResult: 'Tests passed', hoverTray: 'Hover the Boite icon', + conversation: 'Change agent', voice: 'Dictate', panel: 'Inspect changes', + task: 'Build my portfolio', secondTask: 'Fix the login', thirdTask: 'Write the tests', working: 'Working', request: 'Add a contact form', answer: 'The form is ready.', followup: 'Now check the accessibility', continued: 'Same thread. Same history.', + voiceWords: 'Make the buttons easier to read', voiceHint: 'Speak, review the draft, then send.', + changes: 'Changes', reviewed: 'Review the changes beside the chat.', + usageLabel: 'Claude', window: '5-hour limit', used: '24% used', reset: 'Resets in 2 h 10 min', trayHint: 'Hover the Boite icon to see your limits without opening the app.', + desktop: 'Your computer', phone: 'Your phone', synced: 'The same conversation', reachHint: 'After pairing by QR code, follow your agents from your phone. Your computer keeps running them.', + notification: 'Contact form finished', quietBody: 'Let your agents work without taking over your screen or your speakers.', + installVoice: 'Install local dictation', downloadVoice: 'About 200 MB. Audio stays on this computer.', installingVoice: 'Downloading voice engine', cancelVoice: 'Cancel download', readyVoice: 'Dictation is ready. Use the microphone in the composer.', + }, + label: 'Getting started', + skip: 'Skip', + back: 'Back', + next: 'Next', + done: "Let's Boite", + step: 'Step {index} of {total}', + progress: 'Go to step {index}: {title}', + replay: 'Show the tour again', + changeLater: 'All of this is in Settings.', + + /** The question the preset hangs on: said plainly, with a smile, so nobody is unsure which one they are. */ + profile: { + title: 'First, which one are you?', + everyday: "I'm not a developer! Don't confuse me with code and commands!", + everydayHint: 'A new conversation waits in Documents/Boite until you give it a folder, and the agent asks before it acts.', + developer: "I'm a developer, give me the works.", + developerHint: 'The app opens on your last project, conversations show the terminal and the process trace, and the panel opens on the changes.', + later: 'Nothing is taken away either way. Settings, Appearance shows or hides any button, one by one.' + }, + + welcome: { + title: 'Boite runs your agents', + body: 'One conversation per task. The agents keep working while you do something else.' + }, + + /** Switching model inside a live conversation, the composer's picker. */ + agents: { + title: 'A conversation that follows your work', + body: 'Switch agents, dictate a message, inspect the result. Stay in the same thread.', + }, + + /** Dictation: what it does, and where this core stands on setting it up. */ + voice: { + title: 'Talk instead of typing', + body: 'Press the microphone and speak. You read the text before it is sent.', + open: 'Voice settings', + reading: 'Reading the voice engine' + }, + + /** The panel on the right, each surface with its key. */ + panel: { + title: 'The panel beside the conversation', + body: 'Changes, files, tasks and a browser, one key away.', + open: 'Keyboard shortcuts', + }, + + /** The quota bars, and the switch that fills them. */ + usage: { + title: 'See what you have left', + body: 'See which subscription is filling up and when it resets, even with Boite in the background.', + monitor: 'Read subscription limits for {account}', + connect: 'Connect a provider', + deviceHint: 'Limits are read on the machine hosting the core.' + }, + + /** The two ways this core reaches further: a phone, and another core. */ + reach: { + onPhoneTitle: 'Your computer does the work', + onPhoneBody: 'This phone shows the conversations of your computer. The agents run there and keep going when you put the phone away.', + onPhoneSettings: 'Notifications and the home screen app are in Settings.', + title: 'Step away. Keep the conversation.', + phone: 'On your phone', + phoneBody: 'Scan a QR code and your phone gets the conversations of all your machines, with notifications.', + pair: 'Add a device', + machines: 'Another Boite', + machinesBody: 'Drive a Boite running on a server or another computer from this app.', + connect: 'Connect a machine', + deviceHint: 'Pairing and machines are managed from the app the core runs in.' + }, + + /** The switches everyone ends up looking for on the first evening. */ + quiet: { + title: 'Agents that do not interrupt you', + deviceHint: 'Focus and audio are settings of the machine hosting the core.' + }, + + /** The last screen, and the only one that does something irreversible: it opens a folder. */ + project: { + title: 'Open your first project', + body: 'The folder an agent works in.', + } + } +} as const; diff --git a/packages/ui/src/lib/strings/en/palette.ts b/packages/ui/src/lib/strings/en/palette.ts new file mode 100644 index 000000000..e7f6ee613 --- /dev/null +++ b/packages/ui/src/lib/strings/en/palette.ts @@ -0,0 +1,34 @@ +export const strings = { + + palette: { + placeholder: 'Search threads, or type a command', + terminal: 'Show or hide the terminal', + threads: 'Threads', + commands: 'Commands', + empty: 'Nothing matches.', + hint: 'Enter to open, Esc to close', + newThread: 'New thread', + addProject: 'Add a project', + importSession: 'Import a Claude Code session', + settings: 'Open settings', + appearance: 'Appearance settings', + providers: 'Providers and accounts', + pair: 'Add a device', + sidebar: 'Toggle the sidebar', + panel: 'Toggle the right panel', + trace: 'Show the trace', + changes: 'Show the changes', + files: 'Show the files', + tasks: 'Show the tasks', + subagents: 'Show the subagents', + agents: 'Open the agents', + pin: 'Pin this thread', + unpin: 'Unpin this thread', + rename: 'Rename this thread', + retitle: 'Regenerate the title of this thread', + archive: 'Archive this thread', + themeDark: 'Theme: dark', + themeLight: 'Theme: light', + themeSystem: 'Theme: system' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/parts.ts b/packages/ui/src/lib/strings/en/parts.ts new file mode 100644 index 000000000..722aad883 --- /dev/null +++ b/packages/ui/src/lib/strings/en/parts.ts @@ -0,0 +1,10 @@ +export const strings = { + + /** A part of the app kept in a chunk of its own (`components/Deferred.svelte`), and the phone's navigation (App.svelte). */ + parts: { + loading: 'Loading', + failed: 'This part of Boite did not load.', + navigationFailed: 'The navigation did not load.', + dictationFailed: 'Dictation did not load. Try again' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/permissionMode.ts b/packages/ui/src/lib/strings/en/permissionMode.ts new file mode 100644 index 000000000..900c46ebf --- /dev/null +++ b/packages/ui/src/lib/strings/en/permissionMode.ts @@ -0,0 +1,11 @@ +export const strings = { + + permissionMode: { + default: 'Ask', + acceptEdits: 'Edit freely', + bypassPermissions: 'Auto', + yolo: 'YOLO', + plan: 'Plan', + dontAsk: 'Auto-deny' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/permissionModeAgy.ts b/packages/ui/src/lib/strings/en/permissionModeAgy.ts new file mode 100644 index 000000000..32e4e8d3e --- /dev/null +++ b/packages/ui/src/lib/strings/en/permissionModeAgy.ts @@ -0,0 +1,10 @@ +export const strings = { + + /** agy asks nothing in print mode: what its mode does not allow, it refuses by itself. */ + permissionModeAgy: { + default: 'agy settings', + defaultLong: 'Follows agy\'s own permission setting and refuses what it would ask about', + acceptEditsLong: 'Edits files without asking, refuses commands that need approval', + draft: "with agy's own permission settings" + } +} as const; diff --git a/packages/ui/src/lib/strings/en/permissionModeCodex.ts b/packages/ui/src/lib/strings/en/permissionModeCodex.ts new file mode 100644 index 000000000..3a7bc9dff --- /dev/null +++ b/packages/ui/src/lib/strings/en/permissionModeCodex.ts @@ -0,0 +1,8 @@ +export const strings = { + + /** Codex writes inside the folder on its default mode: the chip says so instead of "Ask". */ + permissionModeCodex: { + default: 'This folder', + defaultLong: 'Edits and runs commands in this folder without asking, asks to go beyond it' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/permissionModeLong.ts b/packages/ui/src/lib/strings/en/permissionModeLong.ts new file mode 100644 index 000000000..a5b04994d --- /dev/null +++ b/packages/ui/src/lib/strings/en/permissionModeLong.ts @@ -0,0 +1,11 @@ +export const strings = { + + permissionModeLong: { + default: 'Reads freely, asks before editing a file or running a command', + acceptEdits: 'Edits files without asking, asks before running a command', + bypassPermissions: 'Runs everything without asking, anywhere on this computer', + yolo: 'Approves every tool request; skips Claude and Codex hooks', + plan: 'Plan only, no changes', + dontAsk: 'Deny anything that would need asking' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/phone.ts b/packages/ui/src/lib/strings/en/phone.ts new file mode 100644 index 000000000..b2c8730fb --- /dev/null +++ b/packages/ui/src/lib/strings/en/phone.ts @@ -0,0 +1,57 @@ +export const strings = { + phone: { + preparing: 'The app is still preparing offline files. Reload and try again.', + settingsOffline: 'Settings have not been downloaded yet. Reconnect and open Settings again.', + dialogOffline: 'This dialog has not been downloaded yet. Reconnect and open it again.', + heading: 'Phone app', ownAddress: 'Use another HTTPS address', publicUrl: 'Public HTTPS address', urlPlaceholder: 'https://boite.example.com', + publicUrlHint: 'The HTTPS origin of your reverse proxy, which must forward the page and /rpc to this core. Links to add a device use it.', + install: 'Install Boite', installed: 'Boite is running as an installed app.', + installHint: 'On iPhone, open Share in Safari, then Add to Home Screen. On Android, use Install app or Add to Home screen in the browser menu.', + httpsRequired: 'This connection uses HTTP. Open Boite through HTTPS to enable offline files and notifications.', + ownOrigin: 'Notifications are enabled from the app installed from this machine\'s address.', + pairFirst: 'Add this app to your group before enabling notifications.', + unsupported: 'This browser does not support push here. On iPhone, add Boite to the Home Screen and open it from its icon.', + pushHint: 'When an agent finishes, fails or needs your answer, even while Boite is closed.', + enable: 'Enable notifications', disable: 'Disable notifications', test: 'Send test notification', + enabled: 'Notifications enabled for this device.', testSent: 'The push service accepted the test notification.', + denied: 'Notifications were not allowed. You can change this in the browser or device settings.', + subscriptionFailed: 'The browser did not return a complete push subscription.', + multiMachine: 'Notifications come from the machine this app was installed from, and through it from every other machine of its group.', + relayed: "This machine reaches this device through the machine this app was installed from: enable notifications there, and this machine's come with them.", + tailscale: { + heading: 'HTTPS through Tailscale', + hint: 'Tailscale gives this machine an HTTPS address on your tailnet. Boite serves itself there with tailscale serve, and links to add a device use it. A phone needs Tailscale connected to reach it.', + checking: 'Checking Tailscale…', + missing: 'Tailscale is not installed on this machine.', + download: 'Get Tailscale', + stopped: 'Tailscale is installed but not connected. Open Tailscale and connect, then check again.', + needsLogin: 'Tailscale is signed out on this machine. Sign in, then check again.', + signIn: 'Sign in to Tailscale', + httpsDisabled: 'HTTPS certificates are off for your tailnet. In the Tailscale admin console, DNS page, turn on MagicDNS and HTTPS Certificates, then check again.', + openAdmin: 'Open the admin console', + off: 'Ready: this core can be served at {url}.', + on: 'This core is served at {url}.', + onPublic: 'Links to add a device use this address.', + conflict: '{url} already serves {target}. Boite leaves it alone unless you replace it.', + error: 'The tailscale command did not answer as expected.', + enable: 'Enable HTTPS via Tailscale', + disable: 'Disable', + replace: 'Replace…', + refresh: 'Check again', + pair: 'Add a device', + approve: 'Allow Serve', + replaceTitle: 'Replace what {url} serves?', + replaceBody: 'Tailscale serves {target} at this address now. Replacing it points the address at Boite, and what used it stops being reachable there.', + replaceConfirm: 'Replace', + disableTitle: 'Stop serving Boite through Tailscale?', + disableBody: 'Phones paired through {url} cannot reach this core until it is served again. Their keys stay valid.', + details: { + 'permission-denied': 'Tailscale refused the change. On Linux, run sudo tailscale set --operator=$USER once, then try again.', + 'serve-consent': 'Your tailnet has not allowed Serve yet. Open the link, approve it, then try again.', + 'not-logged-in': 'Tailscale is signed out. Sign in, then try again.', + timeout: 'Tailscale did not answer in time. Try again.', + unknown: 'Tailscale refused the change. Run tailscale serve status on this machine to see why.' + } + } + } +} as const; diff --git a/packages/ui/src/lib/strings/en/plugins.ts b/packages/ui/src/lib/strings/en/plugins.ts new file mode 100644 index 000000000..a79d56fb6 --- /dev/null +++ b/packages/ui/src/lib/strings/en/plugins.ts @@ -0,0 +1,91 @@ +export const strings = { + plugins: { + heading: 'Plugins', + /** A desktop app asked to show a thread or an agent on a machine this window does not have. */ + revealUnknownMachine: 'The thread or agent an app asked to show is on a machine this window is not connected to. Add it in Settings, Machines and updates.', + revealMachineOffline: 'The thread or agent an app asked to show is on {machine}, which is not connected right now.', + intro: 'A plugin is a program that runs on the machine hosting your core, checked against the SHA-256 its manifest publishes.', + installedHeading: 'Installed', + recommendedHeading: 'Recommended', + install: 'Install', + update: 'Update to {version}', + reinstall: 'Reinstall', + remove: 'Uninstall', + stateInstalled: 'Installed', + stateInstalling: 'Downloading and checking', + stateError: 'Install failed', + stateRejected: 'Refused by Boite', + updateAvailable: '{version} available', + source: 'Source code', + atCommit: '{ref} at {commit}', + pools: 'Account pools', + empty: 'No saved account in this pool.', + add: 'Save current CLI login', + switch: 'Use this account', + forget: 'Forget account', + active: 'Active', + removeTitle: 'Uninstall {name}?', + removeBody: 'Deletes the plugin\'s folder in Boite\'s data directory. What the program saved elsewhere, such as its saved logins, stays where it is.', + switchTitle: 'Change the CLI login?', + switchBody: 'This changes the provider\'s default CLI login for future turns, including outside Boite. Isolated Boite accounts keep their own login.', + forgetTitle: 'Forget this saved login?', + forgetBody: 'Removes this login from the plugin\'s pool.', + cliScope: 'CLI logins, separate from the Boite accounts connected in Providers.', + addHeading: 'Add from a git URL', + addHint: 'The https URL of a git repository with a boite-plugin.json at its root. Boite shows what it would download before installing anything.', + urlLabel: 'Repository URL', + urlPlaceholder: 'https://github.com/owner/repo', + refLabel: 'Branch, tag or commit', + refPlaceholder: 'Default branch', + inspect: 'Preview', + inspecting: 'Reading', + previewSource: 'Source', + previewReplaces: 'Replaces', + previewDownloads: 'Downloads', + previewDigest: 'SHA-256', + previewRuns: 'Runs', + previewPools: 'Account pools', + previewApp: 'Desktop app', + previewAppBody: 'Opens a window of its own and gets full access to Boite through the owner token: it can read and drive every thread, like this app.', + app: 'Desktop app', + appRunning: 'Running', + appStarting: 'Starting again', + appStopped: 'Stopped', + appOff: 'Stopped · starts again only when you start it', + appCrashed: 'Crashed with exit code {code}', + appCrashedSignal: 'Crashed', + appUnavailable: 'Unavailable', + appStart: 'Start', + appStop: 'Stop', + appRestart: 'Restart', + startWithBoite: 'Start with Boite', + /** A gateway plugin: run in the background on a loopback port, for the subscription proxy. */ + gateway: 'Gateway', + gatewayRunning: '{version} on 127.0.0.1:{port}', + gatewayStarting: 'Starting', + gatewayUpdating: 'Updating', + gatewayCrashed: 'Crashed', + gatewayData: 'Its sign-ins and database live in {dir}, and stay there when it is uninstalled.', + removeGatewayBody: 'Deletes the program in Boite\'s data directory. Its sign-ins, settings and database stay in {dir}, ready for the next install.', + previewGateway: 'Gateway', + previewGatewayBody: 'Runs in the background on this machine, on a loopback port, with no access to Boite. The subscription proxy can point at it.', + trust: 'This program runs with your permissions on the machine hosting your core. Install it only if you trust whoever publishes it.', + confirmAdd: 'Install {name}', + refusedTitle: 'Boite refused this plugin', + refusedFile: 'File', + refusedField: 'Field', + refusedExpected: 'Expected', + /** Recommended plugins whose entry names an update source: the check, and what it last found. */ + checkUpdates: 'Check for updates', + checkingUpdates: 'Checking for updates', + upToDate: 'Up to date · checked {time}', + updateFound: 'Found at {source}, {ref} at {commit} · checked {time}', + updateIgnored: 'Not offered: {reason}', + updateFailed: 'Could not check for updates: {error}. Boite tries again later.', + noticeTitle: '{name} {version} is available', + noticeBody: 'Its download is checked against the SHA-256 its release publishes.', + noticeUpdate: 'Update', + noticeLater: 'Later', + noticeLabel: 'Plugin update', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/previewComments.ts b/packages/ui/src/lib/strings/en/previewComments.ts new file mode 100644 index 000000000..409a89b4a --- /dev/null +++ b/packages/ui/src/lib/strings/en/previewComments.ts @@ -0,0 +1,17 @@ +export const strings = { + previewComments: { + annotate: 'Reference an element', + picking: 'Select an element in the page. Escape cancels.', + add: 'Add to message', + added: 'Element added to your message.', + unavailable: 'Element selection is unavailable on this page. Use the desktop app for cross-origin pages.', + failed: 'Element selection failed. Reload the page and try again.', + reveal: 'Show this element in the preview', + remove: 'Remove element reference', + stale: 'This preview has changed page. Reopen the original page or select the element again.', + missing: 'This element no longer exists on the page. Select it again.', + tooMany: 'A message can reference at most 8 page elements.', + activityUnsupported: 'Element references cannot be used with /goal or /loop. Send a regular message or remove the references.', + stashUnsupported: 'This draft contains element references and cannot be stashed. Send it or remove the references first.' +} +} as const; diff --git a/packages/ui/src/lib/strings/en/protection.ts b/packages/ui/src/lib/strings/en/protection.ts new file mode 100644 index 000000000..0c4071a7f --- /dev/null +++ b/packages/ui/src/lib/strings/en/protection.ts @@ -0,0 +1,9 @@ +export const strings = { + + protection: { + quiet: 'Peace and quiet', + limits: 'Resource limits', + tasks: 'Task manager', + windows: 'Focus, audio and resource limits apply on the Windows host.', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/providerSettings.ts b/packages/ui/src/lib/strings/en/providerSettings.ts new file mode 100644 index 000000000..83c1d3b20 --- /dev/null +++ b/packages/ui/src/lib/strings/en/providerSettings.ts @@ -0,0 +1,51 @@ +export const strings = { + providerSettings: { + heading: 'Providers', + intro: 'An agent already signed in on this computer is picked up on its own.', + step: { + installing: 'Installing', + install: 'Not installed', + repair: 'Installed files are missing', + manual: 'Not installed', + 'signing-in': 'Signing in', + 'sign-in': 'Not signed in', + external: 'Not signed in', + ready: 'Ready' + }, + accountsCount: '{count} accounts', + thenSignIn: 'Sign-in starts when the download finishes.', + manualHint: '{provider} comes with its own installer. Run it, then come back: Boite finds it on its own.', + externalHint: '{provider} signs in from its own app. Sign in there, then check again.', + setup: 'Install guide', + refresh: 'Check again', + details: 'Details', + isolated: 'Boite account', + default: 'Your command-line login', + reconnect: 'Sign in again', + check: 'Check connection', + connectionOk: 'Connected', + checking: 'Checking connection', + accountName: 'Account name', + revealEmail: 'Reveal email', + deviceHint: 'Enter this code on the sign-in page. Boite waits for you to finish.', + addAccount: 'Add another account', + useCli: 'Use my command-line login', + executable: 'Runs from', + accounts: 'Accounts', + installation: 'Installation', + managedCopy: "Boite's copy", + addHeading: 'Add a provider', + offHeading: 'Turned off', + off: 'Turned off', + experimental: 'Experimental', + /** The switch's accessible name: `{provider}` is the provider's own name. */ + enable: 'Use {provider}', + enableHint: 'Off, Boite starts nothing of this provider and no list offers it. Its accounts and conversations are kept.', + /** A thread whose provider is off: the composer's chip and the sentence behind it. */ + threadOff: '{provider} is turned off. Turn it on in Settings > Providers to continue this conversation.', + defaultModel: 'Default model', + autoUpdate: 'Automatic updates', + checkUpdates: 'Check for updates', + updating: 'Updating' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/quotas.ts b/packages/ui/src/lib/strings/en/quotas.ts new file mode 100644 index 000000000..8e946932e --- /dev/null +++ b/packages/ui/src/lib/strings/en/quotas.ts @@ -0,0 +1,71 @@ +export const strings = { + + quotas: { + machineAccounts: 'Account limits for {machine}', + outdated: 'Update Boite on this machine to see its limits on this device.', + usingCredits: 'Using credits', + reorder: 'Reorder {name}. Drag or use the arrow keys.', + bankedReset: '{count} banked reset', + observed: 'Observed {time}', + remainingUnder: 'Less than {percent}% left', + bankedResets: '{count} banked resets', + resetExpires: 'Next reset expires {time}', + useReset: 'Use reset', + resetUsing: 'Using reset', + resetConfirmTitle: 'Use a reset for {account}?', + resetConfirmBody: 'This consumes the available reset closest to expiring on this {provider} account and clears its current usage limits. It cannot be undone.', + resetConfirm: 'Use one reset', + resetApplied: 'Reset applied. Usage limits refreshed.', + resetNothing: 'Nothing to reset right now. No reset was consumed.', + resetNoCredit: 'No banked reset is available for this account.', + resetAlreadyUsed: 'This reset was already used. Limits refreshed.', + resetRefreshFailed: 'Reset applied, but the new limits could not be read. Refresh to check.', + resetFailed: 'Could not use the reset: {error}', + budgetRemaining: 'Monthly budget remaining', + budgetCompact: 'Monthly budget', + creditCompact: 'Credits', + windowWeeklyCompact: 'Week', + windowMonthlyCompact: 'Month', + creditRemaining: 'Credits remaining', + creditBalance: '{count} credits', + + /** The tray icon's right-click menu. */ + trayShow: 'Show', + trayQuit: 'Quit', + names: { muse: 'Muse Code', claude: 'Claude', codex: 'Codex', antigravity: 'Antigravity', grok: 'Grok', opencode: 'OpenCode Go' }, + noReading: 'Unavailable', + cliSource: 'agy command-line login', + connect: 'Connect account', + noReset: 'Reset time not reported', + heading: 'Provider quotas', + intro: 'Subscription limits reported by your providers. Separate from tokens used in Boite.', + remaining: '{percent}% left', + resets: 'Resets {time}', + /** The limit windows the core names in English: `5 hours`, `Weekly`, `Monthly`, `Credits`, alone or on one side of ` · `. */ + windowHours: '{hours} hours', + windowWeekly: 'Weekly', + windowMonthly: 'Monthly', + windowCredits: 'Credits', + checked: 'Updated {time}', + stale: 'Last successful reading', + empty: 'Connect a provider to see its subscription limits.', + unsupported: 'This provider does not expose subscription quotas here.', + disabled: 'Quota monitoring is off for this account.', + loading: 'Reading provider limits', + slowHint: 'Antigravity can take up to two minutes to answer.', + tracked: 'Tracked accounts', + trackedHint: 'Boite only asks the accounts switched on here.', + autoResume: 'Resume threads after a usage limit', + autoResumeHint: 'A thread stopped by a subscription usage limit resumes by itself a minute after the limit resets, once per limit window. Stop, a new message or a changed model cancels it.', + providers: 'Manage providers', + unavailable: 'No quota reading available.', + refresh: 'Refresh quotas', + /** The sidebar's gauge button and the popover it opens. */ + glance: 'Account limits', + /** The popover's way to the Limits page in Settings. */ + allLimits: 'Limits page', + /** A read of the limits failed; the core's reason follows. */ + readFailed: "Couldn't read the limits: {error}", + quit: 'Quit Boite', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/remoteBrowser.ts b/packages/ui/src/lib/strings/en/remoteBrowser.ts new file mode 100644 index 000000000..329490753 --- /dev/null +++ b/packages/ui/src/lib/strings/en/remoteBrowser.ts @@ -0,0 +1,11 @@ +export const strings = { + remoteBrowser: { + display: 'Display', resolution: 'Page resolution', fitPhone: 'Fit this screen', phone: 'Phone', tablet: 'Tablet', rotate: 'Rotate', width: 'Width', height: 'Height', apply: 'Apply', sharedSize: 'Resolution also changes the page the agent sees.', restoreSize: 'Restore the default size', previewZoom: 'Preview zoom (this device only)', fit: 'Fit', panHint: 'Drag to pan the preview. Use the arrows to scroll the page.', + address: 'Address', addressPlaceholder: 'Enter an address', back: 'Back', forward: 'Forward', reload: 'Reload', badAddress: 'Enter a web address (http or https).', + title: 'Agent’s browser', waiting: 'Waiting for the page', paused: 'Paused', reconnecting: 'Reconnecting', pause: 'Pause', resume: 'Resume', + interact: 'Tap or swipe the shared page', interactDesk: 'The shared page: click it, then type', + deskHint: 'Click the page, then type in it. Drag to select, double-click a word, scroll with the wheel. Copy, cut and paste use the keyboard.', + copy: 'Copy the selected text', copied: 'Copied.', nothingSelected: 'Nothing is selected in the page. Double-tap a word first.', copyCut: 'The selection was too long: its start was copied.', image: 'Live view of the agent’s browser', scrollUp: 'Scroll up', scrollDown: 'Scroll down', + text: 'Tap a page field, then type here', send: 'Send text to the page', gesture: 'Tap to click. Drag to scroll. Return sends the text and Enter; ⌫ on an empty field erases in the page.' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/resident.ts b/packages/ui/src/lib/strings/en/resident.ts new file mode 100644 index 000000000..01982193b --- /dev/null +++ b/packages/ui/src/lib/strings/en/resident.ts @@ -0,0 +1,6 @@ +export const strings = { + /** The persistent agent the owner entrusted a project to, where its project and threads show it. */ + resident: { + lookedAfterBy: 'Looked after by {name}' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/resources.ts b/packages/ui/src/lib/strings/en/resources.ts new file mode 100644 index 000000000..85a7d9dcd --- /dev/null +++ b/packages/ui/src/lib/strings/en/resources.ts @@ -0,0 +1,30 @@ +export const strings = { + resources: { + memory: 'Memory', + resolved: (mb: number) => `${Math.round(mb)} MB`, + auto: (mb: number) => `Auto: ${Math.round(mb)} MB`, + agents: 'Agents / budget', + available: 'Available / reserve', + unknown: 'Not available', + state: 'Protection status', + states: { ok: 'Enough memory', critical: 'Memory limit crossed' }, + killTitle: 'Process stopped to free memory', + killCount: (count: number) => `${count} processes stopped to free memory`, + stoppedProcesses: (count: number) => `Show ${count} stopped processes`, + memorySettings: 'Memory settings', + protectionOff: 'Memory protection is off', + killReason: { + 'thread-quota': (limit: string) => `This conversation exceeded its ${limit} memory limit.`, + budget: (limit: string) => `The agents exceeded their ${limit} shared memory budget.`, + machine: (limit: string) => `Available memory fell below the ${limit} reserve.`, + }, + threadCap: 'This conversation reached its memory limit. Boite refused more memory.', + budget: 'The agents reached their shared memory budget. Boite refused more memory.', + throttled: (limit: string) => `This conversation's processes are slowed by their ${limit} memory limit. They stall instead of failing.`, + empty: 'Nothing is running right now.', + processes: 'processes', + killTree: 'Kill tree', + killConfirm: 'Kill every process of this thread?', + killConfirmYes: 'Kill', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/rightPanel.ts b/packages/ui/src/lib/strings/en/rightPanel.ts new file mode 100644 index 000000000..6d926df55 --- /dev/null +++ b/packages/ui/src/lib/strings/en/rightPanel.ts @@ -0,0 +1,26 @@ +export const strings = { + + /** The panel on the right of the chat: its tab strip, its launcher, its surfaces. */ + rightPanel: { + resize: 'Resize the panel, double-click to reset', + maximize: 'Maximize the panel', + restore: 'Restore the panel', + newSurface: 'Open a surface', + scrollLeft: 'Scroll the tabs left', + scrollRight: 'Scroll the tabs right', + closeTab: 'Close {title}', + closeOthers: 'Close others', + closeToRight: 'Close to the right', + closeAll: 'Close all', + trace: 'Trace', + browser: 'Browser', + changes: 'Changes', + files: 'Files', + file: 'File', + tasks: 'Tasks', + device: 'Device', + ownerOnly: "Only in the owner's app", + launcher: 'Open a surface in this panel', + untitled: 'Browser' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/serverUpdate.ts b/packages/ui/src/lib/strings/en/serverUpdate.ts new file mode 100644 index 000000000..b319cedfe --- /dev/null +++ b/packages/ui/src/lib/strings/en/serverUpdate.ts @@ -0,0 +1,24 @@ +export const strings = { + + serverUpdate: { + heading: 'Server', + updates: 'Updates', + current: 'Up to date', + waiting: 'Waiting for work to finish', + installing: 'Restarting the server', + reconnecting: 'Reconnecting', + progress: 'Server update download', + release: 'What\'s new', + details: 'Details', + timing: 'Agents finish their current tool call, 30 seconds at most, then the server updates, restarts and resumes them. An agent update under way finishes first.', + backup: 'Conversations and settings are backed up. If startup fails, the previous version is restored.', + reconnect: 'Connected devices reconnect automatically.', + confirmTitle: 'Update {machine}?', + confirmBody: 'Agents finish their current tool call, 30 seconds at most. Version {version} then installs and they resume after the restart. Your conversations are backed up.', + manualTitle: 'Manual update', + docker: 'After agents finish, run this in the Compose directory:', + manual: 'Use the guide to update this installation.', + legacy: 'Update it once manually to enable this button.', + guide: 'Update guide' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/settingRequest.ts b/packages/ui/src/lib/strings/en/settingRequest.ts new file mode 100644 index 000000000..f1e9c06e2 --- /dev/null +++ b/packages/ui/src/lib/strings/en/settingRequest.ts @@ -0,0 +1,64 @@ +export const strings = { + /** A settings card an agent put in the conversation (`boite settings request|open`). */ + settingRequest: { + heading: 'Suggested setting', + headingOpen: 'Look in Settings', + headingSensitive: 'Sensitive setting', + apply: 'Apply', + show: 'Show in Settings', + open: 'Open Settings', + refuse: 'Refuse', + dismiss: 'Dismiss', + sensitiveHint: 'This one changes from Settings, beside its explanation. The card closes once it is changed.', + guestHint: 'Only the computer running Boite can change this setting.', + elsewhere: 'This is in the Settings of the computer running Boite.', + loadFailed: 'This settings card did not load.', + to: 'to', + applied: 'Applied', + changed: 'Set to {value} instead', + refused: 'Refused', + opened: 'Settings opened', + dismissed: 'Dismissed', + closed: 'Closed without an answer', + on: 'On', + off: 'Off', + none: 'None', + worktreeProject: 'Inside each project', + worktreeShared: 'In {dir}', + compactAt: 'From {tokens} tokens', + compactAny: 'At any size', + keys: { + asyncQuestions: 'Let agents ask without stopping', + shortAnswers: 'Short answers', + toolSummaries: 'Tool summaries (Claude)', + titleModel: 'Title model', + threadDoneRetentionDays: 'Delete done threads after', + threadDeletionRetentionDays: 'Purge deleted threads after', + worktreeStorage: 'Where new worktrees go', + focusGuard: 'Keep agent windows out of the foreground', + muteAgents: 'Mute agent audio', + agentCpuCapPercent: 'CPU cap for all agents', + agentMemoryBudgetPercent: 'Memory budget for all agents', + threadMemoryCapMb: 'Memory cap per conversation', + memoryReserveMb: 'Memory reserved for other apps', + memoryProtection: 'Protect available memory', + reapOrphans: 'Stop what agents leave running', + autoUpdateHarnesses: 'Update agents automatically', + listenOnLan: 'Reachable on the local network', + publicUrl: 'Public address for phones', + browserOrigins: 'Allowed browser origins', + warmProcessMinutes: 'Keep an agent warm after its turn', + autoCompact: 'Automatic compaction', + autoResumeLimitedThreads: 'Resume threads after a usage limit', + }, + /** What 0 means for the settings where it is not a number. */ + zero: { + agentCpuCapPercent: 'No cap', + threadMemoryCapMb: 'Automatic', + memoryReserveMb: 'Automatic', + threadDoneRetentionDays: 'Never', + threadDeletionRetentionDays: 'Never', + warmProcessMinutes: 'Released at once', + }, + } +} as const; diff --git a/packages/ui/src/lib/strings/en/settings.ts b/packages/ui/src/lib/strings/en/settings.ts new file mode 100644 index 000000000..2181897a8 --- /dev/null +++ b/packages/ui/src/lib/strings/en/settings.ts @@ -0,0 +1,237 @@ +export const strings = { + + settings: { + heading: 'Settings', + modelDefaults: 'Default models', + modelDefaultsHint: 'For new threads. Saved on this device.', + modelDefaultUnavailable: '{provider} does not offer the default model {model} on this account. Choose an available model in the picker or change the default in Settings.', + tabs: { + home: 'Home', + general: 'General', + machines: 'Machines and updates', + advanced: 'Advanced', + appearance: 'Appearance', + keyboard: 'Keyboard', + accounts: 'Providers', + plugins: 'Plugins', + usage: 'Usage', + resources: 'Protection', + experiments: 'Experiments', + diagnostics: 'Diagnostics' + }, + back: 'Back to threads', + search: 'Search settings', + searchEmpty: 'No setting matches', + homeGroups: ['App', 'Agents', 'Usage and limits', 'System'], + providersReady: '{count} connected', + providersOne: '1 connected', + machinesCount: '{count} machines', + devicesCount: '{count} paired devices', + devicesOne: '1 paired device', + connectProvider: 'Connect a provider', + conversations: 'Conversations', + groupWorkingThreads: 'Group working threads', + groupWorkingThreadsHint: 'Fold working and background threads in Projects and Recent. Pins, drafts, errors and threads that need you stay visible. This device only.', + groupOtherProjects: 'Group idle projects', + groupOtherProjectsHint: 'Fold projects without a visible conversation or draft into Idle projects in the Projects view. Turn off to keep all projects in the list. This device only.', + app: 'App', + tourReplay: 'Replay the tour', + execution: 'Agent execution', + warmProcessMinutesHint: 'How long an agent stays loaded after a turn. 0 stops it right away.', + autoCompact: 'Automatic compaction', + autoCompactHint: 'Boite asks the agent to compact between two turns, never during one. Each compaction counts as usage.', + autoCompactWhen: 'When', + autoCompactCondition: 'Condition', + autoCompactRecommended: 'Recommended', + autoCompactCacheExpiry: 'Before the prompt cache expires', + autoCompactCacheExpiryHint: 'One minute before the provider drops the cached conversation, if nothing happened since. Read at the cached price, and your next message starts from the short version.', + autoCompactBackground: 'While the agent waits on background work', + autoCompactBackgroundHint: 'After an answer that left a command or a monitor running. The compaction fills the wait.', + autoCompactTurnEnd: 'As soon as the agent has finished', + autoCompactTurnEndHint: 'Two seconds after an answer that left nothing running. A message sent right after waits for the compaction.', + autoCompactThreshold: 'Only above a size', + autoCompactThresholdHint: 'Smaller conversations are left alone, and so is a model that never gets that far.', + autoCompactThresholdOff: 'Off: every conversation is compacted at the moments above, whatever its size.', + autoCompactUnit: 'tokens', + projects: 'Projects', + /** The language the UI speaks, on this device. `system` follows the machine. */ + language: 'Language', + languageSystem: 'System', + languageNames: { en: 'English', fr: 'Français' }, + theme: 'Theme', + display: 'Display', + accent: 'Accent colour', + accentNames: ['Blue', 'Violet', 'Rose', 'Orange', 'Amber', 'Green', 'Teal'], + colors: { + title: 'Colours', + hint: 'Light and dark keep separate colours. Saved on this device.', + palette: 'Colour palette', + palettes: { default: 'Original', oled: 'OLED', catppuccin: 'Catppuccin', 'tokyo-night': 'Tokyo Night', nord: 'Nord', gruvbox: 'Gruvbox', amethyst: 'Amethyst' }, + customize: 'Customize colours', + roles: { background: 'Background', frame: 'Sidebar and frame', surface: 'Panels and messages', foreground: 'Text', accent: 'Accent' }, + edit: 'Choose a colour to edit', + reset: 'Reset', + undo: 'Undo', + field: '{color}: saturation and brightness', + coordinates: 'Saturation {saturation}%, brightness {brightness}%', + hue: 'Hue', + hex: 'Hex', + invalidHex: 'Use a hex colour such as #7AA2F7.', + contrast: 'Text contrast', + contrastHint: 'The lowest text contrast across the background, frame and panels. The target is 4.5:1.', + contrastLow: 'Low text contrast {ratio}:1', + improveContrast: 'Improve contrast' + }, + themeSystem: 'System', + themeDark: 'Dark', + themeLight: 'Light', + themeGrain: 'Grain', + reading: 'Reading', + font: 'Text font', + fontSystem: 'System', + /** Drawn in every face of the picker: mixed case, digits, and the glyphs that get confused (Il1, O0, rn). */ + fontSample: 'Rerun the 12 failing tests in Il1/O0/rn.ts', + monoFont: 'Code font', + monoSystem: 'System', + zoom: 'Interface size', + zoomHint: 'Ctrl+= and Ctrl+- anywhere in the app, Ctrl+0 back to 100%.', + zoomOut: 'Smaller', + zoomIn: 'Larger', + zoomReset: 'Back to 100%', + zoomFailed: 'The zoom did not change: {error}', + experiments: { + intro: 'These are unfinished and can change or disappear.' + }, + cacheKeepalive: 'Keep the prompt cache warm', + cacheKeepaliveHint: 'While a conversation waits on its agents or workflows, Boite sends a one-word turn shortly before the provider drops its cached prompt, so their result is read at the cached price. Each ping counts as usage. A ping that misses the cache ends the pings.', + cacheKeepaliveHours: 'For at most', + cacheKeepaliveHoursHint: 'Counted from the end of the conversation\'s last turn. Pings also stop when the agents finish, when you write or stop, and after as many pings as one cache miss would cost.', + cacheKeepaliveUnit: 'hours', + material: 'Window material', + workspace: 'Workspace', + startIn: 'The app opens on', + startDrafts: 'Drafts', + startProject: 'The last project', + panelStart: 'The side panel opens on', + panelLauncher: 'Its menu', + panelFiles: 'Files', + panelChanges: 'Changes', + materialAcrylic: 'Acrylic', + materialMica: 'Mica', + materialSolid: 'Solid', + chatWidth: 'Chat width', + groupChanges: 'Group modified files in the conversation', + expandDiffs: 'Expand change previews by default', + chatWidthComfortable: 'Comfortable', + chatWidthWide: 'Wide', + chatWidthFull: 'Full', + terminalCursor: 'Terminal cursor', + terminalCursorBar: 'Bar', + terminalCursorBlock: 'Block', + terminalCursorUnderline: 'Underline', + terminalGpu: 'Draw terminals on the GPU', + terminalGpuHint: 'Smoother scrolling and output. Turn it off if a terminal draws badly on this device; it applies to terminals opened next.', + connection: 'Connection', + localCore: 'This app runs on the core it started on this computer.', + pair: 'Pair', + manual: 'Core URL and token', + coreUrl: 'Core URL', + token: 'Token', + connect: 'Connect', + warmProcessMinutes: 'Warm process minutes', + listenOnLan: 'Reachable on the local network', + listenOnLanHint: 'Lets a phone on the same network reach this computer, from the next start of Boite. A --host or --lan flag wins over it.', + agentCpuCapPercent: 'CPU cap, %', + agentCpuCapHint: 'For all agents together. 0 means no cap. Windows only.', + threadMemoryCapMb: 'Memory cap per conversation, MB', + threadMemoryCapHint: 'For one conversation and its processes. 0 takes half the agents\' budget, which it cannot exceed.', + agentMemoryBudgetPercent: 'Memory budget for all agents, %', + agentMemoryBudgetHint: 'For all agents together, from 10 to 90%. The default is 60%.', + memoryReserveMb: 'Memory reserved for other apps, MB', + memoryReserveHint: 'Kept free for your other apps. 0 uses 10% of RAM, at least 3072 MB. Below 12288 MB of RAM, it uses 25%.', + memoryProtection: 'Protect available memory', + memoryProtectionHint: 'Over a limit, Boite stops the largest process tree an agent started, never the agent itself. Off also lifts the Windows memory caps.', + background: 'Background', + closeToTray: 'Keep Boite running when the window closes', + closeToTrayHint: 'Hides to the notification area and keeps turns running. Quit always stops Boite.', + launchAtLogin: 'Start Boite when I sign in', + launchAtLoginHint: 'Boite starts in the notification area with its core running, so this computer can run agents and answer your other devices without its window. Applies to this user account.', + notifications: 'Notify me when a thread finishes or asks something', + notificationsHint: 'Only for a thread you are not looking at. This machine only.', + notificationsDenied: 'Notifications are blocked for Boite on this system; allow them in its settings.', + focusGuard: 'Keep agent windows out of the foreground', + muteAgents: 'Mute agent audio', + reapOrphans: 'Stop what agents leave running', + reapOrphansHint: 'Ten seconds after a turn, a process whose parent has exited is stopped. Windows only.', + asyncQuestions: 'Let agents ask without stopping', + asyncQuestionsHint: 'Teaches agents the boite ask command when a session starts. Codex already asks this way.', + shortAnswers: 'Short answers', + shortAnswersHint: 'Asks every agent, on every message, for the result first in a few plain sentences, without preamble, recap or running commentary. Facts, numbers and commands stay.', + toolSummaries: 'Tool summaries (Claude)', + toolSummariesHint: 'Claude labels each batch of tool calls in one line instead of counting them. Each label is one request to its small model, Haiku, billed to the thread\'s account. Applies when a Claude process starts.', + titleModel: 'Title model', + titleModelHint: 'Names a thread after its first answer. Automatic uses the thread\'s own agent on its small model.', + titleModelAuto: 'Automatic', + titleModelAutoHint: "The thread's agent, small model", + titleModelSmall: 'small', + numberRange: 'A whole number from {min} to {max}.', + saved: 'Saved', + core: 'Core', + version: 'Version', + protocol: 'Protocol', + os: 'OS', + pid: 'PID', + dataDir: 'Data directory', + endpoint: 'Endpoint', + noCore: 'Not connected to a core.', + archived: { + doneRetentionLabel: 'Delete done threads after (days)', + doneRetentionHint: 'Counted from Mark done or an automatic archive after a PR merge. 3 days by default; 0 disables deletion. Manual archives are kept. Deleted conversations remain recoverable for the configured deletion retention.', + deletesAt: 'Deletion: {date}', + heading: 'Archived threads', + mergedReason: 'Archived after PR #{number} merged', + intro: 'The agent of an archived thread stops. It starts again with the first message after a restore.', + show: 'Show archived threads', + empty: 'No archived thread on this machine.', + restore: 'Restore', + }, + deleted: { + heading: 'Deleted conversations', + retentionLabel: 'Days before permanent deletion', + retentionHint: 'Counted from deletion. 0 keeps conversations indefinitely. Shortening the delay also applies to conversations already deleted.', + retentionError: 'Enter a whole number between 0 and 3650 days.', + purgesAt: 'Permanent deletion: {date}', + show: 'Show deleted conversations', + empty: 'No deleted conversation to restore on this machine.', + }, + /** The worktrees a thread leaves on disk, and their removal by hand. */ + worktrees: { + storage: 'Storage location', + storageProject: 'In each project', + storageShared: 'One shared folder', + storageProjectHint: 'Inside each project, under', + storageDirectory: 'Folder on this machine', + storagePlaceholder: 'Absolute folder path', + storageBrowse: 'Choose a folder', + storageSharedHint: 'Each project gets its own subfolder. Projects with the same name stay separate.', + storageNewOnly: 'New worktrees only. Existing ones stay where they are.', + storageInvalid: 'Enter an absolute folder path on the machine that runs your agents.', + storageSaveFailed: 'Could not save the storage location. Check the path and connection, then try again.', + heading: 'Worktrees', + intro: 'A worktree stays on disk after its thread is archived. Removing one deletes the folder and its branch.', + show: 'Show worktrees', + noProject: 'No git project on this machine.', + empty: 'No worktree left.', + sweep: 'Remove the clean ones ({count})', + remove: 'Remove', + dirty: 'uncommitted changes', + unmerged: 'commits on no other branch', + missing: 'folder gone', + heldBy: 'used by "{title}"', + archivedThread: 'thread archived: "{title}"', + heldHint: 'A thread works here: archive it first', + forceTitle: 'Remove {name} anyway?', + forceBody: 'It has {reasons}. Removing it deletes the folder and its branch, and that work is lost.' + }, + } +} as const; diff --git a/packages/ui/src/lib/strings/en/sidebar.ts b/packages/ui/src/lib/strings/en/sidebar.ts new file mode 100644 index 000000000..66b2ca4fc --- /dev/null +++ b/packages/ui/src/lib/strings/en/sidebar.ts @@ -0,0 +1,168 @@ +export const strings = { + + sidebar: { + doneThreads: 'Done', + /** Archived by hand to pick up later; done threads have their own group. */ + archivedGroup: 'Archived', + workingThreads: 'Working', + projectWorkingThreads: '{project}: {count} working', + projectDoneThreads: '{project}: {count} done', + projectArchivedThreads: '{project}: {count} archived', + /** The fold under the list for projects with no open conversation, and what choosing one does. */ + idleProjects: 'Idle projects', + /** The same fold once it also holds archived projects. */ + otherProjects: 'Other projects', + archivedProject: 'Archived', + groupingOptions: 'Display options', + /** The sidebar's top switch between the thread list and the Agents page. */ + surface: 'Threads or agents', + threads: 'Threads', + agents: 'Agents', + /** The count beside Threads or Agents in that switch: what works now, and whether something there needs the user. */ + surfaceWorking: '{count} at work', + surfaceWaiting: '{count} at work, something needs you', + surfaceSubagentsOne: 'including 1 subagent', + surfaceSubagentsMany: 'including {count} subagents', + /** The two ways the display options can list threads. */ + projectsView: 'Projects', + recentView: 'Recent', + /** The display options panel: its sections, and the short names its rows use under them. */ + displayView: 'View', + displayOrder: 'Project order', + displayGroup: 'Group', + groupWorkingRow: 'Working threads', + groupIdleRow: 'Idle projects', + customOrderShort: 'Custom', + autoArchiveMergedPrHint: 'A worktree conversation leaves the list for Done once its branch is merged. Its history stays. Applies to this project.', + autoRemoveWorktreesHint: 'An hour after every conversation in a worktree is done or archived, Boite deletes the worktree if nothing would be lost: its last commit is on another branch, or a merged pull request has that exact commit. A merged branch goes too. Uncommitted changes always stay. Applies to this project.', + noActiveProjects: 'No conversations need attention.', + markDone: 'Mark done', + doneToast: 'Marked "{title}" done', + doneLoadFailed: 'Some completed threads could not be loaded.', + /** The pull request under a done or archived conversation, which opens it on the forge. */ + prMerged: '#{number} merged', + prClosed: '#{number} closed', + prOpen: '#{number} open', + reopenThread: 'Move to Recent', + reopenNamedThread: 'Move "{title}" to Recent', + doneNotice: 'This thread is done. Move it to Recent to continue.', + /** Above the composer, or the done notice, of a thread whose worktree Boite removed. */ + worktreeGone: { + title: 'Worktree removed', + merged: 'Boite removed this thread\'s worktree {when}, after #{number} was merged.', + clean: 'Boite removed this thread\'s worktree {when}: every commit in it was already on another branch.', + branchDeleted: 'Branch {branch} was deleted.', + branchKept: 'Branch {branch} is still there.', + archived: 'Moved to Recent, this thread has no folder to work in.', + live: 'A new message here has no folder to run in. Start a new thread to keep working.' + }, + allProjects: 'All projects', + filterProject: 'Filter by project', + recentOrder: 'Recent activity', + customOrder: 'Custom order', + moveProjectUp: 'Move project up', + moveProjectDown: 'Move project down', + search: 'Search threads', + newThread: 'New thread', + newThreadIn: 'New thread in {project}', + addProject: 'Add a project', + noProjects: 'No project yet.', + noThreads: 'No thread yet.', + noMatch: 'Nothing matches.', + unread: 'Unread', + collapse: 'Hide sidebar', + expand: 'Show sidebar', + resize: 'Resize the sidebar, double-click to reset', + settings: 'Settings', + projectMenu: 'Project actions', + copyPath: 'Copy path', + manageProject: 'Manage project', + worktreeDefault: 'Worktree by default', + autoArchiveMergedPr: 'Hide merged PR conversations', + autoRemoveWorktrees: 'Remove settled worktrees', + backToProjectMenu: 'Back', + viewArchivedThreads: 'View archived threads', + importSession: 'Import a Claude Code session', + /** Out of the list, not out of Boite: its threads keep running and a new one brings it back. */ + archiveProject: 'Archive project', + refreshIcon: 'Refresh icon', + projectArchivedToast: 'Archived {project}', + archivedProjects: 'Archived projects', + restoreProject: 'Restore', + restoreAndStart: 'Restore {project} and start a thread', + removeProject: 'Remove from Boite', + /** On a project whose folder was deleted, moved or renamed outside Boite. */ + projectMissing: 'Folder not found', + projectMissingBody: 'The folder {path} does not exist any more: it was deleted, moved or renamed. Put it back to keep working here, or remove the project from Boite.', + projectMissingCheck: 'Check again', + removeProjectTitle: 'Remove {project} from Boite?', + removeProjectBody: 'Its threads go with it. Files on disk stay where they are.', + remove: 'Remove', + threadMenu: 'Thread actions', + /** A pencil on a thread's row: something typed in its box and not sent. */ + unsentDraft: 'Unsent draft', + retitle: 'Regenerate title', + retitling: 'Writing a title', + /** What a row shows in place of its time while the agent works or has news. */ + state: { + working: 'Working', + workingFor: 'Working for {elapsed}', + waiting: 'Needs you', + error: 'Failed', + done: 'Done', + queued: 'Queued', + /** The turn ended, the agent waits on the agents it delegated to: their results wake it. */ + delegating: 'Sub-agents', + delegatingOne: 'Waiting on 1 sub-agent', + delegatingMany: 'Waiting on {count} sub-agents', + /** The chip a parent's row carries while its delegated agents work, in every state. */ + subagentsOne: '1 sub-agent at work', + subagentsMany: '{count} sub-agents at work', + /** The chip's own words, its count written out: the full sentence stays in its title. */ + subagentsShortOne: '1 agent', + subagentsShortMany: '{count} agents', + /** The same chip once a sub-agent asks the user something: the parent reads Needs you. */ + subagentsAskingOne: '1 sub-agent needs you', + subagentsAskingMany: '{count} sub-agents need you', + /** The turn ended, the agent still watches something: a monitor it left going. */ + monitoring: 'Monitoring', + monitoringFor: 'Monitoring for {elapsed}', + /** The turn ended, the agent still runs a shell, a subagent or a workflow. */ + background: 'In background', + backgroundFor: 'In background for {elapsed}', + /** The turn ended, Boite watches pull requests for the agent and wakes it when they need it. */ + watching: 'Watching PR', + watchingMany: 'Watching {count} PRs', + /** A delegated agent's turn ended on its own background work, which wakes it: what it waits for. */ + awaiting: 'Waiting on {what}', + /** That work ended without waking it: the agent stopped there, its result never came. */ + interrupted: 'Interrupted', + /** Its machine stopped answering while it worked: what it does now is unknown until the machine is back. */ + unreachable: 'Unreachable', + unreachableWas: 'Machine unreachable. Last known state: {state}', + interruptedBy: '{what} ended without waking it: {reason}', + interruption: { + 'error': 'the task failed', + 'cancelled': 'the task was cancelled', + 'no-longer-reported': 'its process ended', + 'session-ended': 'its agent process was ended', + 'core-restarted': 'Boite or the PC restarted' + } + }, + /** A folded project's mark for the threads it hides: `2 threads: Needs you`. */ + rollupOne: '1 thread: {state}', + rollupMany: '{count} threads: {state}', + pin: 'Pin', + unpin: 'Unpin', + archive: 'Archive', + deleteUnavailable: 'Update Boite on the machine hosting this conversation to delete it. The conversation has been kept.', + deletedToast: 'Deleted "{title}". You can restore it from Settings.', + /** The toast after an archive, with its way back: the button and Ctrl+Z. */ + archivedToast: 'Archived "{title}"', + undo: 'Undo', + undoHint: 'Undo (Ctrl+Z)', + draft: 'New thread', + /** A draft row's menu: what is typed there and not sent goes. */ + deleteDraft: 'Delete draft' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/slash.ts b/packages/ui/src/lib/strings/en/slash.ts new file mode 100644 index 000000000..e1a54a772 --- /dev/null +++ b/packages/ui/src/lib/strings/en/slash.ts @@ -0,0 +1,12 @@ +export const strings = { + + slash: { + label: 'Commands', + agent: 'Agent', + app: 'Boite', + empty: 'No command matches', + model: 'Change the model', + effort: 'Change the reasoning effort', + mode: 'Change the permission mode' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/speech.ts b/packages/ui/src/lib/strings/en/speech.ts new file mode 100644 index 000000000..66392aa72 --- /dev/null +++ b/packages/ui/src/lib/strings/en/speech.ts @@ -0,0 +1,53 @@ +export const strings = { + speech: { + enabled: 'Voice dictation', + enabledHint: 'Enable dictation on this device. Turning it off removes the microphone from the message bar, including when a model is installed.', + microphone: 'Microphone', + microphoneHint: 'The input used for dictation on this device.', + defaultMicrophone: 'System default', + microphoneNumber: 'Microphone {number}', + microphoneUnavailable: 'The selected microphone is unavailable. Choose another input.', + microphoneDisconnected: 'The microphone was disconnected. Choose another input and try again.', + testMicrophone: 'Test microphone', + stopMicrophoneTest: 'Stop test', + inputLevel: 'Microphone input level', + microphoneTestHint: 'Speak to check the level. This test stays on your device.', + heading: 'Voice', description: 'The microphone beside Send, up to two minutes at a time. The text waits in the composer until you send it.', + start: 'Dictate', stop: 'Finish dictation', listening: 'Listening', opening: 'Waiting for microphone', transcribing: 'Transcribing', + private: 'On this core only', cloud: 'Transcribed by your API provider', retry: 'Retry transcription', discard: 'Discard recording', + live: 'Live preview', previewFailed: 'Live preview paused. Finish to transcribe the full recording.', + local: 'Local', api: 'API', engine: 'Engine', + engineHint: 'Local transcribes on this machine and the audio never leaves it. API sends the audio to Groq or OpenRouter with your key.', + streaming: 'Live transcription', + whistleLanguages: 'English, French, German, Spanish, Italian, Dutch and Polish', + setup: 'Dictation is not set up on this machine yet. Open Voice settings to set it up in one click.', ownerSetup: 'Ask the owner to configure Voice on this core.', + unsupported: 'Voice needs a newer Boite on {machine}', unsupportedHint: 'This machine runs a Boite version without the voice engine. Update Boite there, then open Voice again.', + unsupportedDictation: 'Dictation needs a newer Boite on {machine}. Update Boite there, then try again.', + notReady: 'Not set up yet', ready: 'Ready', + readyLocal: 'Ready, {model} on this machine', readyApi: 'Ready, {provider} transcribes', + notSetUp: 'Dictation is not set up', needsCli: 'This machine also needs whisper-cli on PATH, or its path under Advanced.', + statusKey: 'Add a {provider} key', downloadingModel: 'Downloading {model}', + statusFailed: 'The download did not finish', statusBroken: 'Voice settings could not be read', repair: 'Save to repair', + setUp: 'Set up dictation', + update: 'Update engine', updateHint: 'A faster engine is available, 8 MB.', + https: 'Microphone access needs HTTPS or localhost. Open this machine through its HTTPS address.', + linux: 'Dictation is not available in the Linux app yet: its webview does not grant the microphone. Open Boite in a browser to dictate.', + denied: 'Microphone permission was denied. Allow it in your browser or system settings, then try again.', + noMicrophone: 'No microphone was found. Connect one and try again.', microphoneBusy: 'The microphone is unavailable. Check whether another app is using it.', + silence: 'No speech was detected. Try again closer to the microphone.', failed: 'Could not record audio. Try again.', + model: 'Model', modelHint: 'Nemotron transcribes live in many languages. Whistle uses little disk space and supports seven languages. Whisper remains available for compatibility.', + tiers: { fast: 'Small download', balanced: 'Balanced', accurate: 'Accuracy' }, + download: 'Download', downloading: 'Downloading', remove: 'Remove {model}', + link: 'Add from a link', linkPlaceholder: 'https://huggingface.co/.../ggml-model.bin', + linkHint: 'An https link to a whisper.cpp ggml file, such as the ones on huggingface.co/ggerganov/whisper.cpp.', + add: 'Add', + advanced: 'Advanced', executable: 'Whisper executable', modelPath: 'Model file', savePaths: 'Save paths', + pathHint: 'Leave both empty to use the models above. A model file set here wins over the list.', + provider: 'Provider', providerHint: 'Both run Whisper Large v3 Turbo. Keys stay on this core.', groq: 'Groq', openrouter: 'OpenRouter', + groqKey: 'Groq key', openrouterKey: 'OpenRouter key', keySaved: 'Saved', keyEmpty: 'Paste a key', keyReplace: 'Paste a new key to replace it', + removeKey: 'Remove', saveKeys: 'Save keys', + fallback: 'Fall back to the other provider', fallbackHint: 'When the first provider fails. Needs both keys. Local never falls back to an API.', + language: 'Language', languageHint: 'Empty detects the language. A two-letter code such as en or fr fixes it.', + saved: 'Saved', ownerOnly: 'The owner of this machine manages it.', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/subscriptionProxy.ts b/packages/ui/src/lib/strings/en/subscriptionProxy.ts new file mode 100644 index 000000000..6082f17e8 --- /dev/null +++ b/packages/ui/src/lib/strings/en/subscriptionProxy.ts @@ -0,0 +1,62 @@ +export const strings = { + subscriptionProxy: { + heading: 'Subscription proxy', + enable: 'Use a subscription proxy', + hint: 'Claude and Codex use this machine\'s gateway, Grok does too behind Douane, and OpenCode 2 adds its other models to its own. Limits opens its dashboard inside Boite. Other agents keep their own configuration.', + baseUrl: 'API URL', dashboardUrl: 'Limits dashboard URL', key: 'API key (optional)', + keyHint: 'Leave empty to keep the saved key. This is the proxy\'s API key, not its dashboard management key.', + clearKey: 'Remove saved key', save: 'Save proxy', saved: 'Proxy saved', + configure: 'Configure proxy', native: 'Show account limits', + showDashboard: 'Show proxy dashboard', + openGateway: 'Open {name}', + signInHint: 'Sign in with Open {name}, then reload here. If the page stays blank, this dashboard may block embedded views.', + mixedContent: 'This page uses HTTPS. Set an HTTPS dashboard URL to view it here, or open the HTTP dashboard separately.', + enabledHint: 'When on, the accounts signed in on this machine are no longer used for Claude and Codex, nor for Grok behind Douane.', + /** The single account a proxied provider shows. */ + account: 'Requests go through {name} at {origin}. This machine\'s own sign-ins are not used.', + via: 'via {name}', + quotasFailed: 'Could not read the {name} limits: {error}', + quotasEmpty: '{name} reports no limits yet.', + /** An entry that stands for several gateway accounts, as their average. */ + averageOne: '{name} · 1 account', + average: '{name} · {count} accounts', + entryStatus: { ready: 'Ready', cooldown: 'Cooling down', error: 'Error', disabled: 'Disabled' }, + creditOf: '{remaining} of {limit}', + creditUsed: '{used} used', + /** Where the gateway runs: a URL the user types, or Douane run by this machine's core. */ + where: 'Where the gateway runs', + remote: 'Remote gateway', local: 'Douane on this machine', + localHint: 'The Douane plugin, installed and run by Boite from Settings > Plugins. The proxy points at its loopback port by itself, and follows it if the port changes.', + localMissing: 'The Douane plugin is not installed on this machine.', + localStopped: 'The Douane plugin is stopped.', + localStarting: 'The Douane plugin is starting.', + localRunning: 'Douane {version} answers on 127.0.0.1:{port}.', + openPlugins: 'Open Plugins', + /** Moving this machine's sign-ins and OpenCode keys into Douane. */ + move: { + open: 'Move my connections to Douane', + openHint: 'Douane takes the sign-ins and API keys this machine already has, so nobody signs in again or pastes a key.', + title: 'Move connections to Douane', + intro: 'Claude, Codex and Grok sign-ins move: their tokens rotate, so once Douane holds one it serves that agent through the gateway and this machine\'s copy stops working. Turning the gateway off later means signing in again here. API keys are copied as they are.', + loading: 'Reading this machine\'s sign-ins', + empty: 'Nothing here that Douane can take.', + token: 'Management token (optional)', + tokenHint: 'Only for a Douane whose admin API asks for one. It stays on this machine and is never shown again.', + tokenSaved: 'A management token is saved; leave empty to keep it.', + clearToken: 'Remove saved token', + confirm: 'Move {count}', + moving: 'Moving', + failed: 'Could not move: {error}', + login: '{provider} sign-in', + copilot: 'GitHub Copilot sign-in', + endpoint: '{source} API key', + opencodeGo: 'OpenCode Go key', + route: 'served as {route}', + outcomes: { moved: 'Moved', present: 'Already there', refused: 'Refused', missing: 'No longer here' }, + reasons: { + signedOut: 'Not signed in', unreadable: 'Login file unreadable', noAddress: 'No known API address', + otherApi: 'Not an OpenAI-compatible API', otherSignIn: 'Douane cannot take this sign-in', routeTaken: 'Route used by another endpoint', + }, + }, + } +} as const; diff --git a/packages/ui/src/lib/strings/en/taskManager.ts b/packages/ui/src/lib/strings/en/taskManager.ts new file mode 100644 index 000000000..b1d364673 --- /dev/null +++ b/packages/ui/src/lib/strings/en/taskManager.ts @@ -0,0 +1,16 @@ +export const strings = { + taskManager: { + title: 'Task manager', scope: 'Live processes on this machine, grouped by agent.', + agents: 'Agents', live: 'Updated every 2 seconds', paused: 'Updates paused', disconnected: 'Machine disconnected', + loading: 'Reading agent usage', unsupported: 'Update Boite on this machine to see agent usage.', + loadFailed: 'The task manager could not load. Try again.', + search: 'Find an agent', noMatches: 'No matching agents.', sortBy: 'Sort by usage', + sort: { memory: 'Most memory', cpu: 'Most CPU', disk: 'Most disk activity', network: 'Most network activity' }, + disk: 'Disk', network: 'Network', read: 'Read', write: 'Write', download: 'Download', upload: 'Upload', + observed: 'Observed', unavailableHint: 'No measurement is available yet, or this counter is unsupported. A missing value does not mean zero usage.', + partialHint: 'This total includes only agents with available measurements.', + diskHint: 'Storage reads and writes observed while this view is open. Brief processes can be missed. This is activity, not folder size.', + tcpHint: 'TCP downloads and uploads observed while this view is open. Brief connections and UDP traffic can be missed.', + stop: 'Stop processes', stopConfirm: 'Stop every process in this conversation?', stopping: 'Stopping processes', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/tasks.ts b/packages/ui/src/lib/strings/en/tasks.ts new file mode 100644 index 000000000..dd90cd963 --- /dev/null +++ b/packages/ui/src/lib/strings/en/tasks.ts @@ -0,0 +1,40 @@ +export const strings = { + + /** The tasks surface: the thread's activity, the agent's tasks, the project's todos. */ + tasks: { + goalSection: 'Goal and loop', + noActivity: 'No goal and no loop on this thread', + nextRun: 'Next run {time}', + iterations: '{count} iterations', + agentSection: 'Agent tasks', + noTasks: 'The agent has reported no task list', + todoSection: 'Project todos', + noTodos: 'No todo on this project', + addPlaceholder: 'Add a todo', + add: 'Add', + // The chip on a claimed card. Short enough to leave the card's own text + // readable on a narrow panel; the whole sentence is its tooltip. + awaiting: 'Awaiting you', + awaitingHint: 'This card waits on your confirmation', + markOpen: 'Move back to open', + markClaimed: 'Mark it waiting on you', + markDone: 'Confirm it is done', + removeTodo: 'Remove this todo', + collapse: 'Fold this section', + expand: 'Unfold this section', + /** The pull requests the agent asked Boite to watch (`boite pr watch`). */ + watchSection: 'Pull request watches', + noWatches: 'No pull request watched', + watchHint: 'Boite reads the pull request on GitHub about once a minute and wakes the agent when a check fails, the checks pass, a review or comment lands or the branch conflicts', + watchNotRead: 'Not read yet', + watchNoChecks: 'No checks', + watchChecks: '{total} checks', + watchRunning: '{count} running', + watchFailed: '{count} failed', + watchConflict: 'Conflicts with {base}', + watchRateLimited: 'GitHub rate limit, next read {time}', + watchUnreachable: 'GitHub out of reach, next read {time}', + stopWatch: 'Stop watching', + stopWatchOf: 'Stop watching #{number}' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/telemetry.ts b/packages/ui/src/lib/strings/en/telemetry.ts new file mode 100644 index 000000000..a913f3ec5 --- /dev/null +++ b/packages/ui/src/lib/strings/en/telemetry.ts @@ -0,0 +1,13 @@ +export const strings = { + telemetry: { + heading: 'Privacy and analytics', + description: 'Basic counters are on by default. Enhanced analytics are optional. No messages, project names, paths, account details or recordings are collected.', + basic: 'Anonymous counters', + basicHint: 'Count active installations, launches, conversations and turn outcomes. Usage identifiers change daily; a separate identifier counts active installations only.', + enhanced: 'Enhanced analytics', + enhancedHint: 'Share selected public models, reasoning level, speed and permission modes, durations and token volumes with a stable random installation ID. Private models are grouped as other. Turning this off requests deletion of its profile and events.', + unconfigured: 'This build has no analytics relay configured. Nothing is sent.', + pending: 'Deletion is pending. Boite retries in the background. PostHog removes events asynchronously after accepting the request.', + export: 'Export analytics data', retry: 'Retry deletion', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/terminal.ts b/packages/ui/src/lib/strings/en/terminal.ts new file mode 100644 index 000000000..66649855a --- /dev/null +++ b/packages/ui/src/lib/strings/en/terminal.ts @@ -0,0 +1,29 @@ +export const strings = { + + terminal: { + title: 'Terminal', + hide: 'Hide the terminal', + close: 'Close the shell', + resize: 'Resize the terminal', + /** A tab, `Terminal 2`, named after its first shell as T3 Code does. */ + tab: 'Terminal {n}', + tabs: 'Terminals', + new: 'New terminal', + splitRight: 'Split right', + splitDown: 'Split down', + closeTab: 'Close the tab', + resizePanes: 'Resize the split', + find: 'Find in the terminal', + history: 'Recent commands', + historySearch: 'Search the commands', + historyEmpty: 'No command in this shell\'s history yet', + historyNone: 'No command matches', + historyOld: 'This machine runs an older Boite that cannot list them', + historyLoading: 'Reading the history', + /** Under the list: what the keys do with the command picked. */ + historyHint: 'Enter runs it, Shift+Enter puts it at the prompt', + running: 'A command is running', + /** `exit 1`: the status of the last command, which failed. */ + failed: 'The last command failed (exit {code})' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/thread.ts b/packages/ui/src/lib/strings/en/thread.ts new file mode 100644 index 000000000..d99a77b41 --- /dev/null +++ b/packages/ui/src/lib/strings/en/thread.ts @@ -0,0 +1,73 @@ +export const strings = { + + thread: { + recoveryTitle: 'Prompt saved before restart', + recoveryBody: 'This prompt has not been sent to the agent. Resume it with its saved model and account, or remove it from the queue.', + recoveryTarget: 'Saved selection: {model} · {account}', + recoveryResume: 'Resume prompt', + recoveryDiscard: 'Remove from queue', + retryCapacityTitle: 'Model at capacity', + retryCapacityBody: 'Trying again in {time}, try {attempt} of {attempts}.', + retryCapacityCancel: 'Stop retrying', + retryLimitTitle: 'Usage limit reached', + retryLimitBody: 'Resumes at {time}, once the limit has reset, in {in}.', + retryLimitCancel: 'Do not resume', + retryContinueTitle: 'Stopped before its answer', + retryContinueBody: 'Resumes by itself in {time}, resume {attempt} of {attempts}.', + contextDetails: 'Context', + contextInput: 'Input', + contextCache: 'Cached input', + contextOutput: 'Output', + contextUsed: 'Used', + contextFree: 'Available', + contextNoBreakdown: 'This agent does not report a breakdown.', + contextNoReading: 'No measurement received from this agent yet.', + contextMeasured: 'Last measurement', + cacheTitle: 'Prompt cache', + cacheChipWarm: (left: string) => `Prompt cache warm, about ${left} left`, + cacheChipMaybe: (most: string) => `Prompt cache maybe still warm, ${most} at most`, + cacheChipCold: 'Prompt cache cold', + cacheCold: 'cold', + cacheLeft: (left: string) => `${left} left`, + cacheMaybe: (left: string) => `maybe · ${left}`, + cacheSwitched: 'Model or account changed.', + cacheLifetime: 'Lifetime', + cacheSourceReported: 'API', + cacheSourceDocumented: 'docs', + cacheRange: (lifetime: string, most: string) => `${lifetime} to ${most}`, + cacheReadLast: 'Read last turn', + minutes: (n: number) => `${n} min`, + hours: (n: number) => `${n} h`, + none: 'No thread open', + noneBody: 'Pick one on the left, or start a new one.', + trace: 'Trace', + /** The header's button: it shows or hides the panel, whatever surface is in it. */ + panel: 'Panel', + panelHint: 'Browser, files and tools beside this conversation', + terminalHint: "Terminal in this thread's folder", + cwd: 'Working directory', + model: 'Model', + defaultModel: 'Default model', + renamePlaceholder: 'Thread title', + /** The draft's own heading, the project's name in the dropdown after it. */ + start: 'Start a thread', + inWorktree: 'in a worktree', + inProject: 'in', + using: 'using', + onEffort: 'on {effort} effort', + draftMode: { + default: 'with approval requests', + acceptEdits: 'with edits allowed', + bypassPermissions: 'with all permissions', + yolo: 'with tool approvals bypassed', + plan: 'in plan mode', + dontAsk: 'with automatic denial of approval requests' + }, + changeProject: 'Change project', + /** The project dropdown's search field and what it says when no project matches. */ + searchProjects: 'Search projects', + noProjectMatch: 'No project matches "{query}"', + /** The header badge of a thread working in its own worktree; the title says where. */ + branchHint: 'Working in a worktree on this branch', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/threadMove.ts b/packages/ui/src/lib/strings/en/threadMove.ts new file mode 100644 index 000000000..7d394b27f --- /dev/null +++ b/packages/ui/src/lib/strings/en/threadMove.ts @@ -0,0 +1,33 @@ +export const strings = { + + /** Moving a thread to another project: the menu entry, its project picker and the question about background work. */ + threadMove: { + moveTo: 'Move to project', + /** A project put away, in the picker: moving a thread there brings it back. */ + archivedProject: '{project} (archived)', + noOtherProject: 'No other project', + /** Why the entry is out while a sub-thread works: the parent's turn end is no time to move it. */ + stopFirst: 'A sub-thread is working; stop it before moving this thread', + /** The confirm for a thread whose turn runs: the move waits for the turn to end. */ + runningTitle: 'Move when this turn ends', + /** `{folder}` is the folder the thread leaves, `{project}` the one it goes to. */ + runningBody: 'The agent is working in {folder}. The thread moves to {project} when the current turn ends.', + runningConfirm: 'Move after this turn', + /** Same, when the agent also runs something in the background: stop it with the move, or keep it running. */ + runningBackgroundBody: 'The agent is working in {folder}. The thread moves to {project} when the current turn ends. Something it started keeps running in the background: stop it then, or keep it running there until the agent starts its next turn in {project}.', + /** Under the thread's title in the sidebar and the header until the move happens. */ + pending: 'Moves to {project} after this turn', + cancelMove: 'Cancel move', + monitorsTitle: 'Monitors still running', + backgroundTitle: 'Work still running in the background', + /** `{folder}` is the folder the thread leaves, `{project}` the one it goes to. */ + backgroundBody: 'The agent still runs something in {folder}. Stop it now, or keep it running there until the agent starts its next turn in {project}.', + stopMonitors: 'Stop monitors', + stopWork: 'Stop it', + keep: 'Keep them', + /** Said by a project while a thread is dragged over it. */ + dropHere: 'Move the thread to {project}', + /** Under the card a dragged row becomes, while no project would take it. */ + dragHint: 'Drop on another project' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/threadStatus.ts b/packages/ui/src/lib/strings/en/threadStatus.ts new file mode 100644 index 000000000..e7ba03fed --- /dev/null +++ b/packages/ui/src/lib/strings/en/threadStatus.ts @@ -0,0 +1,10 @@ +export const strings = { + + threadStatus: { + idle: 'idle', + queued: 'queued', + running: 'running', + waiting: 'waiting for you', + error: 'error' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/time.ts b/packages/ui/src/lib/strings/en/time.ts new file mode 100644 index 000000000..2aae44c2c --- /dev/null +++ b/packages/ui/src/lib/strings/en/time.ts @@ -0,0 +1,6 @@ +export const strings = { + + time: { + now: 'now' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/titlebar.ts b/packages/ui/src/lib/strings/en/titlebar.ts new file mode 100644 index 000000000..3623a0039 --- /dev/null +++ b/packages/ui/src/lib/strings/en/titlebar.ts @@ -0,0 +1,17 @@ +export const strings = { + + titlebar: { + minimize: 'Minimize', + maximize: 'Maximize', + restore: 'Restore', + quitHold: 'Hold Ctrl+Q to quit', + quitHoldHint: 'or press it twice', + quitTitle: 'Close Boite?', + quitWorkingOne: '1 agent is still working or waiting. Are you sure you want to close Boite?', + quitWorkingMany: '{count} agents are still working or waiting. Are you sure you want to close Boite?', + quitUnknownBody: 'Work on a disconnected machine could not be checked.', + quitResidentBody: 'Resident engines keep working after the app closes. Engines configured to stop with the app will stop.', + quitConfirm: 'Close Boite', + quitCancel: 'Keep open' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/trace.ts b/packages/ui/src/lib/strings/en/trace.ts new file mode 100644 index 000000000..be5b60faa --- /dev/null +++ b/packages/ui/src/lib/strings/en/trace.ts @@ -0,0 +1,22 @@ +export const strings = { + + trace: { + exact: 'All child processes tracked', + limited: 'Limited process tracking', + finished: 'Finished', + parent: 'Parent PID', + cpuTime: 'CPU time', + currentMemory: 'Memory now', + active: 'active', + recorded: 'recorded', + exe: 'Executable', + pid: 'PID', + duration: 'Duration', + cpu: 'CPU', + memory: 'Peak memory', + io: 'I/O', + exit: 'Exit', + live: 'live', + empty: 'This thread has launched no process yet.', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/units.ts b/packages/ui/src/lib/strings/en/units.ts new file mode 100644 index 000000000..51b31b959 --- /dev/null +++ b/packages/ui/src/lib/strings/en/units.ts @@ -0,0 +1,14 @@ +export const strings = { + + units: { + bytes: 'B', + kilobytes: 'kB', + megabytes: 'MB', + gigabytes: 'GB', + milliseconds: 'ms', + seconds: 's', + minutes: 'min', + hours: 'h', + tokens: 'tokens' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/updateAll.ts b/packages/ui/src/lib/strings/en/updateAll.ts new file mode 100644 index 000000000..26873755e --- /dev/null +++ b/packages/ui/src/lib/strings/en/updateAll.ts @@ -0,0 +1,25 @@ +export const strings = { + updateAll: { + heading: 'Updates', + trigger: 'Updates available', + busy: 'Updating', + run: 'Update all', + app: 'Update the app', + retry: 'Try again', + later: 'Remind me later', + restarts: 'Boite downloads its release, then asks to restart. Threads resume where they were.', + summary: (count: number) => count === 1 ? '1 update' : `${count} updates`, + downloading: (percent: number | null) => percent === null ? 'Downloading' : `Downloading ${percent}%`, + ready: 'Restart to finish', + restartsNext: 'Restart once the agents are done', + waiting: 'Waits for work to finish', + installing: 'Restarting', + updating: 'Updating', + waitingFor: (count: number) => count === 1 ? 'Waits for a thread to pause' : `Waits for ${count} threads to pause`, + failed: 'Failed', + done: 'Up to date', + allDone: 'Everything is up to date', + finish: 'Boite is ready: restart it to finish.', + restart: 'Restart' + } +} as const; diff --git a/packages/ui/src/lib/strings/en/usage.ts b/packages/ui/src/lib/strings/en/usage.ts new file mode 100644 index 000000000..535259e56 --- /dev/null +++ b/packages/ui/src/lib/strings/en/usage.ts @@ -0,0 +1,73 @@ +export const strings = { + + usage: { + heading: 'Usage', + note: 'Costs are the tokens at each model\'s public API rates, from models.dev. A model with no listed rate uses the cost its agent reported. On a subscription this is not an invoice.', + thread: 'Thread', + input: 'Input', + output: 'Output', + cost: 'API-rate estimate', + total: 'Total', + empty: 'No token spent yet.', + intro: 'Spent through Boite. Tokens include cache reads and writes.', + range: 'Range', + days: '{days} days', + metric: 'Measure', + provider: 'Provider', + allProviders: 'All providers', + machine: 'Machine', + allMachines: 'All machines', + machinesOffline: 'Not counted, not connected: {machines}', + offline: 'Not connected', + unknownProvider: 'Unknown provider', + filterUnsupported: 'This machine did not filter usage by provider. Update Boite on it and retry.', + noActivity: 'No turns', + noUsage: 'Not reported', + coverage: 'Tokens {reported}/{turns} turns · Cost {priced}/{turns}', + partialCost: '{count} turns have no price and are left out of the estimate.', + rates: 'Public API rates of {model}, per million tokens: input {input}, output {output}, cache read {cacheRead}, cache write {cacheWrite}.', + cacheSplit: 'Cache: {read} read, {write} written.', + cacheSplitCost: 'Cache: {read} read ({readCost}), {write} written ({writeCost}).', + reportedCost: 'No public rate matches this model, so the cost is the one its agent reported.', + noRates: 'No public rate matches this model and its agent reported no cost.', + olderCore: 'The machine that ran these turns has a Boite older than public API rates, so only a cost its agent reported shows here. Update it in Settings, Machines and updates to price them.', + noTokens: 'The agent reported no tokens for these turns, so they have no cost.', + priceOf: 'the price of {model}', + metrics: { tokens: 'Tokens', cost: 'API estimate', turns: 'Turns' }, + totalOf: { + tokens: 'Tokens, last {days} days', + cost: 'API-rate estimate, last {days} days', + turns: 'Turns, last {days} days' + }, + summary: '{turns} turns · {tokens} tokens · {cost} at API rates', + summaryNoPrice: '{turns} turns · {tokens} tokens · No price', + summaryMissing: '{turns} turns · Usage not reported', + overview: 'Overview', + chart: 'Per day', + chartLabel: '{metric} per day and provider. Use the arrow keys to read each day.', + share: '{percent} of the total', + breakdown: 'Breakdown', + byModel: 'By model', + byDay: 'By day', + model: 'Model', + day: 'Day', + turns: 'Turns', + tokens: 'Tokens', + cache: 'Cache', + noPrice: 'No price', + defaultModel: 'Default model', + other: 'Other', + threads: 'Top threads', + noRankedThreads: 'No threads to rank for this measure.', + threadTurns: '{turns} turns', + untitled: 'Untitled thread', + archived: 'Archived', + emptyRange: 'No turn finished in the last {days} days.', + unpriced: 'No price for {providers}: no public rate and no reported cost.', + unreported: '{count} turns reported no usage. They count as turns only.', + limits: 'Limits', + refresh: 'Refresh usage', + loading: 'Reading usage', + failed: 'Could not read usage: {error}', + } +} as const; diff --git a/packages/ui/src/lib/strings/en/workflow.ts b/packages/ui/src/lib/strings/en/workflow.ts new file mode 100644 index 000000000..1c29501fa --- /dev/null +++ b/packages/ui/src/lib/strings/en/workflow.ts @@ -0,0 +1,32 @@ +export const strings = { + + workflow: { + activeOne: '1 workflow', + activeMany: '{count} workflows', + status: { running: 'Running', paused: 'Paused', done: 'Done', failed: 'Failed', stopped: 'Stopped' }, + stepStatus: { waiting: 'Waiting', running: 'Running', done: 'Done', failed: 'Failed', skipped: 'Skipped', stopped: 'Stopped' }, + steps: '{done}/{total} steps', + phase: 'Phase {n}', + pause: 'Pause', + resume: 'Resume', + stop: 'Stop', + retry: 'Retry', + stopTitle: 'Stop "{name}"?', + stopBody: 'Running steps are stopped and the rest never starts. Finished steps keep their results.', + forEach: 'One per item of {path}', + after: 'After {steps}', + condition: 'Only if {path}', + task: 'Task', + result: 'Result', + output: 'Output', + items: 'Executions', + openThread: 'Open conversation', + back: 'Back to the graph', + close: 'Close details', + notStarted: 'Not started yet.', + skipped: 'Skipped: its condition did not hold.', + card: 'Workflow', + show: 'Show the workflow', + tokens: '{tokens} tokens' + } +} as const; diff --git a/packages/ui/src/lib/strings/fr/accounts.ts b/packages/ui/src/lib/strings/fr/accounts.ts new file mode 100644 index 000000000..e56c1b4d7 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/accounts.ts @@ -0,0 +1,31 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + accounts: { + heading: 'Fournisseurs', + login: 'Se connecter', + loginStarting: 'Démarrage de la connexion', + loginOpen: 'Ouvrir la page de connexion', + loginHint: 'Terminez dans votre navigateur. Cette ligne passera prête toute seule.', + loginInputPlaceholder: 'Collez le code donné par la page, si elle en donne un', + loginRedirectPlaceholder: "Sur un autre appareil ? Collez l'adresse localhost sur laquelle la page se termine", + loginSend: 'Envoyer', + terminalHint: 'Répondez à ses questions dans le terminal, puis fermez-le une fois connecté.', + terminalDone: 'Fermer le terminal', + remove: 'Supprimer', + removeTitle: 'Supprimer {account} ?', + removeBody: "Cela déconnecte le compte de Boite et supprime ses fichiers.", + removeDefaultBody: "Boite cesse d'utiliser ce login. Il reste connecté sur votre ordinateur.", + removeThread: "1 conversation utilise encore ce compte. Son prochain message la fait passer sur un autre compte connecté du même agent, ce qui recharge tout son contexte.", + removeThreads: "{count} conversations utilisent encore ce compte. Le prochain message de chacune la fait passer sur un autre compte connecté du même agent, ce qui recharge tout son contexte.", + removed: 'Compte supprimé', + removedHint: "Le compte de cette conversation a été supprimé. Choisissez un modèle sur un autre compte pour continuer.", + status: { + unknown: 'inconnu', + ok: 'ok', + unauthenticated: 'non connecté', + error: 'erreur' + } + } +}; diff --git a/packages/ui/src/lib/strings/fr/activity.ts b/packages/ui/src/lib/strings/fr/activity.ts new file mode 100644 index 000000000..9f27cb219 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/activity.ts @@ -0,0 +1,64 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + activity: { + goal: 'Objectif', + question: 'Question', + questionOf: '{index} sur {total}', + questionPrev: 'Question précédente', + questionNext: 'Question suivante', + questionFold: 'Replier la question', + questionUnfold: 'Déplier la question', + subagentAsks: 'Demandé par {title}', + subagentPermission: '{title} attend votre autorisation', + openSubagent: 'Ouvrir', + openSubagentHint: 'Ouvrir {title}', + loop: 'Boucle', + tasks: 'Tâches', + taskCount: '{done}/{total} tâches', + goalDescription: 'Continuer à travailler vers un objectif', + loopDescription: 'Répéter une tâche un nombre de fois ou selon un rythme', + goalUsage: 'Utilisez /goal suivi d\'un objectif.', + goal2: 'Objectif relu', + goal2Description: "Travailler vers un objectif jusqu'à ce qu'un relecteur neuf le valide", + goal2Usage: "Utilisez /goal2 suivi d'un objectif.", + reviewing: 'En relecture', + reviews: 'Relectures', + reviewRound: 'Relecture {round}', + reviewRoundOf: 'Relecture {round} sur {total}', + reviewInProgress: 'Relecture {round} en cours', + openReview: 'Ouvrir la conversation du relecteur', + pass: 'Validé', + partial: 'Partiel', + fail: 'Refusé', + missing: 'Sans verdict', + loopUsage: 'Utilisez /loop 2 prompt pour deux itérations, ou /loop 5m prompt pour un rythme. Entre 1 et 1000 itérations.', + intervalError: "L'intervalle d'une boucle doit tenir entre 1 seconde et 24 heures.", + active: 'Active', + paused: 'En pause', + complete: 'Terminée', + blocked: 'Attend votre réponse', + blockedHint: "Votre prochain message relance l'objectif.", + reply: 'Répondre', + pause: 'Mettre en pause', + resume: 'Reprendre', + remove: 'Retirer', + finish: 'Marquer terminée', + every: 'Toutes les {interval}', + iterations: '{count} exécutions', + iteration: 'Itération {count}', + iterationOf: 'Itération {count} sur {total}', + history: 'Historique des itérations', + noHistory: 'Aucune itération terminée pour le moment', + allTasksDone: 'Toutes les tâches sont terminées', + waitingTasks: 'En attente de la prochaine tâche', + stopped: 'Arrêtée', + running: 'En cours', + done: 'Terminée', + error: 'Échec', + pending: 'À faire', + in_progress: 'En cours', + completed: 'Terminée' + } +}; diff --git a/packages/ui/src/lib/strings/fr/agentBrowser.ts b/packages/ui/src/lib/strings/fr/agentBrowser.ts new file mode 100644 index 000000000..c0446a74b --- /dev/null +++ b/packages/ui/src/lib/strings/fr/agentBrowser.ts @@ -0,0 +1,11 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + agentBrowser: { + cover: 'Cet agent contrôle un navigateur sur {machine}', coverHint: 'Rien n’est diffusé vers cet appareil avant que vous l’affichiez.', + show: 'Afficher', hide: 'Masquer', hideHint: 'Arrêter de regarder et recouvrir la vue', + none: 'L’agent n’a pas de navigateur ouvert', noneHint: 'Il apparaît ici quand l’agent ouvre une page.', + unavailable: 'Pas de navigateur d’agent sur {machine}', tabs: 'Onglets de l’agent', untitled: 'Page sans titre', + newTab: 'Nouvel onglet', closeTab: 'Fermer {name}', newTabHint: 'Ouvrez une page dans le navigateur de cette conversation sur {machine}. L’agent la voit et peut s’en servir.' + } +}; diff --git a/packages/ui/src/lib/strings/fr/agentMessages.ts b/packages/ui/src/lib/strings/fr/agentMessages.ts new file mode 100644 index 000000000..6f2ec3840 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/agentMessages.ts @@ -0,0 +1,24 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + agentMessages: { + heading: 'Discussions', + open: 'Ouvrir la discussion', back: 'Toutes les discussions', openThread: 'Ouvrir cette conversation', + empty: 'Aucune discussion avec un autre agent pour le moment.', + sentReceived: '{sent} envoyés, {received} reçus', + mine: 'Cet agent', + you: 'Vous', + result: 'Résultat', + about: 'Travaille sur', + replyTo: 'En réponse à', + more: '+{count} autres', + sentOne: 'Envoyé à', sentMany: '{count} messages envoyés à', + receivedOne: 'Reçu de', receivedMany: '{count} messages reçus de', + exchanged: 'Échange avec', + threadMissing: "Aucune machine connectée n'a cette conversation.", + relation: { + starter: 'A lancé cette conversation', started: 'Lancée d\'ici', parent: 'Parent', child: 'Sous-agent', resident: 'Veille sur le projet' + } + } +}; diff --git a/packages/ui/src/lib/strings/fr/agentSettingRequest.ts b/packages/ui/src/lib/strings/fr/agentSettingRequest.ts new file mode 100644 index 000000000..98923e35a --- /dev/null +++ b/packages/ui/src/lib/strings/fr/agentSettingRequest.ts @@ -0,0 +1,35 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + agentSettingRequest: { + heading: 'Changement suggéré pour {name}', + apply: 'Appliquer', + show: 'Ouvrir ses réglages', + refuse: 'Refuser', + guestHint: "Seul l'ordinateur qui fait tourner Boite peut changer les réglages d'un agent.", + projectsHint: 'Décochez un projet pour le laisser de côté.', + entrust: 'Confier', + takeBack: 'Reprendre', + loadFailed: "Cette carte de réglage d'agent n'a pas chargé.", + to: 'vers', + applied: 'Appliqué', + changed: 'Réglé sur {value} à la place', + refused: 'Refusé', + closed: 'Fermé sans réponse', + on: 'Activé', + off: 'Désactivé', + none: 'Aucun', + noProjects: 'Aucun projet', + defaultEffort: 'Par défaut', + minutes: '{count} min', + send: 'Envoyer', + keys: { + projects: 'Projets', + model: 'Modèle', + effort: 'Niveau de raisonnement', + autoMemory: 'Mettre à jour la mémoire après chaque tâche', + subagents: 'Sous-agents', + maxRunMinutes: 'Minutes maximum par exécution', + }, + } +}; diff --git a/packages/ui/src/lib/strings/fr/agentShape.ts b/packages/ui/src/lib/strings/fr/agentShape.ts new file mode 100644 index 000000000..5529f5390 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/agentShape.ts @@ -0,0 +1,31 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + agentShape: { + open: "Ce que voit l'agent", + title: "Ce que voit l'agent", + intro: 'Le prompt système et les outils de la dernière requête, tels que Claude Code les a notés.', + loading: 'Lecture du transcript', + failed: 'Lecture du transcript impossible : {error}', + noSession: 'Aucune requête pour l\'instant : envoyez un premier message.', + noTranscript: "Le transcript de la session n'est plus sur cette machine.", + unrecorded: 'Cette version de Claude Code ne note pas le détail des requêtes. Passez à la 2.1.300 ou plus récente.', + system: 'Prompt système', + tools: 'Outils', + block: 'Bloc {n}', + truncated: 'Coupé ici ; le bloc fait {chars} caractères.', + description: 'description {chars}', + omitted: '{n} de plus, comptés dans le total', + estimate: 'environ {tokens} tokens', + estimateHint: 'Caractères divisés par quatre : une estimation, pas le compte du fournisseur.', + chars: '{n} car.', + model: 'Modèle', + effort: 'Effort', + thinking: 'Réflexion', + maxTokens: 'Sortie max', + betas: 'Bêtas', + version: 'Claude Code', + sent: 'Envoyée', + } +}; diff --git a/packages/ui/src/lib/strings/fr/agents.ts b/packages/ui/src/lib/strings/fr/agents.ts new file mode 100644 index 000000000..f680340ed --- /dev/null +++ b/packages/ui/src/lib/strings/fr/agents.ts @@ -0,0 +1,111 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + agents: { + runtime: "Modèles et limites", + brain: "Cerveau", + routines: "Routines", + defaultModel: "Modèle par défaut", + allowedModels: "Modèles autorisés pour cet agent", + subagents: "Modèles des sous-agents", + enableSubagents: "Autoriser les sous-agents", + addModel: "Ajouter un modèle", + removeRoute: "Retirer ce modèle", + compactEvery: "Compacter après ce nombre de tours", + runMinutes: "Minutes maximum par exécution", + runtimeHint: 'Le modèle par défaut doit être autorisé. Retirer un accès arrête les travaux qui s\'en servent et les suspend pour vérification.', + brainHint: 'Instructions et skills survivent aux changements de modèle. Les contextes partagés gardent leur propre mémoire.', + compactNow: "Compacter ce contexte", + routineHint: "Vos agents s’en chargent seuls, même cette fenêtre fermée. Une tâche encore en cours n’est jamais relancée.", + runNow: "Exécuter maintenant", + schedule: "Planification", + once: "Une fois", + interval: "À intervalle régulier", + daily: "Chaque jour", + everyMinutes: "Toutes les N minutes", + timezone: "Fuseau horaire", + accountAccess: "Accès aux comptes", + accountAccessHint: 'Quels agents peuvent utiliser chaque compte, sous-agents compris. Retirer un accès arrête les exécutions qui s\'en servent.', + allAgents: "Tous les agents, actuels et futurs", + heading: 'Agents', intro: "Des agents avec leur rôle, leur mémoire et leurs missions. Les sous-agents d'une conversation apparaissent dans Sous-agents.", back: 'Retour aux conversations', + profiles: 'Agents', groups: 'Groupes', teams: 'Équipes', missions: 'Missions', attention: 'À examiner', + list: 'Tous les agents', create: 'Nouveau', edit: 'Modifier', + search: 'Rechercher', empty: 'Rien ici pour le moment.', select: 'Sélectionnez un élément pour voir son activité.', + name: 'Nom', domain: 'Rôle', instructions: 'Instructions', identityHint: 'Changer de modèle conserve son identité et sa mémoire.', + model: 'Modèle et compte', noAccount: 'Ajoutez un compte fournisseur dans les réglages avant de créer un agent.', permissions: 'Permissions', + ask: 'Demander avant les actions', edits: 'Autoriser les modifications', plan: 'Planifier uniquement', full: 'Accès complet', deny: 'Ne pas demander', + noApprovals: 'Ce fournisseur ne permet pas de valider les outils en direct. Ses permissions de lancement restent applicables. Les demandes de décision restent disponibles.', + tools: 'Outils de collaboration', messages: 'Messages', memory: 'Mémoire', artifacts: 'Résultats', decisions: 'Décisions', + members: 'Membres', responsibility: 'Responsabilité', description: 'Description', project: 'Projet', noProject: 'Sans projet', + team: 'Équipe', noTeam: 'Sans équipe', group: 'Groupe de discussion', noGroup: 'Aucun groupe associé', + mode: 'Qui répond', mentions: 'Destinataires choisis', round: 'Une réponse par membre', autonomous: 'Prendre la parole à tour de rôle', + maxTurns: 'Nombre maximum de tours', perAgent: 'Tours par agent', modeHint: 'Chaque nouveau message ouvre un budget. Ses réponses partagent ce budget. Les échanges automatiques cessent à la première limite atteinte.', + paused: 'En pause', pause: 'Suspendre', resume: 'Reprendre', archive: 'Archiver', active: 'Actif', archived: 'Archivé', + archiveTitle: 'Archiver "{name}" ?', archiveBody: 'L’archivage arrête le travail de cet agent et le retire de la liste active. Son profil et son historique restent accessibles par la recherche. Saisissez son nom actuel exactement pour confirmer.', archiveName: 'Nom de l’agent', + objective: 'Objectif', expectedResult: 'Résultat attendu', minutes: 'Minutes totales de travail', tokens: 'Limite de jetons, vide si aucune', + finishMission: 'Terminer la mission', reopenMission: 'Rouvrir la mission', + conversation: 'Discussion', activity: 'Activité', send: 'Envoyer', message: 'Écrire un message', recipients: 'Destinataires', + user: 'Vous', noWork: 'Aucune exécution pour le moment.', loadEarlier: 'Afficher les plus anciens', + openRun: 'Ouvrir cette exécution', requested: 'Demandé', executed: 'Exécuté', noUsage: 'Consommation non communiquée', + pending: 'En attente', running: 'Au travail', waiting: 'Attend une décision', done: 'Terminé', cancelled: 'Annulé', interrupted: 'Interrompu', error: 'Échec', review: 'À relire', open: 'Ouvert', assigned: 'Attribué', accepted: 'Accepté', + included: 'Inclus dans cette exécution', processed: 'Traité', failed: 'Échec', limited: 'Limite des échanges atteinte', + reconcile: 'Vérifier et reprendre', reconcileHint: 'Inspectez les fichiers et le travail effectué. Indiquez ce qui est terminé et ce qui peut continuer.', reconcileNote: 'Qu’avez-vous vérifié ?', + answer: 'Répondre', answerPlaceholder: 'Votre décision et vos instructions', + task: 'Tâche', tasks: 'Tâches', addTask: 'Ajouter une tâche', acquire: 'Attribuer', dependencies: 'Attendre ces tâches', approve: 'Accepter le résultat', revise: 'Demander des modifications', reject: 'Refuser', feedback: 'Retour pour la prochaine tentative', + result: 'Résultat', verification: 'Vérifications déclarées par cet agent', workspace: 'Espace de travail', copyPath: 'Copier le chemin', + resources: 'Ressources', addResource: 'Ajouter une ressource', directory: 'Dossier', url: 'URL', value: 'Valeur', read: 'Lecture', write: 'Écriture', + resourceHint: 'Ces accès guident le travail. Les permissions du fournisseur contrôlent les actions sur les fichiers.', + addMemory: 'Ajouter un souvenir', text: 'Texte', scope: 'Portée', sources: 'Sources', ownerMemory: 'Ajouté par vous', + searchKnowledge: 'Rechercher dans la mémoire et les ressources', expiresAt: 'Expiration facultative', expired: 'Expiré', expireMemory: 'Faire expirer', + engine: 'Travail en arrière-plan', concurrency: 'Agents simultanés', engineHint: 'Fermer cette page laisse le travail continuer sur le moteur connecté.', + idle: 'Disponible', offline: 'Chargement impossible. Reconnectez cette machine ou mettez son moteur à jour.', retry: 'Réessayer', + stale: 'Connexion perdue. Voici le dernier état reçu. Le travail peut continuer sur la machine hôte.', + history: 'Historique', + minutesUnit: 'minutes', status: 'État', settings: 'Réglages', overview: 'Vue d’ensemble', createAction: 'Créer', rolePlaceholder: 'Recherche, développement ou relecture', + newTitle: { profile: 'Nouvel agent', group: 'Nouveau groupe', team: 'Nouvelle équipe', mission: 'Nouvelle mission' }, + summary: '{running} au travail · {pending} en attente', + engineSettings: 'Réglages du moteur', engineSettingsHint: 'La quantité de travail en arrière-plan de ce moteur, et les comptes que ses agents peuvent utiliser.', + advanced: 'Plus d’options', identity: 'Identité', noMembers: 'Créez d’abord un agent.', + brainFiles: 'Fichiers du cerveau', procedures: 'Procédures', proceduresHint: 'AGENTS.md dans le dossier du cerveau. Les façons de travailler réutilisables.', notes: 'Notes', notesHint: 'MEMORY.md dans le dossier du cerveau. Ce que cet agent garde d’une conversation à l’autre.', + contexts: 'Contextes de conversation', memories: 'Souvenirs', toEveryone: 'Tout le monde', + missionMembers: 'Agents de cette mission', dangerZone: 'Arrêter le moteur', dangerHint: 'Arrête tous les agents de ce moteur et déconnecte ses clients.', + stopEngine: 'Arrêter ce moteur', stopEngineBody: 'Arrête les travaux et déconnecte les clients. Les exécutions commencées demanderont une vérification avant reprise. Rouvrez Boite sur la machine hôte pour redémarrer le moteur.', + createAgent: 'Créer un agent', noAgents: 'Aucun agent pour l’instant', noAgentsOn: 'Aucun agent sur {machine} pour l’instant', more: 'Plus', noMatch: 'Aucun résultat', + noMessagesYet: 'Aucun message pour l’instant', noMissions: 'Aucune mission pour l’instant', previewFrom: '{name} : {text}', unread: '{count} non lus', + backToChat: 'Retour à la discussion', backTo: 'Retour à {name}', editRoles: 'Modifier les rôles', startChat: 'Démarrer la discussion', + asks: '{name} vous demande', + planned: 'Tâches planifiées', plannedResult: 'Tâche planifiée', + entrustTo: 'Confier à un agent', entrustToName: 'Confier à {name}', entrustedTo: 'Confié à {name}', takeBack: 'Reprendre la main', + threadEvent: { entrusted: '{name} reprend « {title} »', done: 'Terminé · {title}', blocked: 'A besoin de toi · {title}', stopped: 'Arrêté · {title}' }, + openThread: 'Ouvrir le thread', deliveryFailed: '{name} ne l’a pas reçu', + messageTo: 'Écrire à {name}', sayHello: 'Dites bonjour à {name}, ou planifiez une tâche qu’il fera tout seul.', + liveWorking: '{name} travaille', liveWaiting: '{name} a besoin de vous', seeThread: 'Ouvrir son thread', + welcomeHint: 'Un agent garde son nom, sa mémoire et ses tâches. Parlez-lui, ou planifiez du travail qu’il fera seul.', + runsHeading: 'Où il tourne', runsHint: 'Ses conversations, sa mémoire et ses tâches planifiées restent sur cette machine. Les modèles viennent des comptes de cette machine.', + machine: 'Machine', modelLabel: 'Modèle', machineOffline: 'Hors ligne', machineNotOwner: 'Accès propriétaire requis', + machineUnreachable: '{name} n’est pas connectée. Reconnectez-la, puis créez l’agent à nouveau.', whatItDoesHint: 'Ses instructions permanentes. Vous pourrez les changer plus tard dans ses réglages.', + namePlaceholder: 'Mira, Scout, Pixel', whatItDoes: 'Ce qu’il fait', instructionsPlaceholder: 'Par exemple : chaque matin, lis les nouvelles issues et résume-les en cinq lignes.', + ownedBy: '{name} gère ce thread', ownedHint: 'Parlez à {name} dans sa discussion. Ce thread montre ce qu’il fait.', openAgent: 'Ouvrir {name}', + projects: 'Projets', projectsHint: 'Il suit leurs threads, en lance de nouveaux, répond à leurs questions et vous dit ce qui compte.', noProjects: 'Ouvrez d’abord un projet.', looksAfter: 'S’occupe de {names}', + seeWork: 'Voir son travail', aboutProjects: 'À propos de vos projets', memoryUpdated: 'Mémoire mise à jour', memoryUpdating: 'Mise à jour de la mémoire', + autoMemory: 'Mettre à jour la mémoire après chaque tâche', autoMemoryHint: 'Un petit assistant réécrit ses notes à partir de ce que vous avez demandé et de ce qu’il a répondu.', + brainFolder: 'Dossier des cerveaux', brainFolderHint: 'Où chaque agent garde ses instructions et ses notes sur cette machine. Le changer les déplace tous.', brainDefault: 'Par défaut, dans le dossier de données de Boite', + brainChange: 'Changer', brainUseDefault: 'Utiliser le dossier par défaut', brainMove: 'Déplacer les cerveaux ici', brainPath: 'Dossier sur cette machine', brainBrowse: 'Parcourir', brainParent: 'Dossier parent', brainUseFolder: 'Utiliser ce dossier', + robot: { + family: 'Style', shape: 'Forme', color: 'Couleur', eyes: 'Yeux', top: 'Accessoire', shuffle: 'Mélanger', customize: 'Personnaliser', done: 'Terminé', + option: '{part} {n}', families: { jelly: 'Gelée', bubble: 'Bulle', capsule: 'Capsule', retro: 'Rétro' }, + }, + when: { + who: 'Agent', pickAgent: 'Choisir un agent', what: 'Que doit-il faire ?', whatPlaceholder: 'Par exemple : résume les nouveaux messages et dis-moi lesquels attendent une réponse.', + when: 'Quand ?', modes: { once: 'Une fois', daily: 'Chaque jour', days: 'Certains jours', interval: 'Régulièrement' }, + inAnHour: 'Dans une heure', thisEvening: 'Ce soir, 18:00', tomorrowMorning: 'Demain, 09:00', dateAndTime: 'Date et heure', + otherInterval: 'Toutes les', minutesUnit: 'minutes', at: 'À', inThePast: 'Cette date est passée. Choisissez un moment à venir.', pickDays: 'Choisissez au moins un jour.', + next: 'Prochaine fois : {when}', name: 'Nom', schedule: 'Planifier', newRoutine: 'Planifier une tâche', scheduleThis: 'Planifier ce message comme une tâche', scheduleShort: 'Planifier', + everyMinutes: 'Toutes les {count} minutes', everyHour: 'Toutes les heures', everyHours: 'Toutes les {count} heures', everyDay: 'Tous les jours', everyDays: 'Tous les {count} jours', + minutesShort: '{count} min', hoursShort: '{count} h', + onceAt: 'Une fois, {date}', daily: 'Tous les jours à {time}', weekdays: 'Du lundi au vendredi à {time}', weekend: 'Le week-end à {time}', onDays: 'Chaque {days} à {time}', + today: 'aujourd’hui à {time}', tomorrow: 'demain à {time}', dateAt: '{date} à {time}', + lastRun: 'Dernière exécution {when} : {state}', stillRunning: 'Sa dernière exécution n’est pas terminée.', + }, + } +}; diff --git a/packages/ui/src/lib/strings/fr/app.ts b/packages/ui/src/lib/strings/fr/app.ts new file mode 100644 index 000000000..1cb935939 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/app.ts @@ -0,0 +1,16 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + app: { + name: 'Boite', + nightlyName: 'boite (de nuit)', + channelDev: 'Dev', + channelDevTitle: 'Le canal dev, sur son propre répertoire de données', + loading: 'Démarrage', + compacting: "Libération d'espace disque dans l'historique. Cela n'arrive qu'une fois et peut prendre quelques minutes.", + noEndpointTitle: 'Aucun cœur à joindre', + noEndpointBody: + "Sur l'une de vos machines, ouvrez Réglages, Machines, Ajouter un appareil, puis scannez ou tapez son code ici.", + openSettings: 'Ouvrir les réglages' + } +}; diff --git a/packages/ui/src/lib/strings/fr/appUpdate.ts b/packages/ui/src/lib/strings/fr/appUpdate.ts new file mode 100644 index 000000000..3456799f2 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/appUpdate.ts @@ -0,0 +1,36 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + appUpdate: { + released: 'Sortie {time}', + changelog: 'Changelog sur GitHub', + heading: "Mises à jour de l'application", + intro: 'Choisissez les versions suivies par cette application de bureau. Boite et Boite Nightly partagent cette installation, vos conversations et vos réglages.', + unsupported: 'Les mises à jour automatiques sont disponibles dans les versions installées : l’installateur Windows x64, l’app macOS et le .deb ou l’AppImage Linux.', + channel: 'Canal de publication', + stable: 'Boite', + nightly: 'Boite Nightly', + nightlyHint: "Boite Nightly est expérimentale et peut contenir des changements inachevés. Vous pouvez revenir à Boite même si son numéro de version est inférieur.", + installedVersion: 'Version installée', + idle: "Aucune recherche de mise à jour n'a encore eu lieu.", + checking: 'Recherche de mises à jour', + available: 'La version {version} est disponible.', + downloading: 'Téléchargement de {received} sur {total}', + downloaded: '{received} téléchargés', + ready: 'La version {version} est prête à installer.', + waiting: 'En attente de la fin du travail', + installing: "Les agents terminent leur étape, puis l'installation démarre", + current: 'Boite est à jour.', + failed: 'La mise à jour a échoué.', + lastChecked: 'Vérifié à {time}', + progress: "Téléchargement de la mise à jour de l'application", + notes: 'Notes de version', + retained: 'Changer de canal conserve vos conversations et vos réglages.', + check: 'Rechercher des mises à jour', + checkingAction: 'Recherche en cours', + install: 'Installer et redémarrer', + readyAction: 'Mise à jour', + installTitle: 'Installer la mise à jour de Boite ?', + installBody: "Les agents terminent leur appel d'outil en cours, 30 secondes au plus. Boite se met ensuite à jour, redémarre et les reprend.", + } +}; diff --git a/packages/ui/src/lib/strings/fr/artifacts.ts b/packages/ui/src/lib/strings/fr/artifacts.ts new file mode 100644 index 000000000..9903a70d6 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/artifacts.ts @@ -0,0 +1,5 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + artifacts: { viewReplay: 'Rejouer', viewExpand: 'Afficher en grand', viewCollapse: 'Fermer la vue agrandie', loadImage: 'Charger', mediaFailed: 'Impossible d’afficher ce média. Réessayez ou téléchargez le fichier pour l’ouvrir.', videoFailed: 'Cet appareil ne lit pas cette vidéo. Téléchargez-la pour la regarder dans une autre app.', saving: 'Enregistrement', zoomIn: 'Agrandir', zoomOut: 'Réduire', fitImage: 'Ajuster l’image', openLocal: 'Ouvrez ce fichier dans son application par défaut.', preview: 'Aperçu', download: 'Télécharger', close: 'Fermer l’aperçu', closeImage: 'Fermer l’image', enlarge: 'Voir en grand', saved: 'Enregistré dans Téléchargements', loading: 'Chargement du fichier', unavailable: 'Ce fichier ne peut pas être affiché ici. Téléchargez-le pour l’ouvrir.', failed: 'Impossible d’ouvrir ce fichier', ambiguous: (name: string, paths: string) => `${name} désigne plusieurs fichiers : ${paths}`, ownerOnly: 'Les liens vers les fichiers locaux nécessitent une connexion propriétaire. Demandez à l’agent de joindre le fichier pour le partager avec cet appareil.' } +}; diff --git a/packages/ui/src/lib/strings/fr/brain.ts b/packages/ui/src/lib/strings/fr/brain.ts new file mode 100644 index 000000000..402bbe4d0 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/brain.ts @@ -0,0 +1,25 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + brain: { + heading: 'Brain', description: 'Les mêmes instructions et skills pour tous vos agents.', + folder: 'Dossier du brain', pathHint: 'Chemin du dossier', save: 'Connecter le dossier', browse: 'Parcourir', useFolder: 'Choisir ce dossier', + enabled: 'Utiliser avec les agents', disconnect: 'Déconnecter', sync: 'Synchroniser', working: 'En cours', + empty: 'Connectez votre brain', emptyHint: 'Choisissez le dossier de vos instructions et skills.', + change: 'Changer de dossier', + autoPull: 'Pull automatique', onStartup: 'Au démarrage', periodic: 'À intervalle régulier', minutes: 'min', + globalInstructions: 'AGENTS.md global', globalDetails: 'Harness reliés', + globalHint: 'Ce brain s\'applique à tous les projets, même hors de Boite. Les fichiers existants sont sauvegardés puis restaurés à la désactivation.', + boiteGuide: 'Guide Boite', boiteGuideHint: 'Une note après AGENTS.md au début d\'une session d\'agent : la commande boite et le panneau. Environ 300 tokens.', + linked: 'Relié', existing: 'Déjà relié', blocked: 'À vérifier', + interval: 'Intervalle du pull en minutes', intervalHint: 'Un nombre entier entre 1 et 1440 minutes.', + detected: 'Contenu', instructions: 'Instructions', skill: 'Skills', plugin: 'Plugins', + pluginHint: 'Détectés ici. Installez les plugins depuis leur agent.', + details: 'Détails de la synchro', noGit: 'Dossier local. Connectez un dépôt Git pour synchroniser entre PC.', + noUpstream: 'Configurez une branche distante dans Git pour activer la synchro.', dirty: 'Enregistrez vos changements dans Git avant de synchroniser.', + counts: '{ahead} à envoyer, {behind} à recevoir', lastSync: 'Dernière synchro', never: 'Pas encore synchronisé', + upToDate: 'À jour', noEntries: 'Rien de détecté dans cette catégorie.', + missing: 'Mettez Boite à jour sur cette machine pour connecter un brain.', + } +}; diff --git a/packages/ui/src/lib/strings/fr/browser.ts b/packages/ui/src/lib/strings/fr/browser.ts new file mode 100644 index 000000000..1abb8eb59 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/browser.ts @@ -0,0 +1,23 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + browser: { + resetViewport: 'Adapter la page à la fenêtre', + enlarge: 'Agrandir le navigateur', + restore: 'Réduire le panneau', + detach: 'Afficher en popup dans l’application', + dock: 'Ramener dans le panneau', + move: 'Déplacer le panneau flottant', + loading: 'Chargement de la page', + failed: "La page n'a pas pu être ouverte. {reason}", + invalidUrl: 'Saisissez une adresse HTTP ou HTTPS valide, ou une recherche.', + resetZoom: 'Réinitialiser le zoom de la page', + back: 'Précédent', + forward: 'Suivant', + reload: 'Recharger', + urlPlaceholder: 'Rechercher ou saisir une URL', + openExternal: 'Ouvrir dans le navigateur du système', + slotEmpty: "La page s'affiche ici dès que la coque fournit la vue web", + slotLinux: "Le navigateur intégré n'est pas encore disponible dans l'app Linux. Ouvrez la page dans votre navigateur." + } +}; diff --git a/packages/ui/src/lib/strings/fr/browserProfiles.ts b/packages/ui/src/lib/strings/fr/browserProfiles.ts new file mode 100644 index 000000000..abcaa37c7 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/browserProfiles.ts @@ -0,0 +1,32 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + browserProfiles: { + card: 'Navigateur intégré', + heading: 'Profils du navigateur', + hint: "Chaque profil garde ses propres cookies, son stockage et ses connexions dans le navigateur intégré, y compris après un redémarrage. Un onglet privé ne garde rien une fois le dernier onglet privé fermé.", + default: 'Par défaut', + private: 'Privé', + profile: 'Profil du navigateur : {name}', + newTabIn: 'Nouvel onglet dans {name}', + newPrivateTab: 'Nouvel onglet privé', + manage: 'Gérer les profils du navigateur', + isDefault: 'Ouvre les nouveaux onglets', + makeDefault: 'Y ouvrir les nouveaux onglets', + renameLabel: 'Nouveau nom pour {name}', + addLabel: 'Nom du nouveau profil', + addPlaceholder: 'Pro, Perso…', + add: 'Ajouter un profil', + invalidName: "Choisissez un nom de 1 à 40 caractères qu'aucun autre profil n'utilise, autre que Default ou Private.", + tooMany: '{count} profils de navigateur au plus.', + deleteTitle: 'Supprimer le profil {name} ?', + deleteBody: 'Ses onglets ouverts se ferment, et ses cookies, son stockage et ses connexions sont effacés de cet ordinateur. Impossible de revenir en arrière.', + deleteFailed: "Le profil a été retiré, mais ses données n'ont pas pu être effacées : {reason}", + copySignIns: 'Copier les connexions vers les agents', + copySignInsHint: 'Envoyer les cookies de {name} au navigateur de l’agent d’une machine, dans le même profil', + copied: '{count} cookies de {name} copiés vers {machine}.', + copiedNone: '{name} n’a aucun cookie à copier.', + copyFailed: 'Les connexions n’ont pas pu être copiées : {reason}' + } +}; diff --git a/packages/ui/src/lib/strings/fr/browserTools.ts b/packages/ui/src/lib/strings/fr/browserTools.ts new file mode 100644 index 000000000..c96308646 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/browserTools.ts @@ -0,0 +1,16 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + browserTools: { + title: 'Outils de test du navigateur', desktop: 'Ordinateur', laptop: 'Portable', portrait: 'Portrait', landscape: 'Paysage', + system: 'Apparence du système', light: 'Page claire', dark: 'Page sombre', diagnostics: 'Diagnostics et actions de la page', + startRecording: 'Enregistrer cet onglet', stopRecording: 'Arrêter la vidéo', reviewRecording: 'Vidéo enregistrée', + clear: 'Effacer', pageEvents: 'Événements de la page', actions: 'Actions', noEvents: 'Aucun événement capturé.', noActions: 'Aucune action enregistrée pour le moment.', + omitted: '{count} événements anciens ou trop volumineux omis.', recorded: '{seconds} secondes · {fps} i/s · {mb} Mo · {format} sans son', + recordingError: 'Enregistrement arrêté après une erreur.', recordingLimit: 'Enregistrement arrêté de lui-même à la limite de {mb} Mo. La vidéo se lit jusqu’à ce point.', + frameRate: 'Enregistrer à {rate} images par seconde', codec: 'Encoder en {codec}', codecUnavailable: 'Cet ordinateur ne sait pas l’encoder', + unplayable: 'Cette vue ne peut pas lire la vidéo {codec}. Téléchargez-la pour la regarder dans un autre lecteur.', + download: 'Télécharger la vidéo', discard: 'Supprimer la vidéo', + } +}; diff --git a/packages/ui/src/lib/strings/fr/btw.ts b/packages/ui/src/lib/strings/fr/btw.ts new file mode 100644 index 000000000..6d86e5822 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/btw.ts @@ -0,0 +1,14 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + btw: { + fork: 'Fork', forking: 'Création du fork', + title: 'Question de côté', description: "Poser une question de côté sans interrompre l'agent", + loading: 'Réponse à votre question en cours', close: 'Fermer la réponse de côté', + needsThread: 'Ouvrez une conversation avant de poser une question de côté.', + questionRequired: 'Écrivez une question après /btw.', + textOnly: 'Les questions de côté acceptent seulement du texte. Retirez les pièces jointes et les références de prévisualisation.', + editing: 'Terminez ou annulez la modification du message avant de poser une question de côté.', + offline: 'Reconnectez cette machine avant de poser une question de côté.' + } +}; diff --git a/packages/ui/src/lib/strings/fr/changes.ts b/packages/ui/src/lib/strings/fr/changes.ts new file mode 100644 index 000000000..d7eba1909 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/changes.ts @@ -0,0 +1,33 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + changes: { + clean: 'Copie de travail propre', + noBranch: 'Aucune branche', + ahead: '{count} en avance', + behind: '{count} en retard', + count: '{count} modifiés', + oneCount: '1 modifié', + staged: 'Indexé', + stagedShort: 'I', + binary: 'Fichier binaire, rien à montrer ligne par ligne', + truncated: "Ce fichier a été tronqué : le diff ci-dessous n'est pas le fichier entier.", + pick: 'Choisissez un fichier pour voir son diff', + loading: 'Lecture de la copie de travail', + renamedFrom: 'Renommé depuis {path}', + backToList: 'Toutes les modifications', + previousFile: 'Fichier précédent', + nextFile: 'Fichier suivant', + position: '{index} sur {count}', + status: { + added: 'Ajouté', + modified: 'Modifié', + deleted: 'Supprimé', + renamed: 'Renommé', + copied: 'Copié', + untracked: 'Non suivi', + conflict: 'Conflit' + } + } +}; diff --git a/packages/ui/src/lib/strings/fr/chat.ts b/packages/ui/src/lib/strings/fr/chat.ts new file mode 100644 index 000000000..7780bf8c8 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/chat.ts @@ -0,0 +1,178 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + chat: { + accepted: 'Requête acceptée', + responseStarted: 'Activité reçue', + working: 'Travaille', + writing: 'Écrit', + stopped: 'Arrêté', + inputTokens: '{count} jetons en entrée', + outputTokens: '{count} jetons en sortie', + cacheTokens: '{read} lus depuis le cache, {write} écrits dedans', + workingFor: 'Travaille depuis {time}', + workedFor: 'A travaillé {time}', + compacting: 'Compaction en cours', + compactionElapsed: 'Temps écoulé : {time}', + turnOwnTime: 'Cette réponse seule : {time}', + compactedFor: 'Contexte compacté en {time}', + compactionFailed: 'Échec de la compaction', + compactionStopped: 'Compaction arrêtée', + progress: { starting: 'Démarrage de l\'agent', thinking: 'Réflexion en cours', compacting: 'Compaction du contexte', retrying: 'Nouvelle tentative', tool: 'Outil en cours', working: 'Activité reçue', waiting: 'En attente du fournisseur' }, + providerSignal: 'Signal du fournisseur il y a {time}', + noActivity: 'Aucune nouvelle activité depuis {time}', + newMessage: '1 nouveau message', + newMessages: '{count} nouveaux messages', + findPlaceholder: 'Chercher dans la conversation', + findNone: 'Aucun résultat', + findCount: '{at} sur {total}', + findPrevious: 'Résultat précédent (Maj+Entrée)', + findNext: 'Résultat suivant (Entrée)', + finishedAt: 'fini à {time}', + backgroundRunning: '{what} encore en cours', + forkOrigin: 'Dérivé de', forkReturn: 'Renvoyer les conclusions', forkSummary: "Conclusions pour la conversation d'origine", + forkReturned: "Conclusions envoyées à la conversation d'origine", forkSourceUnavailable: "Conversation d'origine indisponible", + forkNative: 'Contexte natif conservé', forkSeeded: 'Historique visible copié', + backgroundJoin: ', ', + backgroundOne: { shell: '{count} shell', agent: '{count} agent', monitor: '{count} moniteur', workflow: '{count} workflow', other: '{count} tâche' }, + backgroundMany: { shell: '{count} shells', agent: '{count} agents', monitor: '{count} moniteurs', workflow: '{count} workflows', other: '{count} tâches' }, + backgroundStop: 'Arrêter l’agent et ce qu’il exécute en arrière-plan', + backgroundChip: 'en arrière-plan', + toolLive: { command: 'Exécute {subject}', read: 'Lit {subject}', edit: 'Modifie {subject}', write: 'Écrit {subject}', search: 'Cherche {subject}', fetch: 'Récupère {subject}', web: 'Cherche sur le web {subject}', agent: 'Délègue {subject}', other: 'Utilise {subject}' }, + toolDone: { command: 'A exécuté {subject}', read: 'A lu {subject}', edit: 'A modifié {subject}', write: 'A écrit {subject}', search: 'A cherché {subject}', fetch: 'A récupéré {subject}', web: 'A cherché sur le web {subject}', agent: 'A délégué {subject}', other: 'A utilisé {subject}' }, + toolBare: { command: 'A exécuté une commande', read: 'A lu un fichier', edit: 'A modifié un fichier', write: 'A écrit un fichier', search: 'A cherché dans le code', fetch: 'A récupéré une page', web: 'A cherché sur le web', agent: 'A délégué une tâche', other: 'A utilisé un outil' }, + toolCommandWord: 'une commande', + toolRunOne: { command: '{count} commande exécutée', read: '{count} fichier lu', edit: '{count} fichier modifié', search: 'Une recherche dans le code', fetch: '{count} page récupérée', web: 'Une recherche sur le web', agent: '{count} tâche déléguée', other: '{count} outil utilisé', attempt: 'Tentative de {count} appel' }, + toolRunMany: { command: '{count} commandes exécutées', read: '{count} fichiers lus', edit: '{count} fichiers modifiés', search: '{count} recherches dans le code', fetch: '{count} pages récupérées', web: '{count} recherches sur le web', agent: '{count} tâches déléguées', other: '{count} outils utilisés', attempt: 'Tentative de {count} appels' }, + toolRunJoin: ', ', + toolRunLast: ' et ', + toolRunShow: 'Afficher chaque appel', + toolRunHide: 'Masquer les appels', + toolIssueOne: '{count} appel non abouti', + toolIssueMany: '{count} appels non aboutis', + system: 'Système', + toolInput: 'Entrée', + toolOutput: 'Sortie', + toolDocuments: 'Documents', + textOmitted: 'Ce message est trop long pour être affiché en entier. {count} caractères ne sont pas affichés.', + documentChip: { + diff: '{count} diff', + diffs: '{count} diffs', + doc: '{count} doc', + docs: '{count} docs' + }, + documentImage: "Ce que l'outil a produit", + imagePart: 'Image envoyée avec le message', + imageUnavailable: "Cette image n'a pas pu être chargée. Revenez dessus pour réessayer.", + diffHidden: '{count} lignes inchangées', + diffIgnoreWhitespace: 'Ignorer les changements d’espaces', + diffSideBySide: 'Ancien et nouveau côte à côte', + diffWhitespaceOnly: 'Seuls des espaces ont changé.', + diffShowAll: 'Afficher les {count} lignes', + diffAdded: '+{count}', + diffRemoved: '-{count}', + noOutput: 'Aucune sortie', + toolExitCode: 'Code de sortie {code}', + toolCommandFailed: 'Aucun diagnostic signalé. Consultez la sortie complète ci-dessous.', + showAll: 'Tout afficher', + toolStatus: { + running: 'en cours', + done: 'terminé', + error: 'erreur', + denied: 'refusé' + }, + thinking: 'Réflexion', + thinkingShow: 'Afficher le raisonnement', + thinkingHide: 'Masquer le raisonnement', + compaction: 'Contexte compacté, de {pre} à {post} jetons', + compactionNoPost: 'Contexte compacté depuis {pre} jetons', + compactionUnknown: 'Contexte compacté', + compactionManual: 'à la main', + moveExplained: "Déplacement expliqué à l'agent", + moveShow: "Voir ce qui a été dit à l'agent", + moveHide: "Masquer ce qui a été dit à l'agent", + movePending: "Déplacé vers {project} · l'agent sera prévenu à votre prochain message", + movePendingShow: "Voir ce qui sera dit à l'agent", + movePendingHide: "Masquer ce qui sera dit à l'agent", + movedByAgent: "Déplacé par l'agent vers {project}", + keepalive: 'Cache de prompt gardé au chaud', + keepaliveMany: 'Cache de prompt gardé au chaud {count} fois', + keepaliveRunning: 'Cache de prompt en cours de relance', + startedByAgent: "Lancé par l'agent de {title} dans {project}", + promptFromAgent: "Envoyé par l'agent de", + agentStartedThread: "L'agent a lancé {title} dans {project}", + openLinkedThread: 'Ouvrir ce thread', + hookBlocked: 'Un hook a bloqué ce message', + hookStopped: 'Un hook a arrêté le tour', + hookSays: '{label} : {message}', + permissionAsk: { + command: 'Lancer une commande', + edit: 'Modifier {file}', + editFiles: 'Modifier des fichiers', + editFolder: 'Écrire dans {folder} à partir de maintenant', + write: 'Créer ou remplacer {file}', + read: 'Lire {file}', + search: 'Chercher {subject} dans les fichiers', + fetch: 'Ouvrir {subject}', + web: 'Chercher {subject} sur le web', + agent: 'Lancer un agent auxiliaire', + other: 'Utiliser son outil {tool}' + }, + permissionTechnical: 'Détails techniques', + turnFiles: 'Fichiers modifiés', + showTurnDiff: 'Voir le diff', + hideTurnDiff: 'Masquer le diff', + allChanges: 'Tous les changements du dossier', + expandFileFolders: 'Déplier tous les dossiers', + collapseFileFolders: 'Replier tous les dossiers', + fileCreated: 'Nouveau', + fileChanged: 'Modifié', + fileDeleted: 'Supprimé', + openFile: 'Ouvrir {name}', + revealFile: 'Afficher dans le dossier', + revealFailed: "Le dossier n'a pas pu s'ouvrir : {reason}", + allow: 'Autoriser', + deny: 'Refuser', + allowed: 'Autorisé', + denied: 'Refusé', + questionHeading: 'Vous demande', + questionAsyncHeading: 'Question', + questionAsyncHint: 'L\'agent continue. Sans réponse, il poursuit avec un choix par défaut.', + questionAnswer: 'Répondre', + questionAnswered: 'Répondu', + questionCancelled: 'Cette question n’attend plus de réponse', + questionSkip: 'Passer', + questionReplying: 'Écrivez votre réponse dans le champ de message en bas, avec photos ou fichiers si besoin.', + questionWrite: 'Répondre par écrit', + questionDocked: 'Attend votre réponse au-dessus du champ de message', + questionOpen: 'Y répondre', + streaming: 'écrit', + jumpToLatest: 'Aller au dernier message', + loadingOlder: 'Chargement des messages précédents', + loadingNewer: 'Chargement des messages suivants', + opening: 'Chargement de la conversation', + openingProgress: '{received} / {total}', + partLoading: 'Chargement de {size}', + outline: 'Messages de cette conversation', + messageGroup: 'Parcourir {count} messages', + earlierMessages: 'Charger les messages précédents', + outlineEarlier: 'Afficher les messages précédents dans le rail', + outlineLater: 'Afficher les messages suivants dans le rail', + goToMessage: 'Aller au message {number} : {text}', + copied: 'Copié', + plan: 'Plan', + planDownload: 'Télécharger le .md', + planSave: 'Enregistrer dans le dossier', + planSaveHint: 'Écrire le plan dans un fichier markdown à la racine du dossier de ce thread', + planSaved: 'Enregistré sous {name}', + copyMessage: 'Copier le message', + editMessage: 'Modifier et renvoyer depuis ici', + retry: 'Relancer cette réponse', + fork: 'Bifurquer depuis ici', + forkHere: 'Bifurquer dans une nouvelle conversation', + forkWorktree: 'Bifurquer dans un nouveau worktree', + sentAt: 'Envoyé {time}', + error: 'Erreur' + } +}; diff --git a/packages/ui/src/lib/strings/fr/common.ts b/packages/ui/src/lib/strings/fr/common.ts new file mode 100644 index 000000000..47e059a63 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/common.ts @@ -0,0 +1,22 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + common: { + none: 'aucun', + unknown: 'inconnu', + yes: 'oui', + no: 'non', + close: 'Fermer', + moreInfo: 'À propos : {topic}', + refresh: 'Actualiser', + dismiss: 'Ignorer', + cancel: 'Annuler', + retry: 'Réessayer', + save: 'Enregistrer', + rename: 'Renommer', + delete: 'Supprimer', + copy: 'Copier', + open: 'Ouvrir', + } +}; diff --git a/packages/ui/src/lib/strings/fr/composer.ts b/packages/ui/src/lib/strings/fr/composer.ts new file mode 100644 index 000000000..d6402b980 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/composer.ts @@ -0,0 +1,99 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + composer: { + editing: 'Modification d’un message envoyé : l’envoi le remplace, avec tout ce qui suit', + editingQueued: 'Envoyez ou retirez les messages en attente avant de remplacer un message envoyé.', + filesUnavailable: "Les messages ont été rembobinés, mais cette modification n'a pas de sauvegarde complète des fichiers. Le code n'a pas pu être restauré.", + editingCancel: 'Arrêter la modification (Échap)', + replying: 'Réponse à : {question}', + replyPlaceholder: 'Écrivez votre réponse', + replySend: 'Envoyer la réponse', + replyIgnore: 'Écrire un message', + replyIgnoreHint: 'Laisser la question en attente et écrire un message normal', + switchTitle: 'Passer une conversation de {tokens} jetons à {provider} ?', + switchBody: "{provider} démarre une nouvelle session et reçoit des extraits du début et des échanges les plus récents, environ 20 000 jetons. Le reste lui est inaccessible. Compacter avant ne change rien.", + switchConfirm: "Changer d'agent", + switchCancel: 'Rester avec {provider}', + cacheTitle: "Reconstruire le cache d'une conversation de {tokens} jetons ?", + cacheBody: "Ce changement peut faire retraiter toute la conversation hors cache au prochain tour, ce qui consomme plus de quota. Changer de modèle ou d'effort de raisonnement peut affecter le cache, tout comme activer le mode Fast de Claude pour la première fois.", + cacheConfirm: 'Changer', + cacheCancel: 'Garder le réglage', + options: 'Options du message', + placeholder: 'Message à {provider} dans {project}', + placeholderNoProject: "Message à l'agent", + placeholderNew: 'Que voulez-vous faire ?', + placeholderOffline: '{machine} est hors ligne : les messages attendent ici et partent à sa reconnexion', + send: 'Envoyer', + stop: 'Arrêter', + queued: 'Envoyé après le prochain outil, ou à la fin du tour', + queuedPaused: 'Retenu après un envoi refusé', + sendNow: 'Envoyer maintenant', + queueWaitForEnd: 'Les objectifs et les boucles attendent la fin du tour en cours. Votre commande reste en attente.', + queueTurnNotReady: "Ce client n'a pas encore reçu le tour en cours. Votre message reste en attente.", + queueNotAccepted: "Ce tour n'a pas accepté le message. Il reste en attente et sera envoyé à la fin du tour.", + sendNowHint: "Transmet les messages à l'agent en cours sans l'arrêter. Entrée dans le champ vide fait de même.", + retryQueuedHint: 'Renvoie tous les messages en attente ensemble. Entrée dans le champ vide fait de même.', + outboxKept: "Gardés sur cet appareil, envoyés dans l'ordre, une seule fois chacun", + outboxWaiting: 'En attente de {machine}', + outboxNext: 'Part dès que le fil est libre', + outboxSending: 'Envoi…', + outboxFailed: 'Non envoyé : {reason}', + outboxRetry: 'Renvoyer', + outboxSettings: "Le modèle et l'effort choisis à l'écriture n'ont pas pu être rétablis.", + removeQueued: 'Retirer ce message en attente', + picker: 'Fournisseur et modèle', + models: 'Modèles', + searchModels: 'Rechercher un modèle', + noModels: 'Aucun modèle ne correspond', + noModelsAvailable: "L'agent n'a renvoyé aucun modèle. Actualisez pour réessayer.", + showAllModels: 'Afficher les {count} modèles', + legacyModels: 'Anciens modèles', + refreshModels: 'Actualiser les modèles', + probing: "Lecture des modèles de l'agent", + notInstalled: 'Pas installé sur cette machine', + installInSettings: 'Installer dans les réglages', + effortTitle: 'Niveau de raisonnement', + speed: 'Vitesse de réponse', + effortDefault: 'Par défaut', + effortDefaultHint: "Laisser l'agent choisir son propre niveau pour ce modèle", + standardSpeed: 'Vitesse normale', + favorites: 'Favoris', + favorite: 'Ajouter aux favoris', + unfavorite: 'Retirer des favoris', + favoritesEmpty: 'Mettez une étoile à un modèle pour le retrouver ici.', + favoriteUnavailable: "Ce favori n'est plus proposé par le compte. Choisissez un autre modèle.", + compact: 'Compacter le contexte', + compactBusy: 'Attendez la fin du tour en cours', + compactUnavailable: "Cet agent n'a pas annoncé de compactage du contexte", + compactNoSession: 'Envoyez un message avant de compacter', + newBadge: 'nouveau', + lockedHint: 'Une conversation garde son fournisseur et son compte', + mode: 'Permissions', + model: 'Modèle', + unavailable: 'pas installé', + noAccount: 'aucun compte', + noProvider: 'Aucune IA connectée', + worktree: 'Worktree', + worktreeOff: 'Démarrer dans un worktree git sur sa propre branche', + worktreeOn: 'Démarre dans un worktree git sur sa propre branche', + attach: 'Joindre des fichiers', + editQueued: 'Modifier ce message en attente, ou appuyez sur Haut dans un champ vide', + attachReadError: '{name} est illisible.', + attachRemove: 'Retirer {name}', + attachAlt: 'Image jointe', + imageReference: '[Image {number}]', + imagePreview: 'Aperçu de {image}', + imagePreviewClose: "Fermer l'aperçu de l'image", + attachPending: 'Recharger pour récupérer', + attachMore: 'Afficher {count} de plus', + attachLess: 'Afficher moins', + attachUnnamed: "l'image collée", + attachFormat: '{name} est en {type}, or une image doit être de type {formats}.', + attachTooLarge: '{name} est trop lourde : une image pèse {max} au maximum.', + attachTooMany: 'Un tour porte {max} images au maximum, donc {name} a été laissée de côté.', + attachTotalTooLarge: "Les fichiers d'un tour pèsent {max} au maximum à eux tous, donc {name} a été laissé de côté.", + attachNoImages: "{provider} n'accepte pas les images : envoyez le message sans elles." + } +}; diff --git a/packages/ui/src/lib/strings/fr/connect.ts b/packages/ui/src/lib/strings/fr/connect.ts new file mode 100644 index 000000000..12bcb9c70 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/connect.ts @@ -0,0 +1,30 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + connect: { + button: 'Connecter une IA', + title: 'Connecter une IA', + intro: "Boite fonctionne avec un abonnement IA que vous avez déjà. Choisissez-le, Boite installe ce qu'il faut et ouvre sa page de connexion.", + more: 'Autres agents', + plan: { + claude: 'Avec un forfait Claude Pro ou Max', + codex: 'Avec un forfait ChatGPT' + }, + otherPlan: 'Avec votre compte {provider}', + install: 'Installer {provider}', + installNote: "Télécharge {size}. Rien d'autre ne change sur cet ordinateur.", + signIn: 'Se connecter à {provider}', + signInNote: 'Votre navigateur ouvre la page de connexion. Revenez ici une fois connecté.', + manual: "{provider} s'installe avec son propre installateur. Lancez-le, puis vérifiez à nouveau ici.", + manualOpen: "Ouvrir la page d'installation", + external: '{provider} se connecte depuis sa propre fenêtre. Connectez-vous là-bas, puis vérifiez à nouveau.', + checkAgain: 'Vérifier à nouveau', + ready: '{provider} est connecté.', + use: 'Utiliser {provider}', + back: 'Retour', + device: "Aucune IA n'est encore connectée sur l'ordinateur qui fait tourner Boite. Connectez-en une depuis cet ordinateur.", + reconnect: 'Se reconnecter', + signedOut: "{provider} n'est plus connecté" + } +}; diff --git a/packages/ui/src/lib/strings/fr/connection.ts b/packages/ui/src/lib/strings/fr/connection.ts new file mode 100644 index 000000000..1e9b76e32 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/connection.ts @@ -0,0 +1,25 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + connection: { + machine: 'Machine', + local: 'Ce PC', + current: 'Machine actuelle', + machines: 'machines connectées', + issues: 'à vérifier', + manage: 'Gérer les machines', + folder: 'Dossier', + browse: 'Parcourir les dossiers', + nativeBrowse: "Choisir dans l'explorateur Windows", + parent: 'Dossier parent', + emptyFolder: 'Aucun sous-dossier', + unavailable: 'Connexion impossible', + idle: 'Non connecté', + connecting: 'Connexion', + ready: 'Connecté', + closed: 'Déconnecté', + offline: 'Hors ligne', + label: 'Connexion' + } +}; diff --git a/packages/ui/src/lib/strings/fr/contextMode.ts b/packages/ui/src/lib/strings/fr/contextMode.ts new file mode 100644 index 000000000..46d46dd8f --- /dev/null +++ b/packages/ui/src/lib/strings/fr/contextMode.ts @@ -0,0 +1,18 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + contextMode: { + light: 'Léger', + neutral: 'Neutre', + custom: 'Sur mesure', + rules: 'règles', + heading: 'Contexte {label}', + without: 'Démarré sans : {parts}.', + instructions: "fichiers d'instructions", + guides: 'guides Boite et brain', + mcp: 'serveurs MCP', + hooks: 'hooks', + tools: 'Outils : {tools}.', + withRules: 'Ses règles : {rules}' + } +}; diff --git a/packages/ui/src/lib/strings/fr/controls.ts b/packages/ui/src/lib/strings/fr/controls.ts new file mode 100644 index 000000000..ec0d7c696 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/controls.ts @@ -0,0 +1,22 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + controls: { + heading: 'Boutons', + headingHint: 'Un bouton masqué reste accessible depuis la palette de commandes et les raccourcis clavier.', + preset: 'Préréglage', + everyday: "L'essentiel", + developer: 'Tout', + header: 'En-tête de la conversation', + composer: 'Barre de saisie', + sidebar: 'Barre latérale', + panel: 'Menu du panneau latéral', + project: 'Nom du projet', + branch: 'Branche', + context: 'Jauge de contexte', + hide: 'Masquer ce bouton', + customize: 'Choisir les boutons', + allHidden: 'Toutes les cartes sont masquées sur cet appareil.' + } +}; diff --git a/packages/ui/src/lib/strings/fr/coordination.ts b/packages/ui/src/lib/strings/fr/coordination.ts new file mode 100644 index 000000000..ddc851285 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/coordination.ts @@ -0,0 +1,26 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + coordination: { + options: 'Réglages avancés', + heading: 'Communication entre conversations', off: 'Désactivée', on: 'Activée', + summaryOff: 'Les autres agents ne peuvent ni trouver, ni lire, ni contacter cette conversation.', + summaryOn: 'Les agents de cette machine et des machines liées peuvent trouver cette conversation, la lire et lui écrire.', + ownerOnly: 'Seul le propriétaire peut modifier la coordination. Cet appareil peut consulter les contacts et les échanges.', + resources: 'Ressources et responsabilités', resourcesPlaceholder: "Ce que cet agent gère, peut expliquer ou doit éviter", + remote: 'Entre projets et machines', remoteHint: 'Permettre aux agents des autres projets et des machines liées de trouver cette conversation, de la lire et de lui écrire. Les deux conversations doivent activer ce choix.', + pause: 'Suspendre la coordination', resume: 'Reprendre la coordination', paused: 'Suspendue', + sends: '{used} envois sur {limit} cette heure', wakes: '{used} réveils sur {limit} cette heure', receives: "Les destinataires acceptent jusqu'à {limit} messages par heure.", + sent: '{used} envoyés cette heure', woken: '{used} réveils cette heure', + directory: 'Contacts autorisés', directoryEmpty: "Aucune conversation volontaire n'est disponible dans ce projet.", unavailable: 'Contacts indisponibles', + details: "Afficher l'échange", + machineNotConnected: 'Connectez cette machine dans les réglages pour ouvrir son fil.', + status: { + queued: "En attente d'envoi", received: 'Reçu par le cœur destinataire', delivered: "Transmis à l'agent destinataire", + uncertain: 'Transmission incertaine. Boite ne la réessaiera pas.', expired: 'Expiré', rejected: 'Refusé' + }, + bubbleStatus: { + queued: 'En attente', received: 'En attente', delivered: 'Envoyé', uncertain: 'Non confirmé', expired: 'Expiré', rejected: 'Échec' + } + } +}; diff --git a/packages/ui/src/lib/strings/fr/delegation.ts b/packages/ui/src/lib/strings/fr/delegation.ts new file mode 100644 index 000000000..246d03776 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/delegation.ts @@ -0,0 +1,51 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + delegation: { + off: 'Désactivés', + paused: 'En pause', + heading: 'Sous-agents', + nativeHeading: 'Lancés par le fournisseur', + nativeNoResult: 'Aucun résultat reçu.', + processHeading: 'Lancés depuis une commande', + processResult: 'La progression et le résultat sont dans la sortie de la commande.', + nativeStatus: { running: 'En cours', done: 'Terminé', error: 'Échec', stopped: 'Arrêté', unknown: 'État inconnu' }, + parent: 'Fil principal', + activeOne: '1 sous-agent actif', + activeMany: '{count} sous-agents actifs', + configure: 'Réglages', + enabled: 'Autoriser cette conversation à lancer des sous-agents', + enabledHint: "L'agent peut confier des parties d'une tâche à d'autres agents. Chacun travaille dans sa propre conversation et rend compte ici.", + anyModel: "Laisser l'agent choisir le modèle", + anyModelHint: "Pour chaque tâche, l'agent choisit un modèle et son niveau de réflexion, par exemple Codex pour une revue de code. Désactivé, les sous-agents utilisent le modèle de cette conversation ou les modèles ci-dessous.", + profiles: 'Modèles suggérés', + profilesHint: "L'agent les voit par leur nom. Ajoutez-en un pour suggérer un modèle et un niveau de réflexion pour un type de tâche.", + empty: "Aucun sous-agent pour l'instant. Demandez à l'agent de répartir le travail : chaque sous-agent apparaît ici et sa conversation s'ouvre en un clic.", + addProfile: 'Ajouter un modèle', + profileName: 'Modèle {count}', + profileLabel: 'Modèle du sous-agent', + removeProfile: 'Retirer ce modèle', + pause: 'Mettre les sous-agents en pause', + resume: 'Reprendre les sous-agents', + backToTeam: 'Retour aux sous-agents', + stopAll: 'Tout arrêter', + send: 'Envoyer au sous-agent', + startedOne: '1 sous-agent lancé', + startedMany: '{count} sous-agents lancés', + completed: '{done}/{total} terminés', + failed: '{count} en erreur', + stopped: '{count} arrêtés', + elapsed: 'Temps écoulé', + showTeam: 'Voir les sous-agents', + stoppedStatus: 'Arrêté', + failedStatus: 'Échec', + waitingStatus: 'En attente', + interruptedStatus: 'Interrompu', + interrupted: '{count} interrompus', + resumeAgent: 'Reprendre', + resumeAgentHint: 'Demander à ce sous-agent de vérifier comment son travail en arrière-plan s’est terminé et de finir sa tâche', + resumeAgentText: 'Ton travail en arrière-plan ({what}) s’est arrêté sans te réveiller : {reason}. Vérifie comment il s’est terminé, puis finis ta tâche.', + activeSection: 'En cours', + finishedSection: 'Terminés' + } +}; diff --git a/packages/ui/src/lib/strings/fr/desktopUpdate.ts b/packages/ui/src/lib/strings/fr/desktopUpdate.ts new file mode 100644 index 000000000..83bc1a9c3 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/desktopUpdate.ts @@ -0,0 +1,15 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + desktopUpdate: { + heading: 'Application de bureau', + progress: 'Téléchargement de la mise à jour de l’application', + ready: 'Téléchargée, prête à installer', + installing: 'Installation, Boite redémarre là-bas', + current: 'À jour', + elsewhere: 'Installez cette mise à jour sur la machine elle-même : son installateur peut demander une confirmation.', + confirmTitle: 'Mettre à jour Boite sur {machine} ?', + confirmBody: 'La version {version} s’installe quand les agents de cette machine ont fini. Boite redémarre sur {machine} et le travail en cours reprend.', + requested: 'Une autre machine installe Boite {version} ici. Boite redémarre dans un instant.' + } +}; diff --git a/packages/ui/src/lib/strings/fr/devicePanel.ts b/packages/ui/src/lib/strings/fr/devicePanel.ts new file mode 100644 index 000000000..d3ff93886 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/devicePanel.ts @@ -0,0 +1,19 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + devicePanel: { + cover: 'Cet agent contrôle un appareil sur {machine}', coverHint: 'Rien n’est diffusé vers cet appareil avant que vous l’affichiez.', + show: 'Afficher', hide: 'Masquer', hideHint: 'Arrêter de regarder et recouvrir la vue', + empty: "Aucun appareil n'est ouvert dans cette conversation. Un agent en ouvre un avec boite device open, ou ouvrez-en un ici.", + onMachine: 'Sur cette machine', add: 'Ouvrir un appareil', + none: 'Aucun simulateur ni émulateur trouvé sur cette machine.', + starting: 'Démarrage de {name}. Cela peut prendre une minute.', failed: "{name} n'a pas démarré", + stopped: 'Éteint', running: 'Allumé', booting: 'Démarrage', + viewOnly: "Lecture seule. L'agent pilote les simulateurs iOS avec xcrun simctl.", + back: 'Retour', home: 'Accueil', recents: 'Applications récentes', rotate: 'Pivoter', power: "Bouton d'alimentation", enter: 'Entrée', backspace: 'Effacer le caractère précédent', + text: 'Écrire dans le champ actif', send: 'Envoyer le texte', ascii: "Seuls les caractères ASCII imprimables atteignent l'appareil.", + screenshot: "Enregistrer une capture d'écran", close: 'Fermer {name}', shutdown: 'Éteindre', + interact: "Touchez ou glissez sur l'écran de l'appareil", interactDesk: "L'écran de l'appareil : cliquez dessus, puis écrivez", + deskHint: "Cliquez sur l'écran, puis écrivez. Glissez pour balayer, défilez à la molette, Échap revient en arrière. Coller écrit le presse-papiers ; copier depuis l'appareil n'est pas disponible.", image: 'Vue en direct de {name}', gesture: 'Touchez pour appuyer. Glissez pour balayer.' + } +}; diff --git a/packages/ui/src/lib/strings/fr/diagnostics.ts b/packages/ui/src/lib/strings/fr/diagnostics.ts new file mode 100644 index 000000000..36d39bda8 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/diagnostics.ts @@ -0,0 +1,74 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + diagnostics: { + heading: 'Diagnostic', + intro: "Ce que cette machine a noté de ses propres problèmes. Un export ou un ticket est anonymisé : chemins, noms et adresses deviennent des marqueurs, les identifiants de conversation restent.", + status: 'État', + loading: 'Lecture des journaux', + version: 'Boite', + system: 'Système', + uptime: 'Cœur lancé depuis', + lastDay: 'Dernières 24 heures', + levels: { debug: 'débogage', info: 'info', warn: 'avertissements', error: 'erreurs' }, + files: 'Fichiers de journal', + noFiles: 'Aucun fichier de journal pour le moment', + problems: 'Problèmes', + problemsHint: 'Avertissements et erreurs des dernières 24 heures, regroupés par type, erreurs en premier.', + problemsEmpty: 'Aucun avertissement ni erreur ces dernières 24 heures.', + times: '{count}×', + lastSeen: 'dernier {time}', + latest: 'Dernier message', + threads: 'Conversations touchées', + missingThread: 'absente de cette machine', + log: 'Journal', + level: 'Niveau', + atLeast: { debug: 'Tout', info: 'Info et plus', warn: 'Avertissements et erreurs', error: 'Erreurs seulement' }, + origin: 'Source', + origins: { all: 'Toutes les sources', core: 'Cœur', shell: 'Application de bureau', ui: 'Interface' }, + thread: 'Conversation', + allThreads: 'Toutes les conversations', + search: 'Chercher dans le journal', + period: 'Période', + periods: { '15m': '15 dernières minutes', '1h': 'Dernière heure', '24h': 'Dernières 24 heures', '7d': '7 derniers jours' }, + loadOlder: 'Charger plus ancien', + noOlder: 'Rien de plus ancien sur cette période.', + empty: 'Aucune entrée ne correspond à ces filtres.', + live: 'Les nouvelles entrées apparaissent tant que cette page est ouverte.', + duration: 'Durée', + details: 'Détails', + ids: 'Identifiants', + agent: 'Agent', + parent: 'Lancée par', + turn: 'Tour', + request: 'Requête', + run: 'Exécution', + export: 'Exporter les journaux anonymisés', + exportHint: "Les dernières 24 heures dans un fichier texte lisible par un développeur : environnement, problèmes regroupés, conversations et chronologie.", + exportDone: '{name} enregistré ({size}).', + exportDownloaded: '{name} téléchargé ({size}).', + report: 'Signaler un problème', + reportHint: "Prépare un ticket GitHub pour Boite avec votre description et, si vous l'acceptez, les journaux anonymisés.", + title: 'Titre', + titlePlaceholder: "Ce qui n'a pas marché, en quelques mots", + description: "Ce qui s'est passé", + descriptionPlaceholder: "Ce que vous avez fait, ce que vous attendiez et ce que vous avez vu", + includeLogs: 'Joindre les journaux anonymisés', + preview: 'Aperçu', + previewHint: 'Voici le texte que contiendra le ticket.', + previewUpdate: "Mettre à jour l'aperçu", + submit: 'Créer le ticket', + openGithub: 'Ouvrir sur GitHub', + ghMissing: "GitHub CLI n'est pas configuré sur cette machine : le ticket s'ouvre dans votre navigateur. Glissez-y {file} pour joindre les journaux complets.", + ghMissingNoFile: "GitHub CLI n'est pas configuré sur cette machine : le ticket s'ouvre dans votre navigateur.", + opened: "Le ticket s'est ouvert dans votre navigateur.", + created: 'Ticket créé :', + cancel: 'Annuler', + close: 'Fermer', + agentAccess: 'Les agents peuvent lire les journaux anonymisés', + agentAccessHint: "Un agent peut vérifier si Boite elle-même a échoué et rédiger un ticket. Il ne lit que la vue anonymisée, jamais les fichiers sur le disque.", + failed: "Le diagnostic n'a pas pu se charger : {reason}", + outdated: "Boite sur {machine} est antérieur à ce diagnostic : son journal s'affiche plus bas, mais l'état, l'export et le signalement demandent une mise à jour. Mettez-le à jour dans Réglages, Machines et mises à jour.", + outdatedHere: "Boite sur cette machine est antérieur à ce diagnostic : son journal s'affiche plus bas, mais l'état, l'export et le signalement demandent une mise à jour. Mettez-la à jour dans Réglages, Machines et mises à jour." + } +}; diff --git a/packages/ui/src/lib/strings/fr/drafts.ts b/packages/ui/src/lib/strings/fr/drafts.ts new file mode 100644 index 000000000..6f4821e6c --- /dev/null +++ b/packages/ui/src/lib/strings/fr/drafts.ts @@ -0,0 +1,15 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + drafts: { + name: 'Brouillons', + hint: 'Un dossier par conversation, dans Documents/Boite', + openFolder: 'Travailler dans un de mes dossiers', + pickFolder: 'Ouvrir un dossier', + incognito: 'Incognito', + incognitoStart: "Démarrer cette conversation en incognito : rien n'en reste une fois que vous la quittez", + incognitoStop: 'Démarrer plutôt une conversation normale', + incognitoOn: 'Incognito : cette conversation et son dossier sont effacés quand vous la quittez' + } +}; diff --git a/packages/ui/src/lib/strings/fr/drop.ts b/packages/ui/src/lib/strings/fr/drop.ts new file mode 100644 index 000000000..01c810f9c --- /dev/null +++ b/packages/ui/src/lib/strings/fr/drop.ts @@ -0,0 +1,12 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + drop: { + title: 'Déposez pour ajouter un projet', + body: 'Chaque dossier devient un projet. Les fichiers vont dans le message.', + attachTitle: 'Déposez pour joindre', + attachBody: 'Les fichiers vont dans le message en cours.', + noComposer: 'Ouvrez une conversation pour joindre les fichiers déposés.' + } +}; diff --git a/packages/ui/src/lib/strings/fr/effortLevels.ts b/packages/ui/src/lib/strings/fr/effortLevels.ts new file mode 100644 index 000000000..a25a23bac --- /dev/null +++ b/packages/ui/src/lib/strings/fr/effortLevels.ts @@ -0,0 +1,18 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + effortLevels: { + none: 'Aucun', + off: 'Désactivé', + minimal: 'Minimal', + low: 'Faible', + medium: 'Moyen', + high: 'Élevé', + xhigh: 'Xhigh', + max: 'Max', + ultra: 'Ultra', + ultrathink: 'Ultrathink', + fast: 'Rapide' + } +}; diff --git a/packages/ui/src/lib/strings/fr/errors.ts b/packages/ui/src/lib/strings/fr/errors.ts new file mode 100644 index 000000000..3e1c8935c --- /dev/null +++ b/packages/ui/src/lib/strings/fr/errors.ts @@ -0,0 +1,23 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + errors: { + draftStorage: "Cet appareil n'a pas pu enregistrer votre brouillon. Gardez cette fenêtre ouverte et copiez votre texte avant de la fermer.", + draftAttachment: "Une pièce jointe n'a pas pu être lue depuis le stockage. Rechargez pour la récupérer, ou retirez-la avant l'envoi.", + prefix: 'Erreur', + activityUnsupported: "Ce cœur ne gère pas les objectifs ni les boucles. Mettez Boite à jour sur {machine}, puis reconnectez-vous. Votre commande n'a pas été envoyée.", + pullRequestUnsupported: "Mettez Boite à jour sur la machine qui héberge cette conversation pour afficher sa pull request. Ce cœur ne sait pas encore les chercher.", + noEndpoint: "Aucun point de connexion n'a pu être résolu.", + coreStart: "Le cœur de cet ordinateur n'a pas démarré : {reason}", + connect: 'Connexion au cœur impossible.', + clipboard: 'Le presse-papiers a refusé le texte.', + revoked: "Cet appareil a été retiré du groupe. Rajoutez-le avec un nouveau code depuis l'une de vos machines.", + unpaired: "Cet appareil n'est pas encore dans le groupe de cette machine. Ajoutez-le avec le code d'Ajouter un appareil.", + olderCore: "Boite sur {machine} est trop ancien pour cette action. Mettez-le à jour dans Réglages, Machines et mises à jour, puis réessayez.", + olderCoreHere: "Cette machine a une version de Boite trop ancienne pour cette action. Mettez-la à jour dans Réglages, Machines et mises à jour, puis réessayez.", + notConnected: "{machine} n'est pas connectée. Boite se reconnecte tout seul dès qu'elle répond.", + notConnectedHere: "Cette machine n'est pas connectée. Boite se reconnecte tout seul dès qu'elle répond.", + pairingLink: "Ce n'est pas un lien pour ajouter un appareil. Copiez le lien affiché par Ajouter un appareil sur l'autre machine ; il commence par http et contient ?grant=." + } +}; diff --git a/packages/ui/src/lib/strings/fr/experiments.ts b/packages/ui/src/lib/strings/fr/experiments.ts new file mode 100644 index 000000000..7abe01ddb --- /dev/null +++ b/packages/ui/src/lib/strings/fr/experiments.ts @@ -0,0 +1,41 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + experiments: { + recordingIndicators: { title: 'Actions visibles dans les vidéos', hint: 'Ajouter des repères de clics et les touches de navigation aux enregistrements. Le texte saisi et les mots de passe ne sont jamais affichés comme libellés de touches.' }, + openChatLinks: { title: 'Ouvrir les liens du chat dans leurs applications', hint: 'Sur ce bureau, un clic sur un fichier ou dossier local ouvre son application, y compris les raccourcis et les fichiers hors du projet. Seuls les liens de cet ordinateur sont ouverts.' }, + goal2: { title: 'Objectifs relus', hint: "Ajoute /goal2 : quand l'agent annonce l'objectif atteint, une nouvelle conversation qui n'a pas vu son travail le vérifie et répond validé, partiel ou refusé. Tout ce qui n'est pas validé repart à l'agent, jusqu'à cinq relectures" }, + whip: { + title: 'Fouet', + hint: 'Un fouet animé suit le pointeur ou le doigt. Un geste vif le fait claquer, un clic ou un appui le lâche', + action: 'Sortir le fouet', + drop: 'Lâcher le fouet' + }, + previewComments: { title: 'Commentaires sur les aperçus', hint: 'Sélectionner un élément dans le navigateur intégré et l’ajouter au brouillon de la conversation' }, + themeGrain: { + title: 'Thème Grain', + hint: 'Un fond grainé qui passe du gris clair en haut à gauche au noir en bas à droite' + }, + sessionImport: { + title: 'Import de session Claude Code', + hint: 'Transformer une session lancée dans le terminal en conversation, depuis le menu du projet et la palette' + }, + promptCache: { + title: 'Minuteur du cache de prompt', + hint: 'Une horloge à côté de la jauge de contexte, qui décompte le temps pendant lequel le provider garde la conversation en cache' + }, + residentAgents: { + title: 'Agents', + hint: 'Des agents résidents avec leur propre brain, leurs missions et leurs routines, depuis la barre latérale. Les agents déjà créés continuent de tourner quand ceci est désactivé' + }, + edgeScrollbars: { + title: 'Barres de défilement au bord', + hint: 'Les barres de défilement ne prennent plus de place. Chacune apparaît quand le pointeur s’approche du bord de sa zone et reste déplaçable. Souris et stylet uniquement' + }, + pressPreload: { + title: 'Chargement à l’appui', + hint: 'Une conversation ou une page commence à charger dès que vous appuyez dessus, avant de relâcher le clic ou de lever le doigt' + } + } +}; diff --git a/packages/ui/src/lib/strings/fr/features.ts b/packages/ui/src/lib/strings/fr/features.ts new file mode 100644 index 000000000..017bff5ac --- /dev/null +++ b/packages/ui/src/lib/strings/fr/features.ts @@ -0,0 +1,9 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + /** Finished features, on by default, each switched from the Settings page it belongs to. */ + features: { + chatArtifacts: { title: 'Fichiers et aperçus dans le chat', hint: 'Ouvrir les liens de fichiers et afficher les fichiers envoyés par un agent dans la conversation' }, + } +}; diff --git a/packages/ui/src/lib/strings/fr/files.ts b/packages/ui/src/lib/strings/fr/files.ts new file mode 100644 index 000000000..3eeaedeb6 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/files.ts @@ -0,0 +1,37 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + files: { + tree: 'Fichiers du répertoire de travail', + noRoot: 'Aucun répertoire de travail', + filter: 'Filtrer', + filterHint: 'Restreindre les lignes déjà chargées', + reading: 'Lecture de ce dossier', + empty: 'Ce dossier est vide', + noMatch: 'Aucune ligne chargée ne correspond', + failed: 'Ce dossier est illisible. {reason}', + capped: 'Seules les {count} premières entrées de ce dossier sont listées.', + readFailed: 'Ce fichier est illisible. {reason}', + writeFailed: "Ce fichier n'a pas pu être enregistré. {reason}", + readingFile: 'Lecture de ce fichier', + noFile: 'Cet onglet ne porte aucun chemin', + truncated: "Ce fichier a été coupé à sa limite de taille : ce qui suit n'est pas le fichier entier et ne peut pas être enregistré.", + readOnly: "Enregistrer un fichier appartient au propriétaire, celui-ci s'ouvre donc en lecture seule.", + saving: 'Enregistrement', + unsaved: 'Modifications non enregistrées', + discardOne: 'Fermer {name} sans enregistrer ?', + discardMany: 'Fermer {count} fichiers sans enregistrer ?', + discardBody: 'Le texte saisi depuis le dernier enregistrement sera perdu.', + discardConfirm: 'Fermer sans enregistrer', + editorLabel: 'Contenu du fichier', + zoomIn: 'Agrandir', + zoomOut: 'Réduire', + fit: 'Ajuster', + actual: '1:1', + natural: '{width} x {height}', + picture: "L'image de ce fichier", + download: 'Télécharger ce fichier', + binary: 'Rien ici ne se lit comme du texte.' + } +}; diff --git a/packages/ui/src/lib/strings/fr/firstRun.ts b/packages/ui/src/lib/strings/fr/firstRun.ts new file mode 100644 index 000000000..2a3edc6ca --- /dev/null +++ b/packages/ui/src/lib/strings/fr/firstRun.ts @@ -0,0 +1,9 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + firstRun: { + heading: 'Ouvrir un projet', + deviceBody: "Les projets s'ouvrent depuis l'application où tourne le cœur. Ceux qu'il détient apparaissent ici.", + pathPlaceholder: 'Chemin absolu du dossier', + } +}; diff --git a/packages/ui/src/lib/strings/fr/group.ts b/packages/ui/src/lib/strings/fr/group.ts new file mode 100644 index 000000000..8be290481 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/group.ts @@ -0,0 +1,57 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + group: { + rename: 'Renommer le groupe', merging: 'Regroupement des machines connectées', + otherGroup: '{machine} appartient à {group}. Quittez ce groupe sur la machine pour la déplacer ici.', + heading: 'Groupe', + intro: "Vos machines et vos téléphones, dans un seul groupe. Chaque machine joint les autres toute seule, et un téléphone ajouté sur l'une d'elles les atteint toutes. Chaque machine d'un groupe a le contrôle total des autres : ne groupez que ce qui est à vous.", + name: 'Nom du groupe', namePlaceholder: 'Maison', + join: 'Rejoindre', joining: 'Connexion', joinLabel: "Lien d'une autre machine", + joinHint: "Sur une machine du groupe, ouvrez Réglages, Machines, Ajouter un appareil, copiez le lien et collez-le ici. Cette machine rejoint ce groupe avec ses téléphones.", + members: 'Machines et appareils du groupe', self: 'Cette machine', connected: 'Connectée', connecting: 'Connexion', + relayed: 'Connectée via {machine}', through: 'Via {machine}', + unreachable: "Injoignable d'ici. Elle est peut-être éteinte, en veille ou sur un autre réseau.", + insecure: "Cette machine ne donne aucune adresse utilisable par cet appareil, et aucune machine du groupe ne peut l'y mener. Donnez-lui une adresse HTTPS publique.", + loopback: "Cette machine n'écoute que sur elle-même : les autres ne peuvent pas la joindre. Connectez-la à Tailscale, ou activez l'écoute sur le réseau.", + copied: 'Copié', + addDevice: 'Ajouter un appareil', adding: 'Préparation', + addOlder: "Boite sur {machine} est trop ancien pour ajouter un appareil. Mettez-le à jour dans Mises à jour sur cette page, puis revenez.", + scanTitle: 'Scannez avec votre téléphone', + addHint: "Ouvrez Boite sur le téléphone et touchez Scanner le code. Sur un ordinateur, copiez le lien et collez-le sous Rejoindre.", + qr: "Le lien qui ajoute un appareil, en QR code", + copyLink: 'Copier le lien', link: 'Lien pour un ordinateur', + expires: "Sert une fois, jusqu'à {time}", + device: 'Appareil', phone: 'Téléphone', browser: 'Navigateur', devicesList: 'Appareils du groupe', + deviceAdded: 'Ajouté sur {machine}', + deviceLegacy: 'Passe à la clé du groupe à sa prochaine connexion', + thisDevice: 'Cet appareil', + revokeDevice: 'Retirer du groupe', revokeDeviceTitle: 'Retirer {device} du groupe ?', + revokeDeviceBody: "Toutes les machines du groupe le refusent dès maintenant, y compris celles qui sont éteintes, dès leur retour. Pour le rajouter, il faudra un nouveau code.", + remove: 'Retirer du groupe', removeTitle: 'Retirer {machine} du groupe ?', + removeBody: "Les autres machines cessent de lui faire confiance dès qu'elles l'apprennent. Une machine éteinte l'apprend à son retour. Les téléphones ajoutés sur elle restent dans le groupe.", + leave: 'Quitter le groupe', leaveTitle: 'Quitter {group} ?', + leaveBody: "Cette machine oublie le groupe. Les téléphones ajoutés sur cette machine quittent le groupe avec elle.", + syncAll: 'Garder les réglages de toutes les machines comme sur {source}', + syncAllHint: "Copie les limites, les protections des processus, les mises à jour des agents, les raccourcis et les options du brain vers toutes les machines du groupe, maintenant et à chaque changement, tant que cette fenêtre est ouverte. L'accès réseau et les connexions aux fournisseurs restent sur chaque machine.", + lanHint: 'Un téléphone atteint ce lien une fois le réseau local activé et Boite redémarré.', + reach: { + loopback: "Ce lien ne fonctionne que sur cet ordinateur : Boite n'écoute pas encore le réseau local.", + button: 'Rendre accessible sur le réseau local', + restarting: "Boite redémarre pour écouter le réseau local. Les agents en cours terminent leur étape et continuent.", + failed: "Boite n'est pas revenu sur le réseau local. Quittez-le depuis la zone de notification, rouvrez-le, puis ajoutez le téléphone." + }, + firewall: { + blocked: 'Le pare-feu Windows bloque Boite sur ce réseau {networks} : un téléphone ne peut pas joindre cet ordinateur.', + unset: "Le pare-feu Windows n'a pas encore autorisé Boite sur ce réseau {networks} : un téléphone ne peut pas joindre cet ordinateur.", + allow: 'Autoriser dans le pare-feu Windows', + allowing: 'En attente de Windows', + allowed: 'Le pare-feu Windows laisse désormais les téléphones joindre Boite sur tous les réseaux.', + cancelled: "Rien n'a changé : Windows a besoin qu'un administrateur accepte sa demande.", + failed: 'Windows a refusé la modification. Autorisez boite-core.exe dans les paramètres du pare-feu Windows.', + unreadable: "Le pare-feu Windows n'a pas pu être lu. Si un téléphone ne se connecte pas, autorisez-y boite-core.exe.", + networks: { public: 'public', private: 'privé', domain: "d'entreprise" }, + and: ' et ' + } + } +}; diff --git a/packages/ui/src/lib/strings/fr/harnessUpdates.ts b/packages/ui/src/lib/strings/fr/harnessUpdates.ts new file mode 100644 index 000000000..9022f99e6 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/harnessUpdates.ts @@ -0,0 +1,23 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + harnessUpdates: { + heading: 'Mises à jour des agents', + intro: 'Chaque machine vérifie ses agents toutes les six heures.', + auto: 'Mettre les agents à jour automatiquement', + autoHint: "Un agent se met à jour sans que personne ne soit connecté : une conversation en cours se met en pause après son appel d'outil et reprend une fois la mise à jour faite", + checking: 'Vérification', + available: (name: string, version: string) => `${name} ${version} est disponible`, + availableShort: 'Mise à jour disponible', + installed: (version: string) => `Version installée : ${version}`, + on: (machine: string) => `Sur ${machine}`, + update: 'Mettre à jour', skip: 'Ignorer', + updating: (name: string) => `Mise à jour de ${name}`, + waiting: (count: number) => count === 1 ? "Attend la pause d'une conversation" : `Attend la pause de ${count} conversations`, + failed: (name: string) => `La mise à jour de ${name} a échoué`, + upToDate: 'À jour', unknown: 'Vérifie lui-même', runUpdater: 'Lancer sa mise à jour', + skipped: (version: string) => `${version} ignorée`, + unskip: 'Proposer à nouveau', none: "Aucun agent sur cette machine ne possède de programme de mise à jour que Boite peut lancer.", + route: { managed: 'Installé par Boite', self: 'Votre installation' } + } +}; diff --git a/packages/ui/src/lib/strings/fr/hooks.ts b/packages/ui/src/lib/strings/fr/hooks.ts new file mode 100644 index 000000000..790bfa804 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/hooks.ts @@ -0,0 +1,31 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + hooks: { + heading: 'Hooks', + description: 'Les scripts que vos agents lancent sur leurs propres événements. Les compteurs et la liste récente partent du démarrage de Boite sur cette machine.', + refresh: 'Actualiser les hooks', + missing: 'Mettez Boite à jour sur cette machine pour voir vos hooks.', + hookOne: '{count} hook', hookMany: '{count} hooks', + moduleOne: '{count} module', moduleMany: '{count} modules', + none: 'Aucun hook pour l\'instant', + notFound: 'Rien trouvé', + runOne: '{count} exécution', runMany: '{count} exécutions', + noRuns: 'Aucune exécution pour l\'instant', + blockedOne: '{count} bloquée', blockedMany: '{count} bloquées', + failedOne: '{count} en échec', failedMany: '{count} en échec', + skippedOne: '{count} ignorée', skippedMany: '{count} ignorées', + sourceMissing: 'Introuvable', + sourceError: 'Illisible', + unreadable: 'Impossible de lire {path}', + configOnly: 'Boite lit cette configuration mais ne voit pas chaque exécution.', + sharedOne: 'Partagé avec {count} compte séparé', sharedMany: 'Partagé avec {count} comptes séparés', + accountMisses: '{account} ne reçoit pas {path}', + withoutOne: '{names} n\'exécute pas de hooks.', withoutMany: '{names} n\'exécutent pas de hooks.', + recent: 'Récemment', + since: 'Depuis {time}', + quiet: 'Rien de bloqué ni en échec.', + more: 'Afficher {count} de plus', + outcomes: { blocked: 'Bloqué', failed: 'Échec', stopped: 'Arrêté', skipped: 'Ignoré' }, + } +}; diff --git a/packages/ui/src/lib/strings/fr/imports.ts b/packages/ui/src/lib/strings/fr/imports.ts new file mode 100644 index 000000000..cfd001eca --- /dev/null +++ b/packages/ui/src/lib/strings/fr/imports.ts @@ -0,0 +1,13 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + imports: { + title: 'Importer une session dans {project}', + intro: "Sessions Claude Code lancées dans ce dossier. L'une d'elles devient une conversation avec son historique, et le tour suivant la continue.", + loading: 'Recherche des sessions', + empty: 'Aucune session Claude Code pour ce dossier.', + imported: 'Importée', + importing: 'Import en cours', + } +}; diff --git a/packages/ui/src/lib/strings/fr/install.ts b/packages/ui/src/lib/strings/fr/install.ts new file mode 100644 index 000000000..e4e1a337d --- /dev/null +++ b/packages/ui/src/lib/strings/fr/install.ts @@ -0,0 +1,22 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + install: { + action: 'Installer', + update: 'Mettre à jour', + repair: 'Réinstaller', + remove: 'Désinstaller', + removeTitle: 'Désinstaller {provider} ?', + removeBody: 'Les fichiers téléchargés par Boite partent avec. Vos comptes restent, et il pourra être réinstallé plus tard.', + removeConfirm: 'Désinstaller', + removeCancel: 'Garder', + absent: 'Pas installé, {size}', + downloading: 'Téléchargement, {percent}', + verifying: "Vérification de l'archive", + extracting: 'Décompression', + installed: 'Installé par Boite', + updateAvailable: 'Version {installed}, {available} disponible', + progress: 'Progression du téléchargement' + } +}; diff --git a/packages/ui/src/lib/strings/fr/keyboard.ts b/packages/ui/src/lib/strings/fr/keyboard.ts new file mode 100644 index 000000000..3ed704ba3 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/keyboard.ts @@ -0,0 +1,54 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + keyboard: { + heading: 'Raccourcis', + intro: 'Cliquez sur un raccourci, puis appuyez sur les touches voulues. Le changement s\'applique aussitôt sur cette machine.', + search: 'Filtrer les commandes', + groups: { general: 'Général', surfaces: 'Panneau latéral', thread: 'Conversation et message', jump: 'Aller à une conversation', theme: 'Thème', terminal: 'Terminal' }, + record: 'Appuyez sur les touches', + change: 'Changer le raccourci', + reset: 'Revenir au défaut', + resetAll: 'Tout réinitialiser', + clear: 'Retirer le raccourci', + conflict: '{chord} lance déjà "{command}". Le déplacer ici ?', + replace: 'Le déplacer ici', + needsModifier: "Tenez Ctrl, Alt ou Win avec la touche. Une touche seule, ou avec Maj seulement, c'est de la frappe.", + noMatch: 'Aucune commande ne correspond.', + file: 'Fichier des raccourcis', + openFile: 'Ouvrir le fichier', + copyPath: 'Copier le chemin', + openFailed: 'Impossible d’ouvrir le fichier : {reason}', + fileHint: + 'Des identifiants de commande associés à des raccourcis comme "mod+shift+k", ou null pour retirer une touche. mod vaut Ctrl ici et Cmd sur un Mac. Les entrées absentes gardent leur valeur par défaut.', + id: 'Identifiant', + none: 'Non défini', + custom: 'modifié', + problems: 'Refusé', + problemsHint: 'Ces lignes du fichier n\'ont pas été appliquées. Corrigez-les et enregistrez.', + commands: { + palette: 'Ouvrir la palette de commandes', + browser: 'Ouvrir la surface navigateur', + changes: 'Ouvrir la surface des modifications', + files: "Ouvrir l'arbre des fichiers", + tasks: 'Ouvrir la surface des tâches', + closeSurface: 'Fermer la surface active', + stash: 'Mettre le message de côté, ou le reprendre', + sendAndDraft: 'Envoyer et ouvrir une nouvelle conversation', + pin: 'Épingler ou désépingler cette conversation', + reopenThread: 'Rouvrir la dernière conversation archivée', + copyAnswer: 'Copier la dernière réponse', + find: 'Chercher dans cette conversation', + terminalNew: 'Nouveau terminal', + terminalSplit: 'Diviser le terminal à droite', + terminalSplitVertical: 'Diviser le terminal en bas', + terminalClose: 'Fermer le terminal', + terminalFind: 'Chercher dans le terminal', + terminalHistory: 'Commandes récentes du shell', + terminalPreviousCommand: 'Aller à la commande précédente', + terminalNextCommand: 'Aller à la commande suivante', + thread: 'Aller à la conversation {n}' + } + } +}; diff --git a/packages/ui/src/lib/strings/fr/machines.ts b/packages/ui/src/lib/strings/fr/machines.ts new file mode 100644 index 000000000..b2415b4f7 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/machines.ts @@ -0,0 +1,47 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + machines: { + heading: 'Machines', local: 'Ce PC', projects: 'Projets', recent: 'Récent', + intro: 'Chaque machine exécute ses propres agents sur ses propres fichiers.', + details: 'Détails', address: 'Adresse', updateFailed: 'Mise à jour échouée', + label: 'Nom de la machine', icon: 'Icône de la machine', icons: { desktop: 'Ordinateur fixe', laptop: 'Portable', server: 'Serveur', rack: 'Baie', cloud: 'Cloud', cpu: 'Processeur' }, link: "Lien d'une autre machine", add: 'Ajouter une machine', adding: 'Connexion', connect: 'Connecter', + addHint: "Sur une machine de votre groupe, ouvrez Réglages, Machines, Ajouter un appareil, copiez le lien et collez-le ici.", + scan: 'Scanner le code', pasteLink: 'Coller le lien', scanTitle: 'Scannez le code', + linkInsecure: "Ce lien mène à une adresse HTTP simple, que l'appli ne peut pas ouvrir. Ajoutez l'appareil depuis une machine qui a une adresse HTTPS.", + scanHint: "Sur l'une de vos machines, ouvrez Réglages, Machines, Ajouter un appareil et visez son QR code avec la caméra.", + scanStarting: 'Démarrage de la caméra', scanClose: 'Fermer la caméra', + scanErrors: { + insecure: "La caméra demande une adresse HTTPS. Ouvrez Boite par son adresse HTTPS, ou collez le lien.", + denied: "L'accès à la caméra a été refusé. Autorisez-le pour ce site dans les réglages du navigateur, ou collez le lien.", + missing: 'Aucune caméra trouvée sur cet appareil. Collez le lien à la place.', + failed: "La caméra n'a pas pu démarrer. Collez le lien à la place." + }, + linkReaches: 'Ce lien connecte à {host}.', linkReplaces: 'Ce lien connecte à {host}, listée ici sous le nom {machine}. Il remplace la clé enregistrée pour elle.', linkConnected: 'Ce lien vise {host}, déjà connectée ici sous le nom {machine}.', + remove: 'Retirer la machine', removeTitle: 'Retirer {machine} ?', + removeBody: 'Boite oublie cette machine sur cet appareil.', + open: 'Ouvrir la machine', invalidUrl: "L'URL d'une machine doit être une adresse HTTP ou HTTPS, sans identifiants, sans requête ni fragment.", + duplicate: 'Cette machine est déjà connectée.', refreshPr: 'Actualiser la pull request', + manual: 'Connecter avec une URL et un jeton', + filter: 'Filtrer les machines', all: 'Toutes les machines', + main: 'Machine principale', mainLast: 'La dernière utilisée', + mainHint: "Boite s'ouvre sur cette machine. Si elle est éteinte, Boite s'ouvre sur une autre.", + mainLastHint: "Boite s'ouvre sur la dernière machine utilisée.", + timeout: "La machine n'a pas répondu en 12 secondes. Vérifiez son adresse et ses origines navigateur, puis reconnectez-la.", + linkTitle: 'Se connecter à {host} ?', + linkBody: "Le lien ouvert dirige cette page vers une machine à laquelle cet appareil ne s'est jamais connecté. Cette machine verra tout ce que vous tapez ici. Ne vous connectez que si vous avez créé ce lien ou si vous faites confiance à qui vous l'a envoyé. Annuler garde votre machine actuelle.", + linkConfirm: 'Se connecter', + browserOrigins: 'Origines navigateur autorisées', + browserOriginsHint: "Les machines du groupe, et les adresses où elles sont servies, sont autorisées toutes seules. N'ajoutez ici qu'une page servie par quelque chose hors du groupe. Une origine http(s) exacte par ligne.", + settings: 'Réglages de cette machine', settingsHint: 'Les limites de ressources, le maintien des processus, les mises à jour des agents et le stockage des worktrees concernent uniquement cette machine.', + synced: 'Préférences communes synchronisées avec {source}.', + syncBrainAbsent: 'Connectez un dossier brain sur cette machine pour copier aussi ses options.', + syncKeysAbsent: "L’une des deux machines a un Boite sans raccourcis clavier : ils sont restés tels quels.", + syncStopped: 'La copie s’est arrêtée sur {stage} : {error}. Ce qui précède reste copié.', + syncStages: { settings: 'les réglages', keybindings: 'les raccourcis clavier', brain: 'les options du brain' }, + syncProviders: "À connecter sur cette machine : {providers}. Une connexion ne quitte jamais la machine où elle a été faite.", + syncOpenProviders: 'Ouvrir ses fournisseurs', + threadFailed: 'Une conversation a échoué ici : {thread}', + agentLinkAddressRequired: 'Mettez les deux machines à jour pour utiliser le relais de l’app, ou définissez une adresse HTTPS joignable pour {machine} dans les réglages généraux.', + } +}; diff --git a/packages/ui/src/lib/strings/fr/media.ts b/packages/ui/src/lib/strings/fr/media.ts new file mode 100644 index 000000000..d617cfa30 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/media.ts @@ -0,0 +1,17 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + media: { + sourceTooLarge: '{name} pèse {size} : Boite réduit les images de {max} au plus, elle a donc été laissée de côté.', + viewer: 'Visionneuse', + previous: 'Précédente', + next: 'Suivante', + position: '{index} sur {total}', + share: 'Partager ou enregistrer', + shareFailed: 'Impossible de partager {name}.', + shareReady: '{name} est prêt : touchez Partager à nouveau.', + answerFiles: 'Fichiers joints : {names}', + answerFileCount: '{count} fichiers' + } +}; diff --git a/packages/ui/src/lib/strings/fr/mention.ts b/packages/ui/src/lib/strings/fr/mention.ts new file mode 100644 index 000000000..338308948 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/mention.ts @@ -0,0 +1,10 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + mention: { + label: 'Fichiers', + empty: 'Aucun fichier ne correspond', + more: '{count} de plus, continuez à taper' + } +}; diff --git a/packages/ui/src/lib/strings/fr/mobile.ts b/packages/ui/src/lib/strings/fr/mobile.ts new file mode 100644 index 000000000..4a448f0ae --- /dev/null +++ b/packages/ui/src/lib/strings/fr/mobile.ts @@ -0,0 +1,39 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + mobile: { + clearSearch: 'Effacer la recherche', + atWork: '{count} en cours', atWorkFilter: 'Afficher seulement les conversations en cours', + draftTitle: 'Que voulez-vous faire ?', draftHint: 'Lancez une tâche, posez une question ou reprenez votre travail.', + pairTitle: 'Votre Boite, sur votre téléphone', + pairBody: 'Sur l’une de vos machines, ouvrez Réglages, Machines, Ajouter un appareil, puis scannez son QR code ici.', + pairStep: 'Sur votre ordinateur : Réglages, Machines, Ajouter un appareil.', + pairInstalled: 'Vous venez d’installer Boite ? Ajoutez-la ici aussi : sur iPhone, l’application garde sa connexion séparément du navigateur.', + pairPrivacy: 'Le QR code ne sert qu’une fois et expire en quelques minutes. Ce téléphone atteint ensuite toutes les machines du groupe.', + scanInApp: "Utilisez le bouton Scanner ici plutôt que l'app Appareil photo : celle-ci ouvre le lien dans Safari, qui garde sa propre connexion.", + pairingRequired: 'Ajouter cette application à votre groupe', + offlineTitle: 'En attente de votre ordinateur', + offlineBody: 'Gardez Boite ouverte sur votre ordinateur et vérifiez votre connexion. Si vous utilisez Tailscale, gardez-le connecté sur les deux appareils.', + localNetworkTitle: 'Autorisez le réseau local', + localNetworkBody: 'Vos machines sont sur votre réseau local, et votre navigateur en bloque Boite. Dans le navigateur qui fait tourner l’application (Chrome ou un autre), ouvrez Paramètres, Paramètres des sites, Réseau local et autorisez {host}. Sous Android, autorisez aussi Appareils à proximité pour ce navigateur dans ses paramètres d’application.', + troubleshoot: 'Dépanner', troubleshootSteps: 'Vérifications de la connexion', + checkLocalNetwork: 'Réseau local', + checkLocalNetworkHint: 'Dans le navigateur qui fait tourner l’application : Paramètres, Paramètres des sites, Réseau local, autorisez {host}.', + localNetworkStates: { granted: 'Autorisé', denied: 'Bloqué', prompt: 'Sans réponse' }, + checkNearby: 'Appareils à proximité', + checkNearbyHint: 'Dans les paramètres Android : Applications, le navigateur qui fait tourner l’application, Autorisations, Appareils à proximité, Autoriser. Android le demande en plus du réseau local.', + checkReach: 'Joindre {host}', checkOpen: 'Ouvrir', + checkReachHint: 'Ouvrez-la dans un onglet du navigateur. Si la page ne charge pas, ce téléphone ne joint pas la machine : vérifiez Tailscale, le VPN ou le Wi-Fi.', + localNetworkAsk: 'Quand votre navigateur le demande, autorisez Boite à accéder aux appareils de votre réseau local : vos machines s’y trouvent.', + emptyTitle: 'Commencez une conversation', + emptyBody: 'Confiez une tâche à un agent. Vos conversations apparaîtront ici.', + emptyActivity: 'Tout est à jour', + computer: 'Ordinateur connecté', + pairAgain: 'Rajouter', + settingsDevice: 'Ce téléphone', + settingsPhone: 'Application et notifications', + settingsBack: 'Retour aux réglages', + navigation: 'Navigation', threads: 'Conversations', activity: 'Activité', project: 'Choisir un projet', + search: 'Trouver une conversation', noActivity: "Aucun agent n'attend votre réponse.", noThreads: 'Aucune conversation ne correspond.' + } +}; diff --git a/packages/ui/src/lib/strings/fr/notify.ts b/packages/ui/src/lib/strings/fr/notify.ts new file mode 100644 index 000000000..eaaef8354 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/notify.ts @@ -0,0 +1,12 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + notify: { + done: 'Terminé', + failed: 'Échec', + openThread: 'Ouvrir la conversation', + needsYou: 'Attend votre réponse', + connected: 'Les notifications fonctionnent' + } +}; diff --git a/packages/ui/src/lib/strings/fr/onboarding.ts b/packages/ui/src/lib/strings/fr/onboarding.ts new file mode 100644 index 000000000..5bfdc05ee --- /dev/null +++ b/packages/ui/src/lib/strings/fr/onboarding.ts @@ -0,0 +1,112 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + onboarding: { + privacy: { + title: 'VENDEZ VOTRE ÂME À BOITE !', + intro: 'Quelques stats anonymes nous disent quoi corriger en premier. Vos messages, fichiers et comptes ne quittent jamais votre machine.', + question: 'Marché conclu ?', + video: 'Mème Trade Offer', + basic: 'NON ! Juste les compteurs de base', + basicDefault: '(par défaut)', + basicHint: 'Des compteurs anonymes, aucun profil.', + deal: 'MARCHÉ CONCLU', + dealHint: 'Aussi les modèles et réglages utilisés. Merci !', + details: "De base : installations actives sous un identifiant aléatoire haché, et lancements, conversations et résultats des tours sous des identifiants qui changent chaque jour. Aucun profil. Marché conclu ajoute les modèles publics choisis, le niveau de raisonnement, les modes de vitesse et de permission, les durées et les volumes de jetons, sous un identifiant aléatoire stable ; les modèles privés comptent comme « autre ». L'application marche pareil dans tous les cas, et les réglages le changent quand vous voulez.", + optOut: 'Si vous ne voulez VRAIMENT pas aider Boite :', + offLabel: 'tout désactiver :c', + doc: 'Ce qui est collecté' + }, + demo: { + pause: 'Pause', play: 'Rejouer', resume: 'Reprendre', + record: 'Cliquez sur le micro', speak: 'Dictez votre message', review: "Relisez avant d'envoyer", + listening: 'Écoute en cours', draft: 'Votre brouillon', localLabel: 'Sur cet ordinateur', + chooseAgent: "Choisissez l'agent suivant", handoff: 'Codex reprend la conversation', checked: 'Libellés vérifiés. Contraste amélioré.', nextMessage: 'Écrire à Codex', + before: 'Avant', after: 'Après', buttonBefore: 'Envoyer', buttonAfter: 'Envoyer le message', changedFile: 'ContactForm.svelte', changesReady: 'Deux lignes modifiées', + background: 'En cours en arrière-plan', writing: 'Préparer le lancement de demain', notes: 'Notes', noteOne: 'Relire le formulaire de contact', noteTwo: 'Publier le portfolio', + taskResult: 'Formulaire prêt', loginResult: 'Connexion réparée', testsResult: 'Tests réussis', hoverTray: "Survolez l'icône Boite", + conversation: "Changer d'agent", voice: 'Dicter', panel: 'Voir les modifications', + task: 'Créer mon portfolio', secondTask: 'Réparer la connexion', thirdTask: 'Écrire les tests', working: 'En cours', request: 'Ajoute un formulaire de contact', answer: 'Le formulaire est prêt.', followup: "Vérifie maintenant l'accessibilité", continued: 'Même conversation. Même historique.', + voiceWords: 'Rends les boutons plus lisibles', voiceHint: 'Parlez, relisez le brouillon, puis envoyez.', + changes: 'Modifications', reviewed: 'Relisez les changements à côté du chat.', + usageLabel: 'Claude', window: 'Limite sur 5 h', used: '24 % utilisés', reset: 'Réinitialisation dans 2 h 10', trayHint: "Survolez l'icône Boite pour voir vos limites sans ouvrir l'application.", + desktop: 'Votre ordinateur', phone: 'Votre téléphone', synced: 'La même conversation', reachHint: "Après un appairage par QR code, suivez vos agents sur votre téléphone. Votre ordinateur continue de les faire tourner.", + notification: 'Formulaire de contact terminé', quietBody: "Vos agents travaillent sans prendre votre écran ni vos haut-parleurs.", + installVoice: 'Installer la dictée locale', downloadVoice: 'Environ 200 Mo. Le son reste sur cet ordinateur.', installingVoice: 'Téléchargement du moteur vocal', cancelVoice: 'Annuler le téléchargement', readyVoice: 'La dictée est prête. Utilisez le micro du champ de message.', + }, + label: 'Prise en main', + skip: 'Passer', + back: 'Retour', + next: 'Suivant', + done: 'À moi de boiter', + step: 'Étape {index} sur {total}', + progress: "Aller à l'étape {index} : {title}", + replay: 'Revoir la présentation', + changeLater: 'Tout ça se retrouve dans les réglages.', + + profile: { + title: "D'abord, lequel êtes-vous ?", + everyday: "Je ne suis pas dev ! Ne m'embrouillez pas avec du code et des commandes !", + everydayHint: "Une nouvelle conversation attend dans Documents/Boite jusqu'à ce que vous lui donniez un dossier, et l'agent demande avant d'agir.", + developer: 'Je suis dev, mettez-moi la totale.', + developerHint: "L'appli s'ouvre sur votre dernier projet, les conversations montrent le terminal et la trace des processus, le panneau s'ouvre sur les modifications.", + later: "Rien n'est retiré, dans un cas comme dans l'autre. Réglages, Apparence affiche ou masque chaque bouton, un par un." + }, + + welcome: { + title: 'Boite fait tourner vos agents', + body: 'Une conversation par tâche. Les agents continuent pendant que vous faites autre chose.' + }, + + agents: { + title: 'Une conversation qui suit votre travail', + body: "Changez d'agent, dictez un message, relisez le résultat. Sans changer de conversation.", + }, + + voice: { + title: 'Parler au lieu de taper', + body: "Appuyez sur le micro et parlez. Vous relisez le texte avant qu'il parte.", + open: 'Réglages de la voix', + reading: 'Lecture du moteur vocal' + }, + + panel: { + title: 'Le panneau à côté de la conversation', + body: 'Modifications, fichiers, tâches et navigateur, à une touche.', + open: 'Raccourcis clavier', + }, + + usage: { + title: "Voir ce qu'il vous reste", + body: "Voyez quel abonnement se remplit et quand il se réinitialise, même avec Boite en arrière-plan.", + monitor: 'Lire les limites de {account}', + connect: 'Connecter un fournisseur', + deviceHint: 'Les limites sont lues sur la machine qui héberge le cœur.' + }, + + reach: { + onPhoneTitle: "Votre ordinateur fait le travail", + onPhoneBody: "Ce téléphone affiche les conversations de votre ordinateur. Les agents tournent là-bas et continuent quand vous rangez le téléphone.", + onPhoneSettings: "Les notifications et l'application sur l'écran d'accueil sont dans les Réglages.", + title: 'Éloignez-vous. Gardez le fil.', + phone: 'Sur votre téléphone', + phoneBody: 'Scannez un QR code et votre téléphone retrouve les conversations de toutes vos machines, avec les notifications.', + pair: 'Ajouter un appareil', + machines: 'Un autre Boite', + machinesBody: 'Pilotez depuis cette application un Boite qui tourne sur un serveur ou un autre ordinateur.', + connect: 'Connecter une machine', + deviceHint: "L'appairage et les machines se gèrent depuis l'application où tourne le cœur." + }, + + quiet: { + title: 'Des agents qui ne vous coupent pas', + deviceHint: 'Le focus et le son sont des réglages de la machine qui héberge le cœur.' + }, + + project: { + title: 'Ouvrir votre premier projet', + body: 'Le dossier dans lequel un agent travaille.', + } + } +}; diff --git a/packages/ui/src/lib/strings/fr/palette.ts b/packages/ui/src/lib/strings/fr/palette.ts new file mode 100644 index 000000000..ac3e0f4ca --- /dev/null +++ b/packages/ui/src/lib/strings/fr/palette.ts @@ -0,0 +1,36 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + palette: { + placeholder: 'Cherchez une conversation, ou tapez une commande', + terminal: 'Afficher ou masquer le terminal', + threads: 'Conversations', + commands: 'Commandes', + empty: 'Rien ne correspond.', + hint: 'Entrée pour ouvrir, Échap pour fermer', + newThread: 'Nouvelle conversation', + addProject: 'Ajouter un projet', + importSession: 'Importer une session Claude Code', + settings: 'Ouvrir les réglages', + appearance: "Réglages d'apparence", + providers: 'Fournisseurs et comptes', + pair: 'Ajouter un appareil', + sidebar: 'Afficher ou masquer la barre latérale', + panel: 'Afficher ou masquer le panneau', + trace: 'Afficher la trace', + changes: 'Afficher les modifications', + files: 'Afficher les fichiers', + tasks: 'Afficher les tâches', + subagents: 'Afficher les sous-agents', + agents: 'Ouvrir les agents', + pin: 'Épingler cette conversation', + unpin: 'Désépingler cette conversation', + rename: 'Renommer cette conversation', + retitle: 'Regénérer le titre de cette conversation', + archive: 'Archiver cette conversation', + themeDark: 'Thème : sombre', + themeLight: 'Thème : clair', + themeSystem: 'Thème : système' + } +}; diff --git a/packages/ui/src/lib/strings/fr/parts.ts b/packages/ui/src/lib/strings/fr/parts.ts new file mode 100644 index 000000000..bd8e6f519 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/parts.ts @@ -0,0 +1,11 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + parts: { + loading: 'Chargement', + failed: "Cette partie de Boite n'a pas pu se charger.", + navigationFailed: "La navigation n'a pas pu se charger.", + dictationFailed: "La dictée n'a pas pu se charger. Réessayer" + } +}; diff --git a/packages/ui/src/lib/strings/fr/permissionMode.ts b/packages/ui/src/lib/strings/fr/permissionMode.ts new file mode 100644 index 000000000..e8a68497b --- /dev/null +++ b/packages/ui/src/lib/strings/fr/permissionMode.ts @@ -0,0 +1,13 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + permissionMode: { + default: 'Demander', + acceptEdits: 'Modifier librement', + bypassPermissions: 'Auto', + yolo: 'YOLO', + plan: 'Plan', + dontAsk: 'Tout refuser' + } +}; diff --git a/packages/ui/src/lib/strings/fr/permissionModeAgy.ts b/packages/ui/src/lib/strings/fr/permissionModeAgy.ts new file mode 100644 index 000000000..7077ba9d5 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/permissionModeAgy.ts @@ -0,0 +1,11 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + permissionModeAgy: { + default: 'Réglages agy', + defaultLong: 'Suit le réglage de permissions d\'agy et refuse ce qu\'il demanderait', + acceptEditsLong: 'Modifie les fichiers sans demander, refuse les commandes qui demandent une autorisation', + draft: 'avec les réglages de permissions d\'agy' + } +}; diff --git a/packages/ui/src/lib/strings/fr/permissionModeCodex.ts b/packages/ui/src/lib/strings/fr/permissionModeCodex.ts new file mode 100644 index 000000000..89bf12600 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/permissionModeCodex.ts @@ -0,0 +1,9 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + permissionModeCodex: { + default: 'Ce dossier', + defaultLong: 'Modifie et lance des commandes dans ce dossier sans demander, demande pour aller au-delà' + } +}; diff --git a/packages/ui/src/lib/strings/fr/permissionModeLong.ts b/packages/ui/src/lib/strings/fr/permissionModeLong.ts new file mode 100644 index 000000000..1cd9e8d0f --- /dev/null +++ b/packages/ui/src/lib/strings/fr/permissionModeLong.ts @@ -0,0 +1,13 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + permissionModeLong: { + default: 'Lit librement, demande avant de modifier un fichier ou de lancer une commande', + acceptEdits: 'Modifie les fichiers sans demander, demande avant de lancer une commande', + bypassPermissions: 'Exécute tout sans demander, partout sur cet ordinateur', + yolo: 'Autorise tous les outils ; ignore les hooks Claude et Codex', + plan: 'Plan seulement, aucune modification', + dontAsk: 'Refuser tout ce qui demanderait une autorisation' + } +}; diff --git a/packages/ui/src/lib/strings/fr/phone.ts b/packages/ui/src/lib/strings/fr/phone.ts new file mode 100644 index 000000000..1c5004f50 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/phone.ts @@ -0,0 +1,59 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + phone: { + preparing: "L'application prépare encore ses fichiers hors ligne. Rechargez la page et réessayez.", + settingsOffline: "Les réglages ne sont pas encore téléchargés. Reconnectez-vous et rouvrez les réglages.", + dialogOffline: "Cette fenêtre n'est pas encore téléchargée. Reconnectez-vous et rouvrez-la.", + heading: 'Application mobile', ownAddress: 'Utiliser une autre adresse HTTPS', publicUrl: 'Adresse HTTPS publique', urlPlaceholder: 'https://boite.example.com', + publicUrlHint: 'L\'origine HTTPS de votre reverse proxy, qui doit transmettre la page et /rpc à ce cœur. Les liens pour ajouter un appareil passent par elle.', + install: 'Installer Boite', installed: "Boite tourne comme application installée.", + installHint: "Sur iPhone, ouvrez Partager dans Safari, puis Sur l'écran d'accueil. Sur Android, utilisez Installer l'application dans le menu du navigateur.", + httpsRequired: "Cette connexion est en HTTP. Ouvrez Boite en HTTPS pour activer les fichiers hors ligne et les notifications.", + ownOrigin: "Les notifications s'activent depuis l'application installée depuis l'adresse de cette machine.", + pairFirst: "Ajoutez cette application à votre groupe avant d'activer les notifications.", + unsupported: "Ce navigateur ne gère pas les notifications push ici. Sur iPhone, ajoutez Boite à l'écran d'accueil et ouvrez-le depuis son icône.", + pushHint: 'Quand un agent termine, échoue ou attend votre réponse, même quand Boite est fermé.', + enable: 'Activer les notifications', disable: 'Désactiver les notifications', test: 'Envoyer une notification de test', + enabled: 'Notifications activées pour cet appareil.', testSent: 'Le service push a accepté la notification de test.', + denied: "Les notifications ont été refusées. Vous pouvez changer ça dans les réglages du navigateur ou de l'appareil.", + subscriptionFailed: "Le navigateur n'a pas renvoyé un abonnement push complet.", + multiMachine: "Les notifications viennent de la machine depuis laquelle cette application a été installée, et à travers elle de toutes les autres machines de son groupe.", + relayed: "Cette machine atteint cet appareil à travers celle depuis laquelle l'application a été installée : activez-y les notifications, celles de cette machine arrivent avec.", + tailscale: { + heading: 'HTTPS via Tailscale', + hint: "Tailscale donne à cette machine une adresse HTTPS sur votre tailnet. Boite s'y sert lui-même avec tailscale serve, et les liens pour ajouter un appareil l'utilisent. Un téléphone doit avoir Tailscale connecté pour l'atteindre.", + checking: 'Vérification de Tailscale…', + missing: "Tailscale n'est pas installé sur cette machine.", + download: 'Obtenir Tailscale', + stopped: "Tailscale est installé mais pas connecté. Ouvrez Tailscale et connectez-le, puis vérifiez à nouveau.", + needsLogin: 'Tailscale est déconnecté de votre compte sur cette machine. Connectez-vous, puis vérifiez à nouveau.', + signIn: 'Se connecter à Tailscale', + httpsDisabled: "Les certificats HTTPS sont désactivés sur votre tailnet. Dans la console d'administration Tailscale, page DNS, activez MagicDNS et HTTPS Certificates, puis vérifiez à nouveau.", + openAdmin: "Ouvrir la console d'administration", + off: 'Prêt : ce cœur peut être servi sur {url}.', + on: 'Ce cœur est servi sur {url}.', + onPublic: "Les liens pour ajouter un appareil utilisent cette adresse.", + conflict: "{url} sert déjà {target}. Boite n'y touche pas, sauf si vous le remplacez.", + error: "La commande tailscale n'a pas répondu comme prévu.", + enable: "Activer l'accès HTTPS via Tailscale", + disable: 'Désactiver', + replace: 'Remplacer…', + refresh: 'Vérifier à nouveau', + pair: 'Ajouter un appareil', + approve: 'Autoriser Serve', + replaceTitle: 'Remplacer ce que sert {url} ?', + replaceBody: "Tailscale sert actuellement {target} à cette adresse. La remplacer la fait pointer vers Boite, et ce qui l'utilisait n'y est plus joignable.", + replaceConfirm: 'Remplacer', + disableTitle: 'Ne plus servir Boite via Tailscale ?', + disableBody: "Les téléphones associés via {url} ne peuvent plus joindre ce cœur tant qu'il n'est pas servi à nouveau. Leurs clés restent valides.", + details: { + 'permission-denied': 'Tailscale a refusé la modification. Sous Linux, lancez une fois sudo tailscale set --operator=$USER, puis réessayez.', + 'serve-consent': "Votre tailnet n'autorise pas encore Serve. Ouvrez le lien, approuvez, puis réessayez.", + 'not-logged-in': 'Tailscale est déconnecté. Connectez-vous, puis réessayez.', + timeout: "Tailscale n'a pas répondu à temps. Réessayez.", + unknown: 'Tailscale a refusé la modification. Lancez tailscale serve status sur cette machine pour savoir pourquoi.' + } + } + } +}; diff --git a/packages/ui/src/lib/strings/fr/plugins.ts b/packages/ui/src/lib/strings/fr/plugins.ts new file mode 100644 index 000000000..bc8cf3a61 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/plugins.ts @@ -0,0 +1,90 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + plugins: { + heading: 'Extensions', + revealUnknownMachine: "Le fil ou l'agent qu'une application demande d'afficher est sur une machine à laquelle cette fenêtre n'est pas connectée. Ajoutez-la dans Réglages, Machines et mises à jour.", + revealMachineOffline: "Le fil ou l'agent qu'une application demande d'afficher est sur {machine}, qui n'est pas connectée pour l'instant.", + intro: 'Une extension est un programme qui tourne sur la machine hébergeant votre cœur, vérifié contre le SHA-256 publié par son manifeste.', + installedHeading: 'Installées', + recommendedHeading: 'Recommandées', + install: 'Installer', + update: 'Mettre à jour vers {version}', + reinstall: 'Réinstaller', + remove: 'Désinstaller', + stateInstalled: 'Installée', + stateInstalling: 'Téléchargement et vérification', + stateError: "L'installation a échoué", + stateRejected: 'Refusée par Boite', + updateAvailable: '{version} disponible', + source: 'Code source', + atCommit: '{ref} au commit {commit}', + pools: 'Pools de comptes', + empty: 'Aucun compte enregistré dans ce pool.', + add: 'Enregistrer le login CLI actuel', + switch: 'Utiliser ce compte', + forget: 'Oublier le compte', + active: 'Actif', + removeTitle: 'Désinstaller {name} ?', + removeBody: 'Supprime le dossier de l\'extension dans le répertoire de données de Boite. Ce que le programme a enregistré ailleurs, ses logins par exemple, reste en place.', + switchTitle: 'Changer le login CLI ?', + switchBody: "Cela change le login CLI par défaut du fournisseur pour les tours suivants, y compris hors de Boite. Les comptes Boite isolés gardent le leur.", + forgetTitle: 'Oublier ce login enregistré ?', + forgetBody: "Retire ce login du pool de l'extension.", + cliScope: 'Des logins CLI, distincts des comptes Boite connectés dans Fournisseurs.', + addHeading: 'Ajouter depuis une URL git', + addHint: 'L\'URL https d\'un dépôt git avec un boite-plugin.json à sa racine. Boite montre ce qu\'il téléchargerait avant d\'installer quoi que ce soit.', + urlLabel: 'URL du dépôt', + urlPlaceholder: 'https://github.com/owner/repo', + refLabel: 'Branche, tag ou commit', + refPlaceholder: 'Branche par défaut', + inspect: 'Aperçu', + inspecting: 'Lecture', + previewSource: 'Source', + previewReplaces: 'Remplace', + previewDownloads: 'Télécharge', + previewDigest: 'SHA-256', + previewRuns: 'Exécute', + previewPools: 'Pools de comptes', + previewApp: 'Application de bureau', + previewAppBody: "Ouvre sa propre fenêtre et obtient un accès complet à Boite grâce au jeton du propriétaire : elle peut lire et piloter chaque fil, comme cette application.", + app: 'Application de bureau', + appRunning: 'En marche', + appStarting: 'Redémarrage', + appStopped: 'Arrêtée', + appOff: "Arrêtée · ne redémarre que si vous la lancez", + appCrashed: 'Plantée avec le code de sortie {code}', + appCrashedSignal: 'Plantée', + appUnavailable: 'Indisponible', + appStart: 'Lancer', + appStop: 'Arrêter', + appRestart: 'Redémarrer', + startWithBoite: 'Démarrer avec Boite', + gateway: 'Passerelle', + gatewayRunning: '{version} sur 127.0.0.1:{port}', + gatewayStarting: 'Démarrage', + gatewayUpdating: 'Mise à jour', + gatewayCrashed: 'Plantée', + gatewayData: 'Ses connexions et sa base sont dans {dir}, et y restent si on la désinstalle.', + removeGatewayBody: 'Supprime le programme du dossier de données de Boite. Ses connexions, ses réglages et sa base restent dans {dir}, prêts pour la prochaine installation.', + previewGateway: 'Passerelle', + previewGatewayBody: 'Tourne en arrière-plan sur cette machine, sur un port local, sans accès à Boite. Le proxy d\'abonnements peut pointer dessus.', + trust: "Ce programme tourne avec vos permissions sur la machine qui héberge votre cœur. Ne l'installez que si vous faites confiance à qui le publie.", + confirmAdd: 'Installer {name}', + refusedTitle: 'Boite a refusé cette extension', + refusedFile: 'Fichier', + refusedField: 'Champ', + refusedExpected: 'Attendu', + checkUpdates: 'Rechercher des mises à jour', + checkingUpdates: 'Recherche de mises à jour', + upToDate: 'À jour · vérifiée {time}', + updateFound: 'Trouvée sur {source}, {ref} au commit {commit} · vérifiée {time}', + updateIgnored: 'Pas proposée : {reason}', + updateFailed: 'Impossible de rechercher les mises à jour : {error}. Boite réessaiera plus tard.', + noticeTitle: '{name} {version} est disponible', + noticeBody: 'Son téléchargement est vérifié contre le SHA-256 publié par sa version.', + noticeUpdate: 'Mettre à jour', + noticeLater: 'Plus tard', + noticeLabel: "Mise à jour d'extension", + } +}; diff --git a/packages/ui/src/lib/strings/fr/previewComments.ts b/packages/ui/src/lib/strings/fr/previewComments.ts new file mode 100644 index 000000000..3e33db107 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/previewComments.ts @@ -0,0 +1,19 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + previewComments: { + annotate: 'Référencer un élément', + picking: 'Sélectionnez un élément dans la page. Échap annule.', + add: 'Ajouter au message', + added: 'Élément ajouté à votre message.', + unavailable: 'Cet élément est inaccessible. Utilisez l’application de bureau pour les pages d’une autre origine.', + failed: 'La sélection a échoué. Rechargez la page et réessayez.', + reveal: 'Montrer cet élément dans l’aperçu', + remove: 'Retirer la référence à cet élément', + stale: 'Cet aperçu a changé de page. Rouvrez la page d’origine ou sélectionnez à nouveau l’élément.', + missing: 'Cet élément n’existe plus dans la page. Sélectionnez-le à nouveau.', + tooMany: 'Un message peut référencer au maximum 8 éléments de page.', + activityUnsupported: 'Les références ne sont pas disponibles avec /goal ou /loop. Envoyez un message ordinaire ou retirez les références.', + stashUnsupported: 'Ce brouillon contient des références et ne peut pas être mis de côté. Envoyez-le ou retirez les références.' +} +}; diff --git a/packages/ui/src/lib/strings/fr/protection.ts b/packages/ui/src/lib/strings/fr/protection.ts new file mode 100644 index 000000000..f95882c7e --- /dev/null +++ b/packages/ui/src/lib/strings/fr/protection.ts @@ -0,0 +1,11 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + protection: { + quiet: 'Tranquillité', + limits: 'Limites de ressources', + tasks: 'Gestionnaire de tâches', + windows: "La mise au second plan, le son et les limites de ressources s'appliquent sur un hôte Windows.", + } +}; diff --git a/packages/ui/src/lib/strings/fr/providerSettings.ts b/packages/ui/src/lib/strings/fr/providerSettings.ts new file mode 100644 index 000000000..70f243d06 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/providerSettings.ts @@ -0,0 +1,53 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + providerSettings: { + heading: 'Fournisseurs', + intro: 'Un agent déjà connecté sur cet ordinateur est repris tout seul.', + step: { + installing: 'Installation', + install: 'Pas installé', + repair: 'Des fichiers installés manquent', + manual: 'Pas installé', + 'signing-in': 'Connexion', + 'sign-in': 'Pas connecté', + external: 'Pas connecté', + ready: 'Prêt' + }, + accountsCount: '{count} comptes', + thenSignIn: 'La connexion démarre à la fin du téléchargement.', + manualHint: '{provider} vient avec son propre installateur. Lancez-le, puis revenez : Boite le trouve tout seul.', + externalHint: '{provider} se connecte depuis sa propre application. Connectez-vous là-bas, puis vérifiez à nouveau.', + setup: "Instructions d'installation", + refresh: 'Vérifier à nouveau', + details: 'Détails', + isolated: 'Compte Boite', + default: 'Votre login en ligne de commande', + reconnect: 'Se reconnecter', + check: 'Vérifier la connexion', + connectionOk: 'Connecté', + checking: 'Vérification', + accountName: 'Nom du compte', + revealEmail: 'Afficher le mail', + deviceHint: 'Saisissez ce code sur la page de connexion. Boite attend la fin de la connexion.', + addAccount: 'Ajouter un autre compte', + useCli: 'Utiliser mon login en ligne de commande', + executable: 'Lancé depuis', + accounts: 'Comptes', + installation: 'Installation', + managedCopy: 'Copie de Boite', + addHeading: 'Ajouter un fournisseur', + offHeading: 'Désactivés', + off: 'Désactivé', + experimental: 'Expérimental', + /** The switch's accessible name: `{provider}` is the provider's own name. */ + enable: 'Utiliser {provider}', + enableHint: "Désactivé, Boite ne lance rien de ce fournisseur et aucune liste ne le propose. Ses comptes et ses conversations sont conservés.", + /** A thread whose provider is off: the composer's chip and the sentence behind it. */ + threadOff: '{provider} est désactivé. Activez-le dans Réglages > Fournisseurs pour continuer cette conversation.', + defaultModel: 'Modèle par défaut', + autoUpdate: 'Mises à jour automatiques', + checkUpdates: 'Rechercher les mises à jour', + updating: 'Mise à jour en cours' + } +}; diff --git a/packages/ui/src/lib/strings/fr/quotas.ts b/packages/ui/src/lib/strings/fr/quotas.ts new file mode 100644 index 000000000..c45a40ed1 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/quotas.ts @@ -0,0 +1,68 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + quotas: { + machineAccounts: 'Limites des comptes de {machine}', + outdated: 'Mettez à jour Boite sur cette machine pour voir ses limites sur cet appareil.', + usingCredits: 'Crédits utilisés', + reorder: 'Réordonner {name}. Glissez ou utilisez les flèches du clavier.', + bankedReset: '{count} reset en réserve', + observed: 'Relevé du {time}', + remainingUnder: 'Moins de {percent} % restants', + bankedResets: '{count} resets en réserve', + resetExpires: 'Prochain reset expirant {time}', + useReset: 'Utiliser un reset', + resetUsing: 'Reset en cours', + resetConfirmTitle: 'Utiliser un reset pour {account} ?', + resetConfirmBody: 'Cette action consomme le reset disponible le plus proche de son expiration sur ce compte {provider} et réinitialise ses limites actuelles. Elle est irréversible.', + resetConfirm: 'Utiliser un reset', + resetApplied: 'Reset appliqué. Limites actualisées.', + resetNothing: "Aucune limite à réinitialiser. Aucun reset n'a été consommé.", + resetNoCredit: "Aucun reset en réserve n'est disponible pour ce compte.", + resetAlreadyUsed: 'Ce reset a déjà été utilisé. Limites actualisées.', + resetRefreshFailed: "Reset appliqué, mais les nouvelles limites n'ont pas pu être lues. Actualisez pour vérifier.", + resetFailed: "Impossible d'utiliser le reset : {error}", + budgetRemaining: 'Budget mensuel restant', + budgetCompact: 'Budget mensuel', + creditCompact: 'Crédits', + windowWeeklyCompact: 'Semaine', + windowMonthlyCompact: 'Mois', + creditRemaining: 'Crédits restants', + creditBalance: '{count} crédits', + + trayShow: 'Afficher', + trayQuit: 'Quitter', + names: { muse: 'Muse Code', claude: 'Claude', codex: 'Codex', antigravity: 'Antigravity', grok: 'Grok', opencode: 'OpenCode Go' }, + noReading: 'Indisponible', + cliSource: 'connexion en ligne de commande agy', + connect: 'Connecter un compte', + noReset: 'Heure de réinitialisation non communiquée', + heading: 'Quotas des fournisseurs', + intro: "Limites d'abonnement communiquées par vos fournisseurs. C'est distinct des jetons dépensés dans Boite.", + remaining: '{percent} % restants', + resets: 'Repart {time}', + windowHours: '{hours} heures', + windowWeekly: 'Hebdomadaire', + windowMonthly: 'Mensuelle', + windowCredits: 'Crédits', + checked: 'Mis à jour {time}', + stale: 'Dernière lecture réussie', + empty: "Connectez un fournisseur pour voir ses limites d'abonnement.", + unsupported: "Ce fournisseur n'expose pas ses quotas d'abonnement ici.", + disabled: 'Le suivi des quotas est désactivé pour ce compte.', + loading: 'Lecture des limites du fournisseur', + slowHint: "Antigravity peut mettre jusqu'à deux minutes à répondre.", + tracked: 'Comptes suivis', + trackedHint: 'Boite n\'interroge que les comptes activés ici.', + autoResume: 'Reprendre les conversations après une limite d\'usage', + autoResumeHint: 'Une conversation arrêtée par une limite d\'usage reprend seule une minute après la réinitialisation, une fois par fenêtre. Stop, un nouveau message ou un autre modèle l\'annule.', + providers: 'Gérer les fournisseurs', + unavailable: 'Aucune lecture de quota disponible.', + refresh: 'Actualiser les quotas', + glance: 'Limites des comptes', + allLimits: 'Page des limites', + readFailed: 'Lecture des limites impossible : {error}', + quit: 'Quitter Boite', + } +}; diff --git a/packages/ui/src/lib/strings/fr/remoteBrowser.ts b/packages/ui/src/lib/strings/fr/remoteBrowser.ts new file mode 100644 index 000000000..46380aefd --- /dev/null +++ b/packages/ui/src/lib/strings/fr/remoteBrowser.ts @@ -0,0 +1,13 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + remoteBrowser: { + display: 'Affichage', resolution: 'Résolution de la page', fitPhone: 'Adapter à cet écran', phone: 'Téléphone', tablet: 'Tablette', rotate: 'Pivoter', width: 'Largeur', height: 'Hauteur', apply: 'Appliquer', sharedSize: 'La résolution change aussi la page que voit l’agent.', restoreSize: 'Revenir à la taille par défaut', previewZoom: 'Zoom de l’aperçu (cet appareil seulement)', fit: 'Ajuster', panHint: 'Glissez pour déplacer l’aperçu. Utilisez les flèches pour défiler dans la page.', + address: 'Adresse', addressPlaceholder: 'Saisir une adresse', back: 'Précédent', forward: 'Suivant', reload: 'Recharger', badAddress: 'Saisissez une adresse web (http ou https).', + title: 'Navigateur de l’agent', waiting: 'En attente de la page', paused: 'En pause', reconnecting: 'Reconnexion', pause: 'Pause', resume: 'Reprendre', + interact: 'Toucher ou faire défiler la page partagée', interactDesk: 'La page partagée : cliquez dessus, puis écrivez', + deskHint: 'Cliquez dans la page, puis écrivez. Glissez pour sélectionner, double-cliquez un mot, défilez à la molette. Copier, couper et coller passent par le clavier.', + copy: 'Copier le texte sélectionné', copied: 'Copié.', nothingSelected: 'Rien n’est sélectionné dans la page. Touchez deux fois un mot d’abord.', copyCut: 'La sélection était trop longue : son début a été copié.', image: 'Vue en direct du navigateur de l’agent', scrollUp: 'Défiler vers le haut', scrollDown: 'Défiler vers le bas', + text: 'Touchez un champ de la page, puis écrivez ici', send: 'Envoyer le texte à la page', gesture: 'Touchez pour cliquer. Glissez pour défiler. Retour envoie le texte puis Entrée ; ⌫ sur un champ vide efface dans la page.' + } +}; diff --git a/packages/ui/src/lib/strings/fr/resident.ts b/packages/ui/src/lib/strings/fr/resident.ts new file mode 100644 index 000000000..29ae21c12 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/resident.ts @@ -0,0 +1,7 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + resident: { + lookedAfterBy: 'Suivi par {name}' + } +}; diff --git a/packages/ui/src/lib/strings/fr/resources.ts b/packages/ui/src/lib/strings/fr/resources.ts new file mode 100644 index 000000000..aefa5ec44 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/resources.ts @@ -0,0 +1,32 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + resources: { + memory: 'Mémoire', + resolved: (mb: number) => `${Math.round(mb)} Mo`, + auto: (mb: number) => `Auto : ${Math.round(mb)} Mo`, + agents: 'Agents / budget', + available: 'Disponible / réserve', + unknown: 'Indisponible', + state: 'État de la protection', + states: { ok: 'Mémoire suffisante', critical: 'Limite de mémoire dépassée' }, + killTitle: 'Processus arrêté pour libérer de la mémoire', + killCount: (count: number) => `${count} processus arrêtés pour libérer de la mémoire`, + stoppedProcesses: (count: number) => `Voir les ${count} processus arrêtés`, + memorySettings: 'Réglages mémoire', + protectionOff: 'Protection mémoire désactivée', + killReason: { + 'thread-quota': (limit: string) => `Cette conversation a dépassé sa limite mémoire de ${limit}.`, + budget: (limit: string) => `Les agents ont dépassé leur budget mémoire commun de ${limit}.`, + machine: (limit: string) => `La mémoire disponible est passée sous la réserve de ${limit}.`, + }, + threadCap: 'Cette conversation a atteint sa limite de mémoire. Boite a refusé de lui en accorder davantage.', + budget: "Les agents ont atteint leur budget mémoire commun. Boite a refusé de leur en accorder davantage.", + throttled: (limit: string) => `Les processus de cette conversation sont ralentis par leur limite mémoire de ${limit}. Ils se figent au lieu d'échouer.`, + empty: 'Rien ne tourne en ce moment.', + processes: 'processus', + killTree: "Tuer l'arborescence", + killConfirm: 'Tuer tous les processus de cette conversation ?', + killConfirmYes: 'Tuer', + } +}; diff --git a/packages/ui/src/lib/strings/fr/rightPanel.ts b/packages/ui/src/lib/strings/fr/rightPanel.ts new file mode 100644 index 000000000..0062fbbcf --- /dev/null +++ b/packages/ui/src/lib/strings/fr/rightPanel.ts @@ -0,0 +1,27 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + rightPanel: { + resize: 'Redimensionner le panneau, double-clic pour réinitialiser', + maximize: 'Agrandir le panneau', + restore: 'Restaurer le panneau', + newSurface: 'Ouvrir une surface', + scrollLeft: 'Faire défiler les onglets vers la gauche', + scrollRight: 'Faire défiler les onglets vers la droite', + closeTab: 'Fermer {title}', + closeOthers: 'Fermer les autres', + closeToRight: 'Fermer à droite', + closeAll: 'Tout fermer', + trace: 'Trace', + browser: 'Navigateur', + changes: 'Modifications', + files: 'Fichiers', + file: 'Fichier', + tasks: 'Tâches', + device: 'Appareil', + ownerOnly: "Seulement dans l'application du propriétaire", + launcher: 'Ouvrir une surface dans ce panneau', + untitled: 'Navigateur' + } +}; diff --git a/packages/ui/src/lib/strings/fr/serverUpdate.ts b/packages/ui/src/lib/strings/fr/serverUpdate.ts new file mode 100644 index 000000000..51efc0ad0 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/serverUpdate.ts @@ -0,0 +1,26 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + serverUpdate: { + heading: 'Serveur', + updates: 'Mises à jour', + current: 'À jour', + waiting: 'En attente de la fin du travail', + installing: 'Redémarrage du serveur', + reconnecting: 'Reconnexion', + progress: 'Téléchargement de la mise à jour du serveur', + release: 'Nouveautés', + details: 'Détails', + timing: 'Les agents terminent leur outil en cours, 30 secondes au plus, puis le serveur se met à jour, redémarre et les reprend. Une mise à jour d’agent en cours se termine d’abord.', + backup: 'Conversations et réglages sauvegardés. Si le démarrage échoue, la version précédente est restaurée.', + reconnect: 'Les appareils connectés se reconnectent automatiquement.', + confirmTitle: 'Mettre à jour {machine} ?', + confirmBody: 'Les agents terminent leur outil en cours, 30 secondes au plus. La version {version} s’installe ensuite et ils reprennent après le redémarrage. Vos conversations sont sauvegardées.', + manualTitle: 'Mise à jour manuelle', + docker: 'Après la fin des agents, exécutez ceci dans le dossier Compose :', + manual: 'Consultez le guide pour mettre à jour cette installation.', + legacy: 'Une première mise à jour manuelle est nécessaire pour activer ce bouton.', + guide: 'Guide de mise à jour' + } +}; diff --git a/packages/ui/src/lib/strings/fr/settingRequest.ts b/packages/ui/src/lib/strings/fr/settingRequest.ts new file mode 100644 index 000000000..9e1167f65 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/settingRequest.ts @@ -0,0 +1,64 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + settingRequest: { + heading: 'Réglage suggéré', + headingOpen: 'À voir dans les Réglages', + headingSensitive: 'Réglage sensible', + apply: 'Appliquer', + show: 'Voir dans Réglages', + open: 'Ouvrir les Réglages', + refuse: 'Refuser', + dismiss: 'Ignorer', + sensitiveHint: "Celui-ci se change depuis les Réglages, à côté de son explication. La carte se ferme une fois le réglage changé.", + guestHint: "Seul l'ordinateur qui fait tourner Boite peut changer ce réglage.", + elsewhere: "C'est dans les Réglages de l'ordinateur qui fait tourner Boite.", + loadFailed: "Cette carte de réglage n'a pas chargé.", + to: 'vers', + applied: 'Appliqué', + changed: 'Réglé sur {value} à la place', + refused: 'Refusé', + opened: 'Réglages ouverts', + dismissed: 'Ignoré', + closed: 'Fermé sans réponse', + on: 'Activé', + off: 'Désactivé', + none: 'Aucun', + worktreeProject: 'Dans chaque projet', + worktreeShared: 'Dans {dir}', + compactAt: 'À partir de {tokens} tokens', + compactAny: 'Quelle que soit la taille', + keys: { + asyncQuestions: 'Laisser les agents demander sans s\'arrêter', + shortAnswers: 'Réponses courtes', + toolSummaries: 'Résumés des outils (Claude)', + titleModel: 'Modèle des titres', + threadDoneRetentionDays: 'Supprimer les conversations terminées après', + threadDeletionRetentionDays: 'Purger les conversations supprimées après', + worktreeStorage: 'Emplacement des nouveaux worktrees', + focusGuard: 'Garder les fenêtres des agents en arrière-plan', + muteAgents: 'Couper le son des agents', + agentCpuCapPercent: 'Plafond CPU de tous les agents', + agentMemoryBudgetPercent: 'Budget mémoire de tous les agents', + threadMemoryCapMb: 'Plafond mémoire par conversation', + memoryReserveMb: 'Mémoire réservée aux autres applications', + memoryProtection: 'Protéger la mémoire disponible', + reapOrphans: 'Arrêter ce que les agents laissent tourner', + autoUpdateHarnesses: 'Mettre à jour les agents automatiquement', + listenOnLan: 'Accessible sur le réseau local', + publicUrl: 'Adresse publique pour les téléphones', + browserOrigins: 'Origines de navigateur autorisées', + warmProcessMinutes: 'Garder un agent chaud après son tour', + autoCompact: 'Compaction automatique', + autoResumeLimitedThreads: 'Reprendre les conversations après une limite d\'usage', + }, + zero: { + agentCpuCapPercent: 'Sans plafond', + threadMemoryCapMb: 'Automatique', + memoryReserveMb: 'Automatique', + threadDoneRetentionDays: 'Jamais', + threadDeletionRetentionDays: 'Jamais', + warmProcessMinutes: 'Libéré tout de suite', + }, + } +}; diff --git a/packages/ui/src/lib/strings/fr/settings.ts b/packages/ui/src/lib/strings/fr/settings.ts new file mode 100644 index 000000000..33ad394ec --- /dev/null +++ b/packages/ui/src/lib/strings/fr/settings.ts @@ -0,0 +1,236 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + settings: { + heading: 'Réglages', + modelDefaults: 'Modèles par défaut', + modelDefaultsHint: 'Pour les nouvelles conversations. Enregistré sur cet appareil.', + modelDefaultUnavailable: "{provider} ne propose pas le modèle par défaut {model} sur ce compte. Choisissez un modèle disponible dans le sélecteur, ou changez le défaut dans les réglages.", + tabs: { + home: 'Accueil', + general: 'Général', + machines: 'Machines et mises à jour', + advanced: 'Avancé', + appearance: 'Apparence', + keyboard: 'Clavier', + accounts: 'Fournisseurs', + plugins: 'Extensions', + usage: 'Consommation', + resources: 'Protection', + experiments: 'Expérimentations', + diagnostics: 'Diagnostic' + }, + back: 'Retour aux conversations', + search: 'Rechercher un réglage', + searchEmpty: 'Aucun réglage ne correspond', + homeGroups: ['Application', 'Agents', 'Consommation et limites', 'Système'], + providersReady: '{count} connectés', + providersOne: '1 connecté', + machinesCount: '{count} machines', + devicesCount: '{count} appareils appairés', + devicesOne: '1 appareil appairé', + connectProvider: 'Connecter un fournisseur', + conversations: 'Conversations', + groupWorkingThreads: 'Regrouper les conversations en cours', + groupWorkingThreadsHint: 'Replie les conversations en cours et le travail en arrière-plan dans Projets et Récent. Les épingles, brouillons, erreurs et demandes de réponse restent visibles. Cet appareil seulement.', + groupOtherProjects: 'Regrouper les projets inactifs', + groupOtherProjectsHint: 'Replie les projets sans conversation visible ni brouillon dans Projets inactifs, dans la vue Projets. Désactive pour garder tous les projets dans la liste. Cet appareil seulement.', + app: 'Application', + tourReplay: 'Revoir la présentation', + execution: 'Exécution des agents', + warmProcessMinutesHint: 'Durée pendant laquelle un agent reste chargé après un tour. 0 l\'arrête aussitôt.', + autoCompact: 'Compaction automatique', + autoCompactHint: "Boite demande à l'agent de compacter entre deux tours, jamais pendant. Chaque compaction compte dans l'usage.", + autoCompactWhen: 'Quand', + autoCompactCondition: 'Condition', + autoCompactRecommended: 'Recommandé', + autoCompactCacheExpiry: "Avant l'expiration du cache de prompt", + autoCompactCacheExpiryHint: "Une minute avant que le fournisseur n'oublie la conversation en cache, si rien ne s'est passé depuis. Lue au tarif du cache, et le message suivant part de la version courte.", + autoCompactBackground: "Pendant que l'agent attend un travail en arrière-plan", + autoCompactBackgroundHint: "Après une réponse qui a laissé une commande ou une surveillance en cours. La compaction occupe l'attente.", + autoCompactTurnEnd: "Dès que l'agent a terminé", + autoCompactTurnEndHint: "Deux secondes après une réponse qui n'a rien laissé en cours. Un message envoyé juste après attend la compaction.", + autoCompactThreshold: "Seulement au-dessus d'une taille", + autoCompactThresholdHint: "Les conversations plus petites sont laissées tranquilles, comme un modèle qui n'arrive jamais jusque-là.", + autoCompactThresholdOff: 'Désactivé : chaque conversation est compactée aux moments ci-dessus, quelle que soit sa taille.', + autoCompactUnit: 'jetons', + projects: 'Projets', + language: 'Langue', + languageSystem: 'Système', + languageNames: { en: 'English', fr: 'Français' }, + theme: 'Thème', + display: 'Affichage', + accent: "Couleur d'accent", + accentNames: ['Bleu', 'Violet', 'Rose', 'Orange', 'Ambre', 'Vert', 'Turquoise'], + colors: { + title: 'Couleurs', + hint: 'Les modes clair et sombre gardent leurs propres couleurs. Enregistré sur cet appareil.', + palette: 'Palette de couleurs', + palettes: { default: 'Origine', oled: 'OLED', catppuccin: 'Catppuccin', 'tokyo-night': 'Tokyo Night', nord: 'Nord', gruvbox: 'Gruvbox', amethyst: 'Amethyst' }, + customize: 'Personnaliser les couleurs', + roles: { background: 'Fond', frame: 'Barre latérale et cadre', surface: 'Panneaux et messages', foreground: 'Texte', accent: 'Accent' }, + edit: 'Choisir une couleur à modifier', + reset: 'Réinitialiser', + undo: 'Annuler', + field: '{color} : saturation et luminosité', + coordinates: 'Saturation {saturation} %, luminosité {brightness} %', + hue: 'Teinte', + hex: 'Hex', + invalidHex: 'Saisissez une couleur hexadécimale comme #7AA2F7.', + contrast: 'Contraste du texte', + contrastHint: 'Le contraste le plus faible du texte sur le fond, le cadre et les panneaux. La cible est de 4,5:1.', + contrastLow: 'Contraste faible du texte {ratio}:1', + improveContrast: 'Améliorer le contraste' + }, + themeSystem: 'Système', + themeDark: 'Sombre', + themeLight: 'Clair', + themeGrain: 'Grain', + reading: 'Lecture', + font: 'Police du texte', + fontSystem: 'Système', + fontSample: 'Relance les 12 tests en échec dans Il1/O0/rn.ts', + monoFont: 'Police du code', + monoSystem: 'Système', + zoom: "Taille de l'interface", + zoomHint: 'Ctrl+= et Ctrl+- partout dans l\'appli, Ctrl+0 pour revenir à 100 %.', + zoomOut: 'Plus petit', + zoomIn: 'Plus grand', + zoomReset: 'Revenir à 100 %', + zoomFailed: "Le zoom n'a pas changé : {error}", + experiments: { + intro: 'Ce n\'est pas fini et peut changer ou disparaître.' + }, + cacheKeepalive: 'Garder le cache de prompt au chaud', + cacheKeepaliveHint: "Pendant qu'une conversation attend ses agents ou ses workflows, Boite envoie un tour d'un mot peu avant que le fournisseur oublie le prompt en cache, pour que leur résultat soit lu au tarif du cache. Chaque relance compte dans l'usage. Une relance qui rate le cache met fin aux relances.", + cacheKeepaliveHours: 'Pendant au plus', + cacheKeepaliveHoursHint: "Compté depuis la fin du dernier tour de la conversation. Les relances s'arrêtent aussi quand les agents ont fini, quand vous écrivez ou arrêtez, et après autant de relances que coûterait un cache raté.", + cacheKeepaliveUnit: 'heures', + material: 'Matériau de la fenêtre', + workspace: 'Espace de travail', + startIn: "L'appli s'ouvre sur", + startDrafts: 'Brouillons', + startProject: 'Le dernier projet', + panelStart: "Le panneau latéral s'ouvre sur", + panelLauncher: 'Son menu', + panelFiles: 'Fichiers', + panelChanges: 'Modifications', + materialAcrylic: 'Acrylique', + materialMica: 'Mica', + materialSolid: 'Opaque', + chatWidth: 'Largeur du chat', + groupChanges: 'Regrouper les fichiers modifiés dans la conversation', + expandDiffs: 'Ouvrir les aperçus des changements par défaut', + chatWidthComfortable: 'Confortable', + chatWidthWide: 'Large', + chatWidthFull: 'Pleine', + terminalCursor: 'Curseur du terminal', + terminalCursorBar: 'Barre', + terminalCursorBlock: 'Bloc', + terminalCursorUnderline: 'Soulignement', + terminalGpuHint: "Défilement et affichage plus fluides. Désactivez-le si un terminal s'affiche mal sur cet appareil ; le changement vaut pour les terminaux ouverts ensuite.", + terminalGpu: 'Dessiner les terminaux avec le GPU', + connection: 'Connexion', + localCore: "Cette application tourne sur le cœur qu'elle a lancé sur cet ordinateur.", + pair: 'Appairer', + manual: 'URL du cœur et jeton', + coreUrl: 'URL du cœur', + token: 'Jeton', + connect: 'Connecter', + warmProcessMinutes: 'Minutes de processus gardé chaud', + listenOnLan: 'Accessible sur le réseau local', + listenOnLanHint: 'Permet à un téléphone du même réseau d\'atteindre cet ordinateur, à partir du prochain démarrage de Boite. Une option --host ou --lan l\'emporte.', + agentCpuCapPercent: 'Plafond CPU, %', + agentCpuCapHint: 'Pour tous les agents ensemble. 0 signifie aucun plafond. Windows uniquement.', + threadMemoryCapMb: 'Plafond mémoire par conversation, Mo', + threadMemoryCapHint: 'Pour une conversation et ses processus. 0 prend la moitié du budget des agents, qu\'il ne peut pas dépasser.', + agentMemoryBudgetPercent: 'Budget mémoire de tous les agents, %', + agentMemoryBudgetHint: 'Pour tous les agents ensemble, de 10 à 90 %. 60 % par défaut.', + memoryReserveMb: 'Mémoire réservée aux autres applications, Mo', + memoryReserveHint: 'Gardée libre pour vos autres applications. 0 garde 10 % de la RAM, au moins 3072 Mo. En dessous de 12288 Mo de RAM, il garde 25 %.', + memoryProtection: 'Protéger la mémoire disponible', + memoryProtectionHint: 'Au-delà d\'une limite, Boite arrête la plus grosse arborescence de processus lancée par un agent, jamais l\'agent lui-même. Désactivé, les plafonds mémoire Windows sautent aussi.', + background: 'Arrière-plan', + closeToTray: 'Garder Boite en marche quand la fenêtre se ferme', + closeToTrayHint: 'Réduit Boite dans la zone de notification et laisse les tours continuer. Quitter arrête toujours Boite.', + launchAtLogin: "Lancer Boite à l'ouverture de session", + launchAtLoginHint: "Boite démarre dans la zone de notification avec son cœur actif : cet ordinateur peut faire tourner des agents et répondre à vos autres appareils sans ouvrir sa fenêtre. S'applique à ce compte utilisateur.", + notifications: "Me prévenir quand une conversation se termine ou pose une question", + notificationsHint: 'Seulement pour une conversation que vous ne regardez pas. Cette machine seulement.', + notificationsDenied: 'Les notifications sont bloquées pour Boite sur ce système ; autorisez-les dans ses réglages.', + focusGuard: 'Garder les fenêtres des agents en arrière-plan', + muteAgents: 'Couper le son des agents', + reapOrphans: 'Arrêter ce que les agents laissent tourner', + reapOrphansHint: 'Dix secondes après un tour, un processus dont le parent est sorti est arrêté. Windows uniquement.', + asyncQuestions: 'Laisser les agents poser des questions sans s’arrêter', + asyncQuestionsHint: 'Apprend la commande boite ask aux agents au début d\'une session. Codex pose déjà ses questions ainsi.', + shortAnswers: 'Réponses courtes', + shortAnswersHint: 'Demande à chaque agent, à chaque message, le résultat d\'abord en quelques phrases simples, sans préambule, récapitulatif ni commentaire en cours de route. Les faits, les chiffres et les commandes restent.', + toolSummaries: 'Résumés des outils (Claude)', + toolSummariesHint: 'Claude décrit chaque série d\'appels d\'outils en une ligne au lieu de les compter. Chaque ligne est une requête à son petit modèle, Haiku, facturée au compte de la conversation. S\'applique au démarrage d\'un processus Claude.', + titleModel: 'Modèle des titres', + titleModelHint: 'Nomme une conversation après sa première réponse. Automatique prend l\'agent de la conversation sur son petit modèle.', + titleModelAuto: 'Automatique', + titleModelAutoHint: 'L’agent de la conversation, petit modèle', + titleModelSmall: 'petit', + numberRange: 'Un nombre entier de {min} à {max}.', + saved: 'Enregistré', + core: 'Cœur', + version: 'Version', + protocol: 'Protocole', + os: 'Système', + pid: 'PID', + dataDir: 'Répertoire de données', + endpoint: 'Point de connexion', + noCore: 'Pas connecté à un cœur.', + archived: { + doneRetentionLabel: 'Supprimer les conversations terminées après (jours)', + doneRetentionHint: 'À compter du passage en terminé ou de l\'archivage automatique après une fusion de PR. 3 jours par défaut ; 0 désactive la suppression. Les archives manuelles sont conservées. Les conversations supprimées restent récupérables pendant le délai configuré.', + deletesAt: 'Suppression : {date}', + heading: 'Conversations archivées', + mergedReason: 'Archivée après la fusion de la PR #{number}', + intro: 'L\'agent d\'une conversation archivée s\'arrête. Il repart au premier message après une restauration.', + show: 'Afficher les conversations archivées', + empty: 'Aucune conversation archivée sur cette machine.', + restore: 'Restaurer', + }, + deleted: { + heading: 'Conversations supprimées', + retentionLabel: 'Jours avant la suppression définitive', + retentionHint: 'À compter de la suppression. 0 conserve les conversations sans limite. Réduire le délai concerne aussi les conversations déjà supprimées.', + retentionError: 'Saisissez un nombre entier entre 0 et 3650 jours.', + purgesAt: 'Suppression définitive : {date}', + show: 'Afficher les conversations supprimées', + empty: 'Aucune conversation supprimée à restaurer sur cette machine.', + }, + worktrees: { + storage: 'Emplacement de stockage', + storageProject: 'Dans chaque projet', + storageShared: 'Un dossier commun', + storageProjectHint: 'Dans chaque projet, sous', + storageDirectory: 'Dossier sur cette machine', + storagePlaceholder: 'Chemin absolu du dossier', + storageBrowse: 'Choisir un dossier', + storageSharedHint: 'Chaque projet a son sous-dossier. Les projets de même nom restent séparés.', + storageNewOnly: 'Nouveaux worktrees seulement. Les autres restent où ils sont.', + storageInvalid: 'Saisissez un chemin absolu sur la machine qui exécute vos agents.', + storageSaveFailed: 'Impossible d\'enregistrer cet emplacement. Vérifiez le chemin et la connexion, puis réessayez.', + heading: 'Worktrees', + intro: 'Un worktree reste sur le disque une fois sa conversation archivée. En supprimer un efface le dossier et sa branche.', + show: 'Afficher les worktrees', + noProject: 'Aucun projet git sur cette machine.', + empty: 'Plus aucun worktree.', + sweep: 'Supprimer les propres ({count})', + remove: 'Supprimer', + dirty: 'modifications non commitées', + unmerged: 'commits sur aucune autre branche', + missing: 'dossier disparu', + heldBy: 'utilisé par « {title} »', + archivedThread: 'conversation archivée : « {title} »', + heldHint: 'Une conversation travaille ici : archivez-la d’abord', + forceTitle: 'Supprimer {name} quand même ?', + forceBody: 'Il contient des {reasons}. Le supprimer efface le dossier et sa branche, et ce travail est perdu.' + }, + } +}; diff --git a/packages/ui/src/lib/strings/fr/sidebar.ts b/packages/ui/src/lib/strings/fr/sidebar.ts new file mode 100644 index 000000000..c26269826 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/sidebar.ts @@ -0,0 +1,144 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + sidebar: { + doneThreads: 'Terminés', + archivedGroup: 'Archivés', + workingThreads: 'En cours', + projectWorkingThreads: '{project} : {count} en cours', + projectDoneThreads: '{project} : {count} terminées', + projectArchivedThreads: '{project} : {count} archivées', + idleProjects: 'Projets inactifs', + otherProjects: 'Autres projets', + archivedProject: 'Archivé', + groupingOptions: "Options d'affichage", + surface: 'Conversations ou agents', + threads: 'Conversations', + agents: 'Agents', + surfaceWorking: '{count} au travail', + surfaceWaiting: '{count} au travail, quelque chose vous attend', + surfaceSubagentsOne: 'dont 1 sous-agent', + surfaceSubagentsMany: 'dont {count} sous-agents', + projectsView: 'Projets', + recentView: 'Récent', + displayView: 'Affichage', + displayOrder: 'Ordre des projets', + displayGroup: 'Regrouper', + groupWorkingRow: 'Conversations en cours', + groupIdleRow: 'Projets inactifs', + customOrderShort: 'Personnel', + autoArchiveMergedPrHint: 'Une conversation en worktree quitte la liste pour Terminés une fois sa branche fusionnée. Son historique reste. S’applique à ce projet.', + autoRemoveWorktreesHint: 'Une heure après que toutes les conversations d’un worktree sont terminées ou archivées, Boite supprime le worktree si rien ne se perd : son dernier commit est sur une autre branche, ou une pull request fusionnée a exactement ce commit. Une branche fusionnée part aussi. Les modifications non commitées restent toujours. S’applique à ce projet.', + noActiveProjects: 'Aucune conversation à traiter.', + markDone: 'Marquer comme terminé', + doneToast: '« {title} » marqué comme terminé', + doneLoadFailed: "Certaines conversations terminées n'ont pas pu être chargées.", + prMerged: '#{number} fusionnée', + prClosed: '#{number} fermée', + prOpen: '#{number} ouverte', + reopenThread: 'Remettre dans Récent', + reopenNamedThread: 'Remettre « {title} » dans Récent', + doneNotice: 'Cette conversation est terminée. Remettez-la dans Récent pour continuer.', + worktreeGone: { + title: 'Worktree supprimé', + merged: 'Boite a supprimé le worktree de cette conversation {when}, après la fusion de #{number}.', + clean: 'Boite a supprimé le worktree de cette conversation {when} : chacun de ses commits était déjà sur une autre branche.', + branchDeleted: 'La branche {branch} a été supprimée.', + branchKept: 'La branche {branch} existe toujours.', + archived: 'Remise dans Récent, cette conversation n’a plus de dossier où travailler.', + live: 'Un nouveau message ici n’a pas de dossier où s’exécuter. Démarrez une nouvelle conversation pour continuer.' + }, + allProjects: 'Tous les projets', + filterProject: 'Filtrer par projet', + recentOrder: 'Activité récente', + customOrder: 'Ordre personnel', + moveProjectUp: 'Monter le projet', + moveProjectDown: 'Descendre le projet', + search: 'Rechercher une conversation', + newThread: 'Nouvelle conversation', + newThreadIn: 'Nouvelle conversation dans {project}', + addProject: 'Ajouter un projet', + noProjects: 'Aucun projet pour le moment.', + noThreads: 'Aucune conversation pour le moment.', + noMatch: 'Rien ne correspond.', + unread: 'Non lu', + collapse: 'Masquer la barre latérale', + expand: 'Afficher la barre latérale', + resize: 'Redimensionner la barre latérale, double-clic pour réinitialiser', + settings: 'Réglages', + projectMenu: 'Actions du projet', + copyPath: 'Copier le chemin', + manageProject: 'Gérer le projet', + worktreeDefault: 'Worktree par défaut', + autoArchiveMergedPr: 'Masquer les conversations des PR mergées', + autoRemoveWorktrees: 'Supprimer les worktrees terminés', + backToProjectMenu: 'Retour', + viewArchivedThreads: 'Voir les conversations archivées', + importSession: 'Importer une session Claude Code', + archiveProject: 'Archiver le projet', + refreshIcon: "Rafraîchir l'icône", + projectArchivedToast: '{project} archivé', + archivedProjects: 'Projets archivés', + restoreProject: 'Restaurer', + restoreAndStart: 'Restaurer {project} et démarrer une conversation', + removeProject: 'Retirer de Boite', + projectMissing: 'Dossier introuvable', + projectMissingBody: "Le dossier {path} n'existe plus : il a été supprimé, déplacé ou renommé. Remettez-le en place pour continuer ici, ou retirez le projet de Boite.", + projectMissingCheck: 'Vérifier à nouveau', + removeProjectTitle: 'Retirer {project} de Boite ?', + removeProjectBody: 'Ses conversations partent avec lui. Les fichiers sur le disque restent où ils sont.', + remove: 'Retirer', + threadMenu: 'Actions de la conversation', + unsentDraft: 'Brouillon non envoyé', + retitle: 'Regénérer le titre', + retitling: "Rédaction d'un titre", + state: { + working: 'En cours', + workingFor: 'Travaille depuis {elapsed}', + waiting: 'Vous attend', + error: 'Échec', + done: 'Terminé', + queued: 'En attente', + delegating: 'Sous-agents', + delegatingOne: 'Attend 1 sous-agent', + delegatingMany: 'Attend {count} sous-agents', + subagentsOne: '1 sous-agent au travail', + subagentsMany: '{count} sous-agents au travail', + subagentsShortOne: '1 agent', + subagentsShortMany: '{count} agents', + subagentsAskingOne: '1 sous-agent vous attend', + subagentsAskingMany: '{count} sous-agents vous attendent', + monitoring: 'Surveille', + monitoringFor: 'Surveille depuis {elapsed}', + background: 'En arrière-plan', + backgroundFor: 'En arrière-plan depuis {elapsed}', + watching: 'Surveille une PR', + watchingMany: 'Surveille {count} PR', + awaiting: 'Attend : {what}', + interrupted: 'Interrompu', + unreachable: 'Injoignable', + unreachableWas: 'Machine injoignable. Dernier état connu : {state}', + interruptedBy: '{what} s’est arrêté sans le réveiller : {reason}', + interruption: { + 'error': 'la tâche a échoué', + 'cancelled': 'la tâche a été annulée', + 'no-longer-reported': 'son processus s’est arrêté', + 'session-ended': 'le processus de l’agent a été arrêté', + 'core-restarted': 'Boite ou le PC a redémarré' + } + }, + rollupOne: '1 conversation : {state}', + rollupMany: '{count} conversations : {state}', + pin: 'Épingler', + unpin: 'Désépingler', + archive: 'Archiver', + deleteUnavailable: 'Mettez à jour Boite sur la machine qui héberge cette conversation pour la supprimer. La conversation a été conservée.', + deletedToast: '« {title} » supprimée. Restauration possible dans les paramètres.', + archivedToast: '« {title} » archivée', + undo: 'Annuler', + undoHint: 'Annuler (Ctrl+Z)', + draft: 'Nouvelle conversation', + deleteDraft: 'Supprimer le brouillon' + } +}; diff --git a/packages/ui/src/lib/strings/fr/slash.ts b/packages/ui/src/lib/strings/fr/slash.ts new file mode 100644 index 000000000..730a9d074 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/slash.ts @@ -0,0 +1,14 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + slash: { + label: 'Commandes', + agent: 'Agent', + app: 'Boite', + empty: 'Aucune commande ne correspond', + model: 'Changer de modèle', + effort: 'Changer le niveau de raisonnement', + mode: 'Changer le mode de permissions' + } +}; diff --git a/packages/ui/src/lib/strings/fr/speech.ts b/packages/ui/src/lib/strings/fr/speech.ts new file mode 100644 index 000000000..4325e7d5c --- /dev/null +++ b/packages/ui/src/lib/strings/fr/speech.ts @@ -0,0 +1,55 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + speech: { + enabled: 'Dictée vocale', + enabledHint: "Activez la dictée sur cet appareil. La désactiver retire le micro de la barre de saisie, même si un modèle est installé.", + microphone: 'Microphone', + microphoneHint: 'Le micro utilisé pour dicter sur cet appareil.', + defaultMicrophone: 'Micro par défaut du système', + microphoneNumber: 'Microphone {number}', + microphoneUnavailable: 'Le micro choisi est indisponible. Choisissez un autre micro.', + microphoneDisconnected: 'Le micro a été débranché. Choisissez un autre micro et réessayez.', + testMicrophone: 'Tester le micro', + stopMicrophoneTest: 'Arrêter le test', + inputLevel: 'Niveau sonore du micro', + microphoneTestHint: 'Parlez pour vérifier le niveau. Ce test reste sur votre appareil.', + heading: 'Voix', description: 'Le micro à côté d\'Envoyer, deux minutes au plus à la fois. Le texte attend dans la zone de saisie jusqu\'à l\'envoi.', + start: 'Dicter', stop: 'Terminer la dictée', listening: 'Écoute', opening: 'Attente du micro', transcribing: 'Transcription', + private: 'Sur ce cœur uniquement', cloud: 'Transcrit par votre fournisseur d\'API', retry: 'Relancer la transcription', discard: 'Supprimer l\'enregistrement', + live: 'Aperçu en direct', previewFailed: 'Aperçu en direct en pause. Terminez pour transcrire tout l\'enregistrement.', + local: 'Local', api: 'API', engine: 'Moteur', + engineHint: 'Local transcrit sur cette machine et l\'audio n\'en sort jamais. API envoie l\'audio à Groq ou OpenRouter avec votre clé.', + streaming: 'Transcription en direct', + whistleLanguages: 'Anglais, français, allemand, espagnol, italien, néerlandais et polonais', + setup: 'La dictée n\'est pas encore configurée sur cette machine. Ouvrez les réglages Voix pour le faire en un clic.', ownerSetup: 'Demandez au propriétaire de configurer la voix sur ce cœur.', + unsupported: 'La voix demande un Boite plus récent sur {machine}', unsupportedHint: 'Cette machine tourne sur une version de Boite sans moteur vocal. Mettez Boite à jour là-bas, puis rouvrez Voix.', + unsupportedDictation: 'La dictée demande un Boite plus récent sur {machine}. Mettez Boite à jour là-bas, puis réessayez.', + notReady: 'Pas encore configurée', ready: 'Prête', + readyLocal: 'Prête, {model} sur cette machine', readyApi: 'Prête, {provider} transcrit', + notSetUp: 'La dictée n\'est pas configurée', needsCli: 'Cette machine a aussi besoin de whisper-cli dans le PATH, ou de son chemin sous Avancé.', + statusKey: 'Ajoutez une clé {provider}', downloadingModel: 'Téléchargement de {model}', + statusFailed: 'Le téléchargement ne s\'est pas terminé', statusBroken: 'Les réglages Voix sont illisibles', repair: 'Enregistrer pour réparer', + setUp: 'Configurer la dictée', + update: 'Mettre à jour le moteur', updateHint: 'Un moteur plus rapide est disponible, 8 Mo.', + https: 'L\'accès au micro demande HTTPS ou localhost. Ouvrez cette machine par son adresse HTTPS.', + linux: 'La dictée n\'est pas encore disponible dans l\'app Linux : sa vue web ne donne pas accès au micro. Ouvrez Boite dans un navigateur pour dicter.', + denied: 'L\'accès au micro a été refusé. Autorisez-le dans les réglages du navigateur ou du système, puis réessayez.', + noMicrophone: 'Aucun micro trouvé. Branchez-en un et réessayez.', microphoneBusy: 'Le micro est indisponible. Vérifiez qu\'une autre application ne l\'utilise pas.', + silence: 'Aucune parole détectée. Réessayez plus près du micro.', failed: 'Enregistrement impossible. Réessayez.', + model: 'Modèle', modelHint: 'Nemotron transcrit en direct dans plusieurs langues. Whistle occupe peu de place et comprend sept langues. Whisper reste disponible pour la compatibilité.', + tiers: { fast: 'Petit téléchargement', balanced: 'Équilibré', accurate: 'Précision' }, + download: 'Télécharger', downloading: 'Téléchargement', remove: 'Supprimer {model}', + link: 'Ajouter depuis un lien', linkPlaceholder: 'https://huggingface.co/.../ggml-model.bin', + linkHint: 'Un lien https vers un fichier ggml de whisper.cpp, comme ceux de huggingface.co/ggerganov/whisper.cpp.', + add: 'Ajouter', + advanced: 'Avancé', executable: 'Exécutable Whisper', modelPath: 'Fichier du modèle', savePaths: 'Enregistrer les chemins', + pathHint: 'Laissez les deux vides pour utiliser les modèles ci-dessus. Un fichier de modèle indiqué ici passe avant la liste.', + provider: 'Fournisseur', providerHint: 'Les deux font tourner Whisper Large v3 Turbo. Les clés restent sur ce cœur.', groq: 'Groq', openrouter: 'OpenRouter', + groqKey: 'Clé Groq', openrouterKey: 'Clé OpenRouter', keySaved: 'Enregistrée', keyEmpty: 'Collez une clé', keyReplace: 'Collez une nouvelle clé pour la remplacer', + removeKey: 'Supprimer', saveKeys: 'Enregistrer les clés', + fallback: 'Passer à l\'autre fournisseur', fallbackHint: 'Quand le premier échoue. Demande les deux clés. Le mode local ne passe jamais par une API.', + language: 'Langue', languageHint: 'Vide, la langue est détectée. Un code à deux lettres comme en ou fr la fixe.', + saved: 'Enregistré', ownerOnly: 'Le propriétaire de cette machine la gère.', + } +}; diff --git a/packages/ui/src/lib/strings/fr/subscriptionProxy.ts b/packages/ui/src/lib/strings/fr/subscriptionProxy.ts new file mode 100644 index 000000000..22595ab7e --- /dev/null +++ b/packages/ui/src/lib/strings/fr/subscriptionProxy.ts @@ -0,0 +1,59 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + subscriptionProxy: { + heading: 'Proxy d\'abonnements', enable: 'Utiliser un proxy d\'abonnements', + hint: 'Claude et Codex utilisent la passerelle de cette machine, Grok aussi derrière Douane, et OpenCode 2 ajoute ses autres modèles aux siens. Limites ouvre son tableau de bord dans Boite. Les autres agents gardent leur configuration.', + baseUrl: 'URL de l\'API', dashboardUrl: 'URL du tableau de bord des limites', key: 'Clé API (facultative)', + keyHint: 'Laissez vide pour garder la clé enregistrée. Utilisez la clé API du proxy, pas sa clé de gestion du tableau de bord.', + clearKey: 'Retirer la clé enregistrée', save: 'Enregistrer le proxy', saved: 'Proxy enregistré', + configure: 'Configurer le proxy', native: 'Voir les limites des comptes', + showDashboard: 'Voir le tableau du proxy', + openGateway: 'Ouvrir {name}', + signInHint: 'Connectez-vous avec Ouvrir {name}, puis rechargez ici. Si la page reste vide, ce tableau peut refuser les vues intégrées.', + mixedContent: "Cette page utilise HTTPS. Configurez une URL de tableau HTTPS pour l'afficher ici, ou ouvrez le tableau HTTP séparément.", + enabledHint: 'Activé, les comptes connectés sur cette machine ne sont plus utilisés pour Claude et Codex, ni pour Grok derrière Douane.', + account: 'Les requêtes passent par {name} ({origin}). Les connexions de ce PC ne sont pas utilisées.', + via: 'via {name}', + quotasFailed: 'Impossible de lire les limites de {name} : {error}', + quotasEmpty: '{name} ne signale encore aucune limite.', + averageOne: '{name} · 1 compte', + average: '{name} · {count} comptes', + entryStatus: { ready: 'Prêt', cooldown: 'En pause', error: 'Erreur', disabled: 'Désactivé' }, + creditOf: '{remaining} sur {limit}', + creditUsed: '{used} utilisés', + where: 'Où tourne la passerelle', + remote: 'Passerelle distante', local: 'Douane sur cette machine', + localHint: 'Le plugin Douane, installé et lancé par Boite depuis Réglages > Plugins. Le proxy pointe tout seul sur son port local, et le suit si le port change.', + localMissing: 'Le plugin Douane n\'est pas installé sur cette machine.', + localStopped: 'Le plugin Douane est arrêté.', + localStarting: 'Le plugin Douane démarre.', + localRunning: 'Douane {version} répond sur 127.0.0.1:{port}.', + openPlugins: 'Ouvrir Plugins', + move: { + open: 'Déplacer mes connexions vers Douane', + openHint: 'Douane reprend les connexions et les clés API que cette machine a déjà : personne ne se reconnecte ni ne recolle de clé.', + title: 'Déplacer les connexions vers Douane', + intro: "Les connexions Claude, Codex et Grok sont déplacées : leurs jetons changent à chaque renouvellement, donc dès que Douane en tient une, il sert cet agent par la passerelle et la copie de cette machine ne marche plus. Désactiver la passerelle plus tard oblige à se reconnecter ici. Les clés API sont copiées telles quelles.", + loading: 'Lecture des connexions de cette machine', + empty: 'Rien ici que Douane puisse reprendre.', + token: 'Jeton de gestion (facultatif)', + tokenHint: "Seulement pour un Douane dont l'API d'administration en demande un. Il reste sur cette machine et n'est plus jamais affiché.", + tokenSaved: 'Un jeton de gestion est enregistré ; laissez vide pour le garder.', + clearToken: 'Retirer le jeton enregistré', + confirm: 'Déplacer {count}', + moving: 'Déplacement', + failed: 'Déplacement impossible : {error}', + login: 'Connexion {provider}', + copilot: 'Connexion GitHub Copilot', + endpoint: 'Clé API {source}', + opencodeGo: 'Clé OpenCode Go', + route: 'servie comme {route}', + outcomes: { moved: 'Déplacée', present: 'Déjà là', refused: 'Refusée', missing: 'Plus ici' }, + reasons: { + signedOut: 'Pas connecté', unreadable: 'Fichier de connexion illisible', noAddress: "Pas d'adresse d'API connue", + otherApi: "Pas une API compatible OpenAI", otherSignIn: 'Douane ne peut pas reprendre cette connexion', routeTaken: 'Route prise par un autre point d\'accès', + }, + }, + } +}; diff --git a/packages/ui/src/lib/strings/fr/taskManager.ts b/packages/ui/src/lib/strings/fr/taskManager.ts new file mode 100644 index 000000000..739d36b0f --- /dev/null +++ b/packages/ui/src/lib/strings/fr/taskManager.ts @@ -0,0 +1,18 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + taskManager: { + title: 'Gestionnaire des tâches', scope: 'Processus actifs sur cette machine, regroupés par agent.', + agents: 'Agents', live: 'Actualisé toutes les 2 secondes', paused: 'Actualisation en pause', disconnected: 'Machine déconnectée', + loading: 'Lecture de la consommation des agents', unsupported: 'Mettez Boite à jour sur cette machine pour voir la consommation des agents.', + loadFailed: 'Le gestionnaire des tâches n’a pas pu se charger. Réessayez.', + search: 'Trouver un agent', noMatches: 'Aucun agent correspondant.', sortBy: 'Trier par consommation', + sort: { memory: 'Plus de mémoire', cpu: 'Plus de CPU', disk: 'Plus d’activité disque', network: 'Plus d’activité réseau' }, + disk: 'Disque', network: 'Réseau', read: 'Lecture', write: 'Écriture', download: 'Réception', upload: 'Envoi', + observed: 'Observé', unavailableHint: 'Aucune mesure disponible pour le moment, ou ce compteur est non pris en charge. Une valeur absente ne signifie pas une consommation nulle.', + partialHint: 'Ce total comprend seulement les agents dont la consommation peut être mesurée.', + diskHint: 'Lectures et écritures sur le stockage observées pendant que cette vue est ouverte. Les processus brefs peuvent échapper à la mesure. Il s’agit d’activité, pas de la taille du dossier.', + tcpHint: 'Réception et envoi TCP observés pendant que cette vue est ouverte. Les connexions brèves et le trafic UDP peuvent échapper à la mesure.', + stop: 'Arrêter les processus', stopConfirm: 'Arrêter tous les processus de cette conversation ?', stopping: 'Arrêt des processus', + } +}; diff --git a/packages/ui/src/lib/strings/fr/tasks.ts b/packages/ui/src/lib/strings/fr/tasks.ts new file mode 100644 index 000000000..acadfb00e --- /dev/null +++ b/packages/ui/src/lib/strings/fr/tasks.ts @@ -0,0 +1,38 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + tasks: { + goalSection: 'Objectif et boucle', + noActivity: "Aucun objectif ni boucle sur cette conversation", + nextRun: 'Prochain passage {time}', + iterations: '{count} itérations', + agentSection: "Tâches de l'agent", + noTasks: "L'agent n'a signalé aucune liste de tâches", + todoSection: 'Todos du projet', + noTodos: 'Aucun todo sur ce projet', + addPlaceholder: 'Ajouter un todo', + add: 'Ajouter', + awaiting: 'Vous attend', + awaitingHint: 'Cette carte attend votre confirmation', + markOpen: 'Remettre à faire', + markClaimed: 'Marquer comme vous attendant', + markDone: 'Confirmer que c\'est fait', + removeTodo: 'Supprimer ce todo', + collapse: 'Replier cette section', + expand: 'Déplier cette section', + watchSection: 'PR surveillées', + noWatches: 'Aucune PR surveillée', + watchHint: 'Boite lit la PR sur GitHub environ une fois par minute et réveille l’agent quand un check échoue, que les checks passent, qu’une review ou un commentaire arrive ou que la branche entre en conflit', + watchNotRead: 'Pas encore lue', + watchNoChecks: 'Aucun check', + watchChecks: '{total} checks', + watchRunning: '{count} en cours', + watchFailed: '{count} en échec', + watchConflict: 'En conflit avec {base}', + watchRateLimited: 'Limite de requêtes GitHub, prochaine lecture {time}', + watchUnreachable: 'GitHub injoignable, prochaine lecture {time}', + stopWatch: 'Arrêter la surveillance', + stopWatchOf: 'Ne plus surveiller #{number}' + } +}; diff --git a/packages/ui/src/lib/strings/fr/telemetry.ts b/packages/ui/src/lib/strings/fr/telemetry.ts new file mode 100644 index 000000000..46ca0e42a --- /dev/null +++ b/packages/ui/src/lib/strings/fr/telemetry.ts @@ -0,0 +1,16 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + "telemetry": { + "heading": "Confidentialité et statistiques", + "description": "Les compteurs de base sont activés par défaut. Les statistiques améliorées restent facultatives. Aucun message, nom de projet, chemin, compte ou enregistrement n'est collecté.", + "basic": "Compteurs anonymes", + "basicHint": "Compter les installations actives, lancements, conversations et résultats des tours. Les identifiants d'usage changent chaque jour ; un identifiant distinct compte uniquement les installations.", + "enhanced": "Statistiques améliorées", + "enhancedHint": "Partager les modèles publics choisis, le niveau de raisonnement, les modes de vitesse et de permission, les durées et les volumes de jetons avec un identifiant aléatoire stable. Les modèles privés sont regroupés sous « autre ». Désactiver demande la suppression du profil et de ses événements.", + "unconfigured": "Cette version n'a pas de relais statistique configuré. Rien n'est envoyé.", + "pending": "Suppression en attente. Boite réessaie en arrière-plan. PostHog supprime les événements après acceptation de la demande.", + "export": "Exporter mes statistiques", + "retry": "Réessayer la suppression" + } +}; diff --git a/packages/ui/src/lib/strings/fr/terminal.ts b/packages/ui/src/lib/strings/fr/terminal.ts new file mode 100644 index 000000000..6c34fa1cf --- /dev/null +++ b/packages/ui/src/lib/strings/fr/terminal.ts @@ -0,0 +1,28 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + terminal: { + title: 'Terminal', + hide: 'Masquer le terminal', + close: 'Fermer le shell', + resize: 'Redimensionner le terminal', + tab: 'Terminal {n}', + tabs: 'Terminaux', + new: 'Nouveau terminal', + splitRight: 'Diviser à droite', + splitDown: 'Diviser en bas', + closeTab: "Fermer l'onglet", + resizePanes: 'Redimensionner la division', + find: 'Chercher dans le terminal', + history: 'Commandes récentes', + historySearch: 'Chercher une commande', + historyEmpty: "Aucune commande dans l'historique de ce shell pour l'instant", + historyNone: 'Aucune commande ne correspond', + historyOld: 'Cette machine a une version de Boite trop ancienne pour les lister', + historyLoading: "Lecture de l'historique", + historyHint: 'Entrée la lance, Maj+Entrée la met au prompt', + running: 'Une commande tourne', + failed: 'La dernière commande a échoué (code {code})' + } +}; diff --git a/packages/ui/src/lib/strings/fr/thread.ts b/packages/ui/src/lib/strings/fr/thread.ts new file mode 100644 index 000000000..6d12a09b9 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/thread.ts @@ -0,0 +1,71 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + thread: { + recoveryTitle: 'Message conservé avant le redémarrage', + recoveryBody: "Ce message n'a pas été envoyé à l'agent. Reprenez-le avec son modèle et son compte enregistrés, ou retirez-le de la file.", + recoveryTarget: 'Sélection enregistrée : {model} · {account}', + recoveryResume: 'Reprendre le message', + recoveryDiscard: 'Retirer de la file', + retryCapacityTitle: 'Modèle saturé', + retryCapacityBody: 'Nouvel essai dans {time}, essai {attempt} sur {attempts}.', + retryCapacityCancel: 'Arrêter les essais', + retryLimitTitle: 'Limite d\'usage atteinte', + retryLimitBody: 'Reprise à {time}, une fois la limite réinitialisée, dans {in}.', + retryLimitCancel: 'Ne pas reprendre', + retryContinueTitle: 'Arrêté avant sa réponse', + retryContinueBody: 'Reprise automatique dans {time}, reprise {attempt} sur {attempts}.', + contextDetails: 'Contexte', + contextInput: 'Entrée', + contextCache: 'Entrée en cache', + contextOutput: 'Sortie', + contextUsed: 'Utilisé', + contextFree: 'Disponible', + contextNoBreakdown: 'Cet agent ne détaille pas sa consommation.', + contextNoReading: "Aucune mesure reçue de cet agent pour l'instant.", + contextMeasured: 'Dernière mesure', + cacheTitle: 'Cache de prompt', + cacheChipWarm: (left: string) => `Cache de prompt chaud, encore environ ${left}`, + cacheChipMaybe: (most: string) => `Cache de prompt peut-être encore chaud, ${most} au plus`, + cacheChipCold: 'Cache de prompt froid', + cacheCold: 'froid', + cacheLeft: (left: string) => `encore ${left}`, + cacheMaybe: (left: string) => `peut-être · ${left}`, + cacheSwitched: 'Modèle ou compte changé.', + cacheLifetime: 'Durée de vie', + cacheSourceReported: 'API', + cacheSourceDocumented: 'doc', + cacheRange: (lifetime: string, most: string) => `${lifetime} à ${most}`, + cacheReadLast: 'Lu au dernier tour', + minutes: (n: number) => `${n} min`, + hours: (n: number) => `${n} h`, + none: 'Aucune conversation ouverte', + noneBody: 'Choisissez-en une à gauche, ou lancez-en une nouvelle.', + trace: 'Trace', + panel: 'Panneau', + panelHint: 'Navigateur, fichiers et outils de cette conversation', + terminalHint: 'Terminal dans le dossier de cette conversation', + cwd: 'Répertoire de travail', + model: 'Modèle', + defaultModel: 'Modèle par défaut', + renamePlaceholder: 'Titre de la conversation', + start: 'Lancer une conversation', + inWorktree: 'dans un worktree', + inProject: 'dans', + using: 'avec', + onEffort: 'en raisonnement {effort}', + draftMode: { + default: 'avec demandes d\'autorisation', + acceptEdits: 'avec les modifications autorisées', + bypassPermissions: 'avec toutes les permissions', + yolo: 'sans demandes de permission pour les outils', + plan: 'en mode plan', + dontAsk: 'avec refus automatique des demandes d\'autorisation' + }, + changeProject: 'Changer de projet', + searchProjects: 'Rechercher un projet', + noProjectMatch: 'Aucun projet ne correspond à « {query} »', + branchHint: 'Travaille dans un worktree sur cette branche', + } +}; diff --git a/packages/ui/src/lib/strings/fr/threadMove.ts b/packages/ui/src/lib/strings/fr/threadMove.ts new file mode 100644 index 000000000..0726299f6 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/threadMove.ts @@ -0,0 +1,25 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + threadMove: { + moveTo: 'Déplacer vers un projet', + archivedProject: '{project} (archivé)', + noOtherProject: 'Aucun autre projet', + stopFirst: 'Une sous-conversation travaille ; arrêtez-la avant de déplacer cette conversation', + runningTitle: 'Déplacer à la fin de ce tour', + runningBody: "L'agent travaille dans {folder}. La conversation passe dans {project} quand le tour en cours se termine.", + runningConfirm: 'Déplacer après ce tour', + runningBackgroundBody: "L'agent travaille dans {folder}. La conversation passe dans {project} quand le tour en cours se termine. Quelque chose qu'il a lancé tourne encore en arrière-plan : arrêtez-le à ce moment-là, ou laissez-le tourner là-bas jusqu'à ce que l'agent commence son prochain tour dans {project}.", + pending: 'Passe dans {project} après ce tour', + cancelMove: 'Annuler le déplacement', + monitorsTitle: 'Des moniteurs tournent encore', + backgroundTitle: 'Du travail tourne encore en arrière-plan', + backgroundBody: "L'agent fait encore tourner quelque chose dans {folder}. Arrêtez-le maintenant, ou laissez-le tourner là-bas jusqu'à ce que l'agent commence son prochain tour dans {project}.", + stopMonitors: 'Arrêter les moniteurs', + stopWork: "L'arrêter", + keep: 'Les garder', + dropHere: 'Déplacer la conversation vers {project}', + dragHint: 'Déposer sur un autre projet' + } +}; diff --git a/packages/ui/src/lib/strings/fr/threadStatus.ts b/packages/ui/src/lib/strings/fr/threadStatus.ts new file mode 100644 index 000000000..782faef80 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/threadStatus.ts @@ -0,0 +1,12 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + threadStatus: { + idle: 'au repos', + queued: 'en attente', + running: 'en cours', + waiting: 'vous attend', + error: 'erreur' + } +}; diff --git a/packages/ui/src/lib/strings/fr/time.ts b/packages/ui/src/lib/strings/fr/time.ts new file mode 100644 index 000000000..6a54d2cc3 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/time.ts @@ -0,0 +1,8 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + time: { + now: 'à l\'instant' + } +}; diff --git a/packages/ui/src/lib/strings/fr/titlebar.ts b/packages/ui/src/lib/strings/fr/titlebar.ts new file mode 100644 index 000000000..072cc60a2 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/titlebar.ts @@ -0,0 +1,19 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + titlebar: { + minimize: 'Réduire', + maximize: 'Agrandir', + restore: 'Restaurer', + quitHold: 'Maintenez Ctrl+Q pour quitter', + quitHoldHint: 'ou appuyez deux fois', + quitTitle: 'Fermer Boite ?', + quitWorkingOne: '1 agent travaille encore ou est en attente. Êtes-vous sûr de vouloir fermer Boite ?', + quitWorkingMany: '{count} agents travaillent encore ou sont en attente. Êtes-vous sûr de vouloir fermer Boite ?', + quitUnknownBody: 'Le travail sur une machine déconnectée n’a pas pu être vérifié.', + quitResidentBody: 'Les moteurs résidents continuent de travailler après la fermeture. Ceux configurés pour s’arrêter avec l’application s’arrêteront.', + quitConfirm: 'Fermer Boite', + quitCancel: 'Garder ouvert' + } +}; diff --git a/packages/ui/src/lib/strings/fr/trace.ts b/packages/ui/src/lib/strings/fr/trace.ts new file mode 100644 index 000000000..13f1f43d6 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/trace.ts @@ -0,0 +1,24 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + trace: { + exact: 'Tous les processus enfants suivis', + limited: 'Suivi des processus limité', + finished: 'Terminé', + parent: 'PID parent', + cpuTime: 'Temps CPU', + currentMemory: 'Mémoire actuelle', + active: 'actifs', + recorded: 'enregistrés', + exe: 'Exécutable', + pid: 'PID', + duration: 'Durée', + cpu: 'CPU', + memory: 'Mémoire maximale', + io: 'E/S', + exit: 'Sortie', + live: 'en cours', + empty: "Cette conversation n'a lancé aucun processus.", + } +}; diff --git a/packages/ui/src/lib/strings/fr/units.ts b/packages/ui/src/lib/strings/fr/units.ts new file mode 100644 index 000000000..37be10438 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/units.ts @@ -0,0 +1,16 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + units: { + bytes: 'o', + kilobytes: 'ko', + megabytes: 'Mo', + gigabytes: 'Go', + milliseconds: 'ms', + seconds: 's', + minutes: 'min', + hours: 'h', + tokens: 'jetons' + } +}; diff --git a/packages/ui/src/lib/strings/fr/updateAll.ts b/packages/ui/src/lib/strings/fr/updateAll.ts new file mode 100644 index 000000000..b37708383 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/updateAll.ts @@ -0,0 +1,27 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + updateAll: { + heading: 'Mises à jour', + trigger: 'Mises à jour disponibles', + busy: 'Mise à jour en cours', + run: 'Tout mettre à jour', + app: "Mettre à jour l'app", + retry: 'Réessayer', + later: 'Me le rappeler plus tard', + restarts: 'Boite télécharge sa version, puis demande à redémarrer. Les conversations reprennent où elles en étaient.', + summary: (count: number) => count === 1 ? '1 mise à jour' : `${count} mises à jour`, + downloading: (percent: number | null) => percent === null ? 'Téléchargement' : `Téléchargement ${percent} %`, + ready: 'Redémarre pour finir', + restartsNext: 'Redémarre une fois les agents à jour', + waiting: 'Attend la fin du travail', + installing: 'Redémarrage', + updating: 'Mise à jour', + waitingFor: (count: number) => count === 1 ? "Attend la pause d'une conversation" : `Attend la pause de ${count} conversations`, + failed: 'Échec', + done: 'À jour', + allDone: 'Tout est à jour', + finish: 'Boite est prête : redémarre pour finir.', + restart: 'Redémarrer' + } +}; diff --git a/packages/ui/src/lib/strings/fr/usage.ts b/packages/ui/src/lib/strings/fr/usage.ts new file mode 100644 index 000000000..eea336068 --- /dev/null +++ b/packages/ui/src/lib/strings/fr/usage.ts @@ -0,0 +1,75 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + usage: { + heading: 'Consommation', + note: "Les coûts sont les jetons au tarif API public de chaque modèle, d'après models.dev. Un modèle sans tarif connu prend le coût rapporté par son agent. Sur un abonnement, ce n'est pas une facture.", + thread: 'Conversation', + input: 'Entrée', + output: 'Sortie', + cost: 'Estimation au tarif API', + total: 'Total', + empty: 'Aucun jeton dépensé pour le moment.', + intro: 'Dépensé via Boite. Les jetons comptent les lectures et les écritures de cache.', + range: 'Période', + days: '{days} jours', + metric: 'Mesure', + provider: 'Fournisseur', + allProviders: 'Tous les fournisseurs', + machine: 'Machine', + allMachines: 'Toutes les machines', + machinesOffline: 'Non comptées, déconnectées : {machines}', + offline: 'Non connectée', + unknownProvider: 'Fournisseur inconnu', + filterUnsupported: 'Cette machine ne filtre pas la consommation par fournisseur. Mettez Boite à jour dessus, puis réessayez.', + noActivity: 'Aucun tour', + noUsage: 'Non rapporté', + coverage: 'Jetons {reported}/{turns} tours · Coût {priced}/{turns}', + partialCost: "{count} tours n'ont pas de prix et sont laissés hors de l'estimation.", + rates: 'Tarif API public de {model}, par million de jetons : entrée {input}, sortie {output}, lecture du cache {cacheRead}, écriture du cache {cacheWrite}.', + cacheSplit: 'Cache : {read} lus, {write} écrits.', + cacheSplitCost: 'Cache : {read} lus ({readCost}), {write} écrits ({writeCost}).', + reportedCost: "Aucun tarif public ne correspond à ce modèle : le coût est celui que son agent a rapporté.", + noRates: "Aucun tarif public ne correspond à ce modèle et son agent n'a rapporté aucun coût.", + olderCore: "La machine qui a fait ces tours a une version de Boite antérieure aux tarifs publics des API : seul un coût rapporté par son agent apparaît ici. Mettez-la à jour dans Réglages, Machines et mises à jour pour les estimer.", + noTokens: "L'agent n'a rapporté aucun jeton pour ces tours : ils n'ont pas de coût.", + priceOf: 'prix de {model}', + metrics: { tokens: 'Jetons', cost: 'Estimation API', turns: 'Tours' }, + totalOf: { + tokens: 'Jetons, {days} derniers jours', + cost: 'Estimation au tarif API, {days} derniers jours', + turns: 'Tours, {days} derniers jours' + }, + summary: '{turns} tours · {tokens} jetons · {cost} au tarif API', + summaryNoPrice: '{turns} tours · {tokens} jetons · Aucun prix', + summaryMissing: '{turns} tours · Consommation non rapportée', + overview: "Vue d'ensemble", + chart: 'Par jour', + chartLabel: '{metric} par jour et par fournisseur. Utilisez les flèches pour lire chaque jour.', + share: '{percent} du total', + breakdown: 'Détail', + byModel: 'Par modèle', + byDay: 'Par jour', + model: 'Modèle', + day: 'Jour', + turns: 'Tours', + tokens: 'Jetons', + cache: 'Cache', + noPrice: 'Aucun prix', + defaultModel: 'Modèle par défaut', + other: 'Autre', + threads: 'Conversations les plus gourmandes', + noRankedThreads: 'Aucune conversation à classer pour cette mesure.', + threadTurns: '{turns} tours', + untitled: 'Conversation sans titre', + archived: 'Archivée', + emptyRange: "Aucun tour terminé sur les {days} derniers jours.", + unpriced: 'Aucun prix pour {providers} : ni tarif public, ni coût rapporté.', + unreported: "{count} tours n'ont rapporté aucune consommation. Ils comptent seulement comme des tours.", + limits: 'Limites', + refresh: 'Actualiser la consommation', + loading: 'Lecture de la consommation', + failed: 'Lecture de la consommation impossible : {error}', + } +}; diff --git a/packages/ui/src/lib/strings/fr/workflow.ts b/packages/ui/src/lib/strings/fr/workflow.ts new file mode 100644 index 000000000..aaa1cff0d --- /dev/null +++ b/packages/ui/src/lib/strings/fr/workflow.ts @@ -0,0 +1,34 @@ +import type { Translation } from '../../i18n.svelte'; + +export const fr: Translation = { + + workflow: { + activeOne: '1 workflow', + activeMany: '{count} workflows', + status: { running: 'En cours', paused: 'En pause', done: 'Terminé', failed: 'En échec', stopped: 'Arrêté' }, + stepStatus: { waiting: 'En attente', running: 'En cours', done: 'Terminée', failed: 'En échec', skipped: 'Sautée', stopped: 'Arrêtée' }, + steps: '{done}/{total} étapes', + phase: 'Phase {n}', + pause: 'Pause', + resume: 'Reprendre', + stop: 'Arrêter', + retry: 'Relancer', + stopTitle: 'Arrêter « {name} » ?', + stopBody: "Les étapes en cours s'arrêtent et les suivantes ne démarrent pas. Les étapes finies gardent leurs résultats.", + forEach: 'Une par élément de {path}', + after: 'Après {steps}', + condition: 'Seulement si {path}', + task: 'Tâche', + result: 'Résultat', + output: 'Sortie', + items: 'Exécutions', + openThread: 'Ouvrir la conversation', + back: 'Retour au graphe', + close: 'Fermer le détail', + notStarted: 'Pas encore démarrée.', + skipped: "Sautée : sa condition n'était pas remplie.", + card: 'Workflow', + show: 'Voir le workflow', + tokens: '{tokens} jetons' + } +}; diff --git a/scripts/architecture/size-budget.json b/scripts/architecture/size-budget.json index 5b9ce7458..5aa74e7fc 100644 --- a/scripts/architecture/size-budget.json +++ b/scripts/architecture/size-budget.json @@ -2,7 +2,6 @@ "ceiling": 900, "allow": {}, "exempt": { - "packages/contracts/src/index.ts": "every RPC method, event and shared type is declared here before it is implemented (AGENTS.md)", - "packages/ui/src/lib/strings.*.ts": "one entry per UI sentence, mirrored in every language (docs/language.md)" + "packages/contracts/src/index.ts": "every RPC method, event and shared type is declared here before it is implemented (AGENTS.md)" } } diff --git a/scripts/architecture/size.test.ts b/scripts/architecture/size.test.ts index e17e3c70f..a2930e766 100644 --- a/scripts/architecture/size.test.ts +++ b/scripts/architecture/size.test.ts @@ -81,7 +81,7 @@ test('the checked-in budget keeps the 900-line ceiling, allows nothing under it expect(checkedIn.ceiling).toBe(900); for (const allowed of Object.values(checkedIn.allow)) expect(allowed).toBeGreaterThan(checkedIn.ceiling); for (const reason of Object.values(checkedIn.exempt ?? {})) expect(reason.length).toBeGreaterThan(20); - for (const file of ['packages/contracts/src/index.ts', 'packages/ui/src/lib/strings.en.ts', 'packages/ui/src/lib/strings.fr.ts']) { - expect(exemptBy(file, checkedIn)).toBeDefined(); - } + expect(exemptBy('packages/contracts/src/index.ts', checkedIn)).toBeDefined(); + // The catalogues are one file per part under lib/strings/, each below the ceiling. + expect(exemptBy('packages/ui/src/lib/strings/en/chat.ts', checkedIn)).toBeUndefined(); }); diff --git a/scripts/ci/budgets.json b/scripts/ci/budgets.json index a1dccc643..c2b5c6846 100644 --- a/scripts/ci/budgets.json +++ b/scripts/ci/budgets.json @@ -1,6 +1,6 @@ { "uiEntryChunk": 492300, - "uiDist": 4869600, + "uiDist": 4872200, "coreBundle": 995000, "coreJavaScript": 4493700 } diff --git a/scripts/ci/translations.test.ts b/scripts/ci/translations.test.ts index 5fddb14a3..ab8cca57e 100644 --- a/scripts/ci/translations.test.ts +++ b/scripts/ci/translations.test.ts @@ -1,5 +1,8 @@ import { expect, test } from 'bun:test'; -import { compare, leaves, loadCatalogues } from './translations.ts'; +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { compare, leaves, loadCatalogues, partErrors } from './translations.ts'; const en = { common: { yes: 'yes', no: 'no' }, @@ -50,3 +53,23 @@ test('the real catalogues load and no translation carries a key English lacks', expect({ code, extra: report.extra, mismatched: report.mismatched }).toEqual({ code, extra: [], mismatched: [] }); } }); + +test('every part of English has a file in each translation and is spread by each index', async () => { + expect(await partErrors()).toEqual([]); + const dir = mkdtempSync(join(tmpdir(), 'boite-parts-')); + try { + for (const locale of ['en', 'fr']) mkdirSync(join(dir, 'strings', locale), { recursive: true }); + writeFileSync(join(dir, 'strings', 'en', 'chat.ts'), `export const strings = { chat: { sent: 'Sent' } };`); + writeFileSync(join(dir, 'strings', 'en', 'common.ts'), `export const strings = { common: { yes: 'yes' } };`); + writeFileSync(join(dir, 'strings', 'fr', 'chat.ts'), `export const fr = { chat: { sent: 'Envoyé' } };`); + writeFileSync(join(dir, 'strings.en.ts'), `import { strings as chat } from './strings/en/chat.ts';\nexport const strings = { ...chat };`); + writeFileSync(join(dir, 'strings.fr.ts'), `export const fr = {};`); + expect(await partErrors(dir)).toEqual([ + `${join(dir, 'strings.en.ts')}: does not spread common from strings/en/common.ts`, + `${join(dir, 'strings', 'fr')}: no common.ts, expected one per file of strings/en`, + `${join(dir, 'strings.fr.ts')}: does not spread chat from strings/fr/chat.ts`, + ]); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); diff --git a/scripts/ci/translations.ts b/scripts/ci/translations.ts index 30119a519..3fa2466a8 100644 --- a/scripts/ci/translations.ts +++ b/scripts/ci/translations.ts @@ -75,11 +75,38 @@ export async function loadCatalogues(dir = CATALOGUE_DIR): Promise<{ reference: return { reference, translations }; } +/** + * Each part of English (`strings/en/.ts`) has a file of the same name in + * every translation, and each index spreads every part: a part left out of an + * index would vanish from the UI without any missing sentence to report. + */ +export async function partErrors(dir = CATALOGUE_DIR): Promise { + const parts = (locale: string) => readdirSync(join(dir, 'strings', locale)).filter((file) => file.endsWith('.ts')).sort(); + const english = parts('en'); + const { reference, translations } = await loadCatalogues(dir); + const errors: string[] = []; + for (const [code, catalogue] of [['en', reference], ...translations] as [string, unknown][]) { + const own = code === 'en' ? english : parts(code); + for (const file of english.filter((part) => !own.includes(part))) errors.push(`${join(dir, 'strings', code)}: no ${file}, expected one per file of strings/en`); + for (const file of own.filter((part) => !english.includes(part))) errors.push(`${join(dir, 'strings', code, file)}: not in strings/en`); + for (const file of own.filter((part) => english.includes(part))) { + const keys = Object.keys((await import(resolve(dir, 'strings', code, file)))[code === 'en' ? 'strings' : code] ?? {}); + const index = `${join(dir, `strings.${code}.ts`)}`; + for (const key of keys) if (!isNode(catalogue) || !Object.hasOwn(catalogue, key)) errors.push(`${index}: does not spread ${key} from strings/${code}/${file}`); + } + } + return errors; +} + if (import.meta.main) { const release = process.argv.includes('--release'); const { reference, translations } = await loadCatalogues(); const total = leaves(reference).length; let failed = false; + for (const line of await partErrors()) { + console.log(`::error title=Catalogue parts::${line}`); + failed = true; + } for (const [code, catalogue] of translations) { const file = `${CATALOGUE_DIR}/strings.${code}.ts`; const report = compare(reference, catalogue); diff --git a/scripts/migrate/split-catalogues.test.ts b/scripts/migrate/split-catalogues.test.ts new file mode 100644 index 000000000..9605b6bdd --- /dev/null +++ b/scripts/migrate/split-catalogues.test.ts @@ -0,0 +1,115 @@ +import { afterEach, describe, expect, test } from 'bun:test'; +import { spawnSync } from 'node:child_process'; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { LIB, migrate, splitCatalogues } from './split-catalogues'; + +const en = (entries: string) => `/** header */\nexport const strings = {\n${entries}\n} as const;\n\nexport type Strings = typeof strings;\n`; +const fr = (entries: string) => `import type { Translation } from './i18n.svelte';\n\nexport const fr: Translation = {\n${entries}\n};\n`; +const single = { + en: [' alpha: {\n one: \'One\',\n two: \'Two\',\n three: \'Three\',\n four: \'Four\',\n },', ' /** Beta. */\n beta: {\n three: \'Three\',\n },'], + fr: [' alpha: {\n one: \'Un\',\n two: \'Deux\',\n },', ' beta: {\n three: \'Trois\',\n },'], +}; + +let dir = ''; +afterEach(() => { + if (dir) rmSync(dir, { recursive: true, force: true }); + dir = ''; +}); + +function repo() { + dir = mkdtempSync(join(tmpdir(), 'split-catalogues-')); + const git = (...args: string[]) => { + const run = spawnSync('git', args, { cwd: dir, encoding: 'utf8' }); + return run.stdout.trim(); + }; + const put = (files: Record) => { + for (const [path, text] of Object.entries(files)) { + mkdirSync(dirname(join(dir, LIB, path)), { recursive: true }); + writeFileSync(join(dir, LIB, path), text); + } + }; + git('init', '-q', '-b', 'main'); + git('config', 'user.name', 'test'); + git('config', 'user.email', 'test@example.invalid'); + git('config', 'commit.gpgsign', 'false'); + git('config', 'core.autocrlf', 'false'); + put({ 'strings.en.ts': en(single.en.join('\n')), 'strings.fr.ts': fr(single.fr.join('\n')) }); + git('add', '-A'); + git('commit', '-qm', 'base'); + return { git, put, read: (path: string) => readFileSync(join(dir, LIB, path), 'utf8') }; +} + +/** Main splits the catalogues, then edits a part; the branch edits the single files. */ +function diverge(branch: { en: string[]; fr: string[] }, mainEdit?: (files: Map) => void) { + const r = repo(); + r.git('checkout', '-qb', 'feature'); + r.put({ 'strings.en.ts': en(branch.en.join('\n')), 'strings.fr.ts': fr(branch.fr.join('\n')) }); + r.git('commit', '-qam', 'branch'); + r.git('checkout', '-q', 'main'); + const files = splitCatalogues(r.read('strings.en.ts'), r.read('strings.fr.ts')); + mainEdit?.(files); + r.put(Object.fromEntries(files)); + r.git('add', '-A'); + r.git('commit', '-qm', 'split'); + r.git('checkout', '-q', 'feature'); + r.git('merge', '-q', '--no-edit', 'main'); + return r; +} + +describe('migrate', () => { + test('replays a branch edit onto the part main kept editing', () => { + const r = diverge( + { en: [single.en[0].replace("'Four'", "'Four, edited'"), single.en[1]], fr: single.fr }, + (files) => files.set('strings/en/alpha.ts', files.get('strings/en/alpha.ts')!.replace("'One'", "'One, from main'")), + ); + const result = migrate(dir, 'HEAD', 'MERGE_HEAD'); + expect(result.conflicts).toEqual([]); + expect(result.written).toEqual([`${LIB}/strings/en/alpha.ts`]); + expect(r.read('strings/en/alpha.ts')).toContain("one: 'One, from main',\n two: 'Two',\n three: 'Three',\n four: 'Four, edited',"); + expect(r.read('strings.en.ts')).toContain("import { strings as beta } from './strings/en/beta';"); + }); + + test('adds a part the branch created to both indexes, after its neighbour', () => { + const gamma = { en: ' gamma: {\n four: \'Four\',\n },', fr: ' gamma: {\n four: \'Quatre\',\n },' }; + const r = diverge({ en: [single.en[0], gamma.en, single.en[1]], fr: [single.fr[0], gamma.fr, single.fr[1]] }); + const result = migrate(dir, 'HEAD', 'MERGE_HEAD'); + expect(result.conflicts).toEqual([]); + expect(r.read('strings/fr/gamma.ts')).toContain("four: 'Quatre'"); + expect(r.read('strings.en.ts')).toContain(' ...alpha,\n ...gamma,\n ...beta,'); + expect(r.read('strings.fr.ts')).toContain("from './strings/fr/alpha';\nimport { fr as gamma } from './strings/fr/gamma';"); + }); + + test('deletes a part the branch removed when main left it alone', () => { + const r = diverge({ en: [single.en[0]], fr: [single.fr[0]] }); + const result = migrate(dir, 'HEAD', 'MERGE_HEAD'); + expect(result.deleted.sort()).toEqual([`${LIB}/strings/en/beta.ts`, `${LIB}/strings/fr/beta.ts`]); + expect(existsSync(join(dir, LIB, 'strings/en/beta.ts'))).toBe(false); + expect(r.read('strings.en.ts')).not.toContain('beta'); + }); + + test('leaves conflict markers where both sides changed the same line', () => { + const r = diverge( + { en: [single.en[0].replace("'One'", "'One, branch'"), single.en[1]], fr: single.fr }, + (files) => files.set('strings/en/alpha.ts', files.get('strings/en/alpha.ts')!.replace("'One'", "'One, main'")), + ); + const result = migrate(dir, 'HEAD', 'MERGE_HEAD'); + expect(result.conflicts).toEqual([`${LIB}/strings/en/alpha.ts`]); + expect(r.read('strings/en/alpha.ts')).toMatch(/<<<<<<< HEAD\n {4}one: 'One, branch',\n=======\n {4}one: 'One, main',\n>>>>>>> main/); + }); + + test('warns when the branch changed text outside the catalogue object', () => { + const r = repo(); + r.git('checkout', '-qb', 'feature'); + r.put({ 'strings.en.ts': en(single.en.join('\n')).replace('/** header */', '/** new header */') }); + r.git('commit', '-qam', 'branch'); + r.git('checkout', '-q', 'main'); + r.put(Object.fromEntries(splitCatalogues(r.read('strings.en.ts'), r.read('strings.fr.ts')))); + r.git('add', '-A'); + r.git('commit', '-qm', 'split'); + r.git('checkout', '-q', 'feature'); + r.git('merge', '-q', '--no-edit', 'main'); + expect(migrate(dir, 'HEAD', 'MERGE_HEAD').warnings).toEqual([expect.stringContaining('strings.en.ts: the branch changed text outside')]); + }); +}); diff --git a/scripts/migrate/split-catalogues.ts b/scripts/migrate/split-catalogues.ts new file mode 100644 index 000000000..06572a20a --- /dev/null +++ b/scripts/migrate/split-catalogues.ts @@ -0,0 +1,285 @@ +/** + * The UI catalogues moved from `strings.en.ts` and `strings.fr.ts` to one file + * per top-level key under `lib/strings//`. A branch cut before that + * split edits the old single files, so merging main conflicts on them. + * + * Run on the branch once `git merge origin/main` stopped (or after the merge + * commit): + * + * bun scripts/migrate/split-catalogues.ts + * + * It splits the merge base and the branch versions of both catalogues with the + * codemod that made the split, then replays each part's change onto main's part + * with `git merge-file`, so main's edits made since the split stay. Resolved + * files are staged; a part both sides changed on the same lines keeps conflict + * markers and is listed, like any merge conflict. + * + * `bun scripts/migrate/split-catalogues.ts --split` reruns the codemod on the + * working tree of a tree that still has the single files. + */ +import { spawnSync } from 'node:child_process'; +import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; + +type Node = { type: string; start: number; end: number; [key: string]: any }; +export const LIB = 'packages/ui/src/lib'; +const ROOT = join(import.meta.dir, '..', '..'); + +const { parseSync } = await import(createRequire(join(ROOT, 'packages/ui/package.json')).resolve('vite')); + +export interface Catalogue { + keys: string[]; + /** top-level key to its source text, comments above it included */ + entries: Map; + /** every byte outside the catalogue object: header comment, imports, types */ + outside: string; +} + +export function parseCatalogue(file: string, source: string, name: string): Catalogue { + const parsed = parseSync(file, source, { lang: 'ts' }); + if (parsed.errors.length) throw new Error(`${file}: ${parsed.errors[0].message}`); + for (const statement of parsed.program.body as Node[]) { + const declaration = statement.type === 'ExportNamedDeclaration' ? statement.declaration : null; + if (declaration?.type !== 'VariableDeclaration') continue; + const declarator = declaration.declarations.find((d: Node) => d.id.name === name); + if (!declarator) continue; + let init = declarator.init as Node; + while (init.type === 'TSAsExpression' || init.type === 'TSSatisfiesExpression') init = init.expression; + const properties = init.properties as Node[]; + const keys = properties.map((p) => p.key.name ?? p.key.value); + // Each entry keeps the comments above it; the separator comma stays behind. + const entries = new Map(); + properties.forEach((property, i) => { + const from = i === 0 ? init.start + 1 : properties[i - 1].end; + entries.set(keys[i], source.slice(from, property.end).replace(/^,/, '').replace(/^[ \t]*\n/, '')); + }); + const tail = source.slice(properties.at(-1)!.end, init.end - 1).replace(/^,/, '').trim(); + if (tail) throw new Error(`${file}: text after the last entry: ${tail.slice(0, 80)}`); + return { keys, entries, outside: source.slice(0, init.start) + source.slice(init.end) }; + } + throw new Error(`${file}: no export const ${name}`); +} + +export const enPart = (entry: string) => `export const strings = {\n${entry}\n} as const;\n`; +export const frPart = (entry: string | undefined) => + `import type { Translation } from '../../i18n.svelte';\n\nexport const fr: Translation = ${entry ? `{\n${entry}\n}` : '{}'};\n`; +export const enImport = (key: string) => `import { strings as ${key} } from './strings/en/${key}';`; +export const frImport = (key: string) => `import { fr as ${key} } from './strings/fr/${key}';`; + +/** The files the split writes, by path under `lib/`, from the two single catalogues. */ +export function splitCatalogues(enSource: string, frSource: string): Map { + const en = parseCatalogue('strings.en.ts', enSource, 'strings'); + const fr = parseCatalogue('strings.fr.ts', frSource, 'fr'); + const unknown = fr.keys.filter((key) => !en.entries.has(key)); + if (unknown.length) throw new Error(`strings.fr.ts: keys English lacks: ${unknown.join(', ')}`); + const files = new Map(); + for (const key of en.keys) { + files.set(`strings/en/${key}.ts`, enPart(en.entries.get(key)!)); + files.set(`strings/fr/${key}.ts`, frPart(fr.entries.get(key))); + } + const spread = en.keys.map((key) => ` ...${key},`).join('\n'); + files.set('strings.en.ts', `/** + * Every user-facing string of the UI. No literal UI text lives anywhere else. + * + * One file per top-level key in \`./strings/en/\`, so two changes to different + * parts of the UI never edit the same file. A new key goes in the file of its + * part; a new part is a file here and in every translation, and a line in each + * index. + */ +${en.keys.map(enImport).join('\n')} + +export const strings = { +${spread} +} as const; + +export type Strings = typeof strings; +`); + files.set('strings.fr.ts', `/* + * Le catalogue français, calqué sur \`strings.en.ts\`, un fichier par clé de + * premier niveau dans \`./strings/fr/\`, nommé comme son modèle anglais. + * + * Le type vient de l'anglais : une clé inconnue de l'anglais arrête + * \`svelte-check\`. Une phrase pas encore traduite peut manquer ici, elle + * s'affiche alors en anglais (\`lib/i18n.svelte.ts\`) et passe en nightly, mais + * \`scripts/ci/translations.ts --release\` bloque la release tant qu'elle manque. + * Les accolades sont des emplacements remplis par \`fill\`, leur nom ne se + * traduit pas. + */ +import type { Translation } from './i18n.svelte'; +${en.keys.map(frImport).join('\n')} + +export const fr: Translation = { +${spread} +}; +`); + return files; +} + +function git(cwd: string, args: string[], input?: string): { status: number; stdout: string } { + const run = spawnSync('git', args, { cwd, input, encoding: 'utf8', maxBuffer: 64 << 20 }); + return { status: run.status ?? 1, stdout: run.stdout }; +} + +function show(cwd: string, ref: string, path: string): string | undefined { + const run = git(cwd, ['show', `${ref}:${path}`]); + return run.status === 0 ? run.stdout : undefined; +} + +const isSplit = (cwd: string, ref: string) => git(cwd, ['cat-file', '-e', `${ref}:${LIB}/strings/en`]).status === 0; + +/** Adds a part to an index after the part that precedes it on the branch, or last. */ +export function addToIndex(index: string, key: string, after: string | undefined, locale: 'en' | 'fr'): string { + const line = locale === 'en' ? enImport : frImport; + const lines = index.split('\n'); + const importAt = after === undefined ? -1 : lines.indexOf(line(after)); + const lastImport = lines.findLastIndex((l) => l.startsWith(locale === 'en' ? 'import { strings as ' : 'import { fr as ')); + lines.splice((importAt >= 0 ? importAt : lastImport) + 1, 0, line(key)); + const spreadAt = after === undefined ? -1 : lines.indexOf(` ...${after},`); + const close = lines.findLastIndex((l) => l === '} as const;' || l === '};'); + lines.splice(spreadAt >= 0 ? spreadAt + 1 : close, 0, ` ...${key},`); + return lines.join('\n'); +} + +export function removeFromIndex(index: string, key: string, locale: 'en' | 'fr'): string { + const line = (locale === 'en' ? enImport : frImport)(key); + return index.split('\n').filter((l) => l !== line && l !== ` ...${key},`).join('\n'); +} + +function mergeFile(ours: string, base: string, theirs: string): { text: string; conflict: boolean } { + const dir = mkdtempSync(join(tmpdir(), 'catalogue-merge-')); + try { + const paths = ['ours', 'base', 'theirs'].map((name) => join(dir, name)); + [ours, base, theirs].forEach((text, i) => writeFileSync(paths[i], text)); + const run = spawnSync('git', ['merge-file', '-p', '-L', 'HEAD', '-L', 'merge base', '-L', 'main', ...paths], { encoding: 'utf8' }); + if ((run.status ?? -1) < 0 || run.status === null) throw new Error(`git merge-file failed: ${run.stderr}`); + return { text: run.stdout, conflict: run.status !== 0 }; + } finally { + rmSync(dir, { recursive: true, force: true }); + } +} + +export interface MigrationResult { + written: string[]; + deleted: string[]; + conflicts: string[]; + warnings: string[]; +} + +/** + * Replays the branch's catalogue edits (`base` to `ours`, single files) onto + * main's split catalogues (`theirs`) in the working tree of `cwd`. + */ +export function migrate(cwd: string, ours: string, theirs: string): MigrationResult { + const result: MigrationResult = { written: [], deleted: [], conflicts: [], warnings: [] }; + const base = git(cwd, ['merge-base', ours, theirs]).stdout.trim(); + if (!base) throw new Error(`no merge base between ${ours} and ${theirs}`); + const catalogues = (ref: string) => { + const en = show(cwd, ref, `${LIB}/strings.en.ts`); + const fr = show(cwd, ref, `${LIB}/strings.fr.ts`); + if (en === undefined || fr === undefined) throw new Error(`${ref} has no ${LIB}/strings.en.ts or strings.fr.ts`); + return { en, fr, files: splitCatalogues(en, fr) }; + }; + const before = catalogues(base); + const after = catalogues(ours); + for (const [file, name] of [['strings.en.ts', 'strings'], ['strings.fr.ts', 'fr']] as const) { + const was = parseCatalogue(file, before[file === 'strings.en.ts' ? 'en' : 'fr'], name).outside; + const now = parseCatalogue(file, after[file === 'strings.en.ts' ? 'en' : 'fr'], name).outside; + if (was !== now) result.warnings.push(`${file}: the branch changed text outside the catalogue object (header, imports, types); carry it over by hand`); + } + + const index = { en: show(cwd, theirs, `${LIB}/strings.en.ts`)!, fr: show(cwd, theirs, `${LIB}/strings.fr.ts`)! }; + const indexBefore = { ...index }; + const branchKeys = [...after.files.keys()].filter((p) => p.startsWith('strings/en/')).map((p) => p.slice(11, -3)); + const parts = new Set([...before.files.keys(), ...after.files.keys()].filter((p) => p.startsWith('strings/'))); + const write = (path: string, text: string) => { + mkdirSync(dirname(join(cwd, path)), { recursive: true }); + writeFileSync(join(cwd, path), text); + }; + + for (const part of parts) { + const path = `${LIB}/${part}`; + const was = before.files.get(part); + const now = after.files.get(part); + if (was === now) continue; + const main = show(cwd, theirs, path); + const locale = part.startsWith('strings/en/') ? 'en' : 'fr'; + const key = part.slice(11, -3); + if (now === undefined) { + if (main === undefined) continue; + if (main !== was) { + result.conflicts.push(`${path}: the branch deletes this part, main changed it since; delete or keep it by hand`); + continue; + } + rmSync(join(cwd, path), { force: true }); + index[locale] = removeFromIndex(index[locale], key, locale); + result.deleted.push(path); + continue; + } + if (main === undefined && was !== undefined) { + write(path, now); + result.conflicts.push(`${path}: main deleted this part since the split, the branch changed it; the branch version is in place`); + continue; + } + const merged = main === undefined ? { text: now, conflict: false } : mergeFile(now, was ?? '', main); + if (main === undefined) { + const at = branchKeys.indexOf(key); + index[locale] = addToIndex(index[locale], key, at > 0 ? branchKeys[at - 1] : undefined, locale); + } + write(path, merged.text); + (merged.conflict ? result.conflicts : result.written).push(path); + } + + for (const locale of ['en', 'fr'] as const) { + const path = `${LIB}/strings.${locale}.ts`; + write(path, index[locale]); + if (index[locale] !== indexBefore[locale]) result.written.push(path); + } + return result; +} + +function main() { + const cwd = git(process.cwd(), ['rev-parse', '--show-toplevel']).stdout.trim() || process.cwd(); + if (process.argv.includes('--split')) { + const lib = join(cwd, LIB); + const read = (file: string) => readFileSync(join(lib, file), 'utf8').replaceAll('\r\n', '\n'); + const files = splitCatalogues(read('strings.en.ts'), read('strings.fr.ts')); + rmSync(join(lib, 'strings'), { recursive: true, force: true }); + for (const [path, text] of files) { + mkdirSync(dirname(join(lib, path)), { recursive: true }); + writeFileSync(join(lib, path), text); + } + console.log(`${(files.size - 2) / 2} parts`); + return; + } + + // Mid-merge: HEAD is the branch, MERGE_HEAD main. After the merge commit: its two parents. + const merging = git(cwd, ['rev-parse', '--verify', '-q', 'MERGE_HEAD']).status === 0; + const [ours, theirs] = merging ? ['HEAD', 'MERGE_HEAD'] : ['HEAD^1', 'HEAD^2']; + if (!merging && git(cwd, ['rev-parse', '--verify', '-q', 'HEAD^2']).status !== 0) { + console.error('Merge main first: git merge origin/main, then rerun this command.'); + process.exit(2); + } + if (isSplit(cwd, ours)) { + console.log('The branch already has the split catalogues; nothing to migrate.'); + return; + } + if (!isSplit(cwd, theirs)) { + console.error(`${theirs} does not have the split catalogues; merge a main that has them.`); + process.exit(2); + } + + const result = migrate(cwd, ours, theirs); + const staged = [...result.written, ...result.deleted]; + if (staged.length) git(cwd, ['add', '-A', '--', ...staged]); + if (merging) git(cwd, ['add', '--', `${LIB}/strings.en.ts`, `${LIB}/strings.fr.ts`]); + for (const path of result.written) console.log(`merged ${path}`); + for (const path of result.deleted) console.log(`deleted ${path}`); + for (const warning of result.warnings) console.warn(`warning ${warning}`); + for (const conflict of result.conflicts) console.error(`conflict ${conflict}`); + if (!merging) console.log('Files are staged; commit them on top of the merge.'); + if (result.conflicts.length) process.exit(1); +} + +if (import.meta.main) main();