Merge branch 'pr889-fixes' into electron-app

# Conflicts:
#	server/index.js
This commit is contained in:
Simos Mikelatos
2026-06-17 15:45:07 +00:00
25 changed files with 1563 additions and 60 deletions

1
.gitignore vendored
View File

@@ -134,6 +134,7 @@ tasks/
# Translations # Translations
!src/i18n/locales/en/tasks.json !src/i18n/locales/en/tasks.json
!src/i18n/locales/fr/tasks.json
!src/i18n/locales/ja/tasks.json !src/i18n/locales/ja/tasks.json
!src/i18n/locales/ru/tasks.json !src/i18n/locales/ru/tasks.json
!src/i18n/locales/de/tasks.json !src/i18n/locales/de/tasks.json

26
package-lock.json generated
View File

@@ -88,6 +88,7 @@
"auto-changelog": "^2.5.0", "auto-changelog": "^2.5.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"cross-env": "^10.1.0",
"electron": "^38.0.0", "electron": "^38.0.0",
"electron-builder": "^26.15.3", "electron-builder": "^26.15.3",
"eslint": "^9.39.3", "eslint": "^9.39.3",
@@ -1748,6 +1749,13 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@epic-web/invariant": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
"dev": true,
"license": "MIT"
},
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.8", "version": "0.25.8",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz",
@@ -8694,6 +8702,24 @@
"optional": true, "optional": true,
"peer": true "peer": true
}, },
"node_modules/cross-env": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@epic-web/invariant": "^1.0.0",
"cross-spawn": "^7.0.6"
},
"bin": {
"cross-env": "dist/bin/cross-env.js",
"cross-env-shell": "dist/bin/cross-env-shell.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.6", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",

View File

@@ -33,7 +33,7 @@
"server:dev-watch": "tsx watch --tsconfig server/tsconfig.json server/index.js", "server:dev-watch": "tsx watch --tsconfig server/tsconfig.json server/index.js",
"client": "vite", "client": "vite",
"desktop": "electron electron/main.js", "desktop": "electron electron/main.js",
"desktop:dev": "ELECTRON_DEV_URL=http://127.0.0.1:5173 electron electron/main.js", "desktop:dev": "cross-env ELECTRON_DEV_URL=http://127.0.0.1:5173 electron electron/main.js",
"desktop:pack": "npm run build && electron-builder --dir", "desktop:pack": "npm run build && electron-builder --dir",
"desktop:dist:mac": "npm run build && electron-builder --mac dmg zip", "desktop:dist:mac": "npm run build && electron-builder --mac dmg zip",
"desktop:icon:mac": "node electron/scripts/generate-macos-icon.js", "desktop:icon:mac": "node electron/scripts/generate-macos-icon.js",
@@ -198,6 +198,7 @@
"auto-changelog": "^2.5.0", "auto-changelog": "^2.5.0",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"cross-env": "^10.1.0",
"electron": "^38.0.0", "electron": "^38.0.0",
"electron-builder": "^26.15.3", "electron-builder": "^26.15.3",
"eslint": "^9.39.3", "eslint": "^9.39.3",

View File

@@ -35,6 +35,7 @@ const readNumber = (value: unknown): number | undefined =>
const apiUrl = (process.env.CLOUDCLI_BROWSER_USE_API_URL || 'http://127.0.0.1:3001/api/browser-use-mcp').replace(/\/$/, ''); const apiUrl = (process.env.CLOUDCLI_BROWSER_USE_API_URL || 'http://127.0.0.1:3001/api/browser-use-mcp').replace(/\/$/, '');
const apiToken = process.env.CLOUDCLI_BROWSER_USE_MCP_TOKEN || ''; const apiToken = process.env.CLOUDCLI_BROWSER_USE_MCP_TOKEN || '';
const API_TIMEOUT_MS = Number.parseInt(process.env.CLOUDCLI_BROWSER_USE_API_TIMEOUT_MS || '60000', 10);
async function callBrowserUseApi(toolName: string, input: Record<string, unknown>) { async function callBrowserUseApi(toolName: string, input: Record<string, unknown>) {
if (!apiToken) { if (!apiToken) {
@@ -48,6 +49,7 @@ async function callBrowserUseApi(toolName: string, input: Record<string, unknown
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify(input), body: JSON.stringify(input),
signal: AbortSignal.timeout(API_TIMEOUT_MS),
}); });
const data = await response.json() as { success?: boolean; data?: unknown; error?: string }; const data = await response.json() as { success?: boolean; data?: unknown; error?: string };
if (!response.ok || data.success === false) { if (!response.ok || data.success === false) {
@@ -378,7 +380,13 @@ process.stdin.on('data', (chunk) => {
buffer = buffer.slice(messageEnd); buffer = buffer.slice(messageEnd);
void (async () => { void (async () => {
const request = JSON.parse(rawMessage) as JsonRpcRequest; let request: JsonRpcRequest;
try {
request = JSON.parse(rawMessage) as JsonRpcRequest;
} catch (error) {
sendError(null, error);
return;
}
try { try {
const result = await handleMessage(request); const result = await handleMessage(request);
sendResult(request.id, result); sendResult(request.id, result);

View File

@@ -155,12 +155,13 @@ Usage:
cloudcli [command] [options] cloudcli [command] [options]
Commands: Commands:
start Start the CloudCLI server (default) start Start the CloudCLI server (default)
sandbox Manage Docker sandbox environments sandbox Manage Docker sandbox environments
status Show configuration and data locations browser-use-mcp Run the Browser Use MCP stdio server
update Update to the latest version status Show configuration and data locations
help Show this help information update Update to the latest version
version Show version information help Show this help information
version Show version information
Options: Options:
-p, --port <port> Set server port (default: 3001) -p, --port <port> Set server port (default: 3001)

View File

@@ -1755,9 +1755,21 @@ async function startServer() {
await closeSessionsWatcher(); await closeSessionsWatcher();
// Clean up plugin processes on shutdown // Clean up plugin processes on shutdown
const shutdownRuntimeServices = async () => { const shutdownRuntimeServices = async () => {
await browserUseService.stopAllSessions(); try {
await stopAllPlugins(); await browserUseService.stopAllSessions();
await removeLocalServerMarker(); } catch (err) {
console.error('[Browser Use] Error stopping sessions during shutdown:', err?.message || err);
}
try {
await stopAllPlugins();
} catch (err) {
console.error('[Plugins] Error stopping plugins during shutdown:', err?.message || err);
}
try {
await removeLocalServerMarker();
} catch (err) {
console.error('[Local Server] Error removing server marker during shutdown:', err?.message || err);
}
process.exit(0); process.exit(0);
}; };
process.on('SIGTERM', () => void shutdownRuntimeServices()); process.on('SIGTERM', () => void shutdownRuntimeServices());

View File

@@ -8,8 +8,8 @@ function readBearerToken(header: unknown): string | null {
if (typeof header !== 'string') { if (typeof header !== 'string') {
return null; return null;
} }
const match = /^Bearer\s+(.+)$/i.exec(header.trim()); const match = /^Bearer\s+(\S.*)$/i.exec(header.trim());
return match?.[1] || null; return match?.[1]?.trim() || null;
} }
router.use((req, res, next) => { router.use((req, res, next) => {

View File

@@ -121,10 +121,12 @@ router.post('/sessions/:sessionId/navigate', async (req: AuthenticatedRequest, r
router.post('/sessions/:sessionId/click', async (req: AuthenticatedRequest, res) => { router.post('/sessions/:sessionId/click', async (req: AuthenticatedRequest, res) => {
try { try {
const session = await browserUseService.userClick(requireUser(req), readParam(req.params.sessionId), { const x = Number(req.body?.x);
x: Number(req.body?.x), const y = Number(req.body?.y);
y: Number(req.body?.y), if (!Number.isFinite(x) || !Number.isFinite(y)) {
}); throw new Error('Click requires numeric x and y coordinates.');
}
const session = await browserUseService.userClick(requireUser(req), readParam(req.params.sessionId), { x, y });
res.json({ success: true, data: { session } }); res.json({ success: true, data: { session } });
} catch (error) { } catch (error) {
res.status(400).json({ res.status(400).json({
@@ -136,7 +138,11 @@ router.post('/sessions/:sessionId/click', async (req: AuthenticatedRequest, res)
router.post('/sessions/:sessionId/press-key', async (req: AuthenticatedRequest, res) => { router.post('/sessions/:sessionId/press-key', async (req: AuthenticatedRequest, res) => {
try { try {
const session = await browserUseService.userPressKey(requireUser(req), readParam(req.params.sessionId), String(req.body?.key || '')); const key = String(req.body?.key || '').trim();
if (!key) {
throw new Error('A key is required.');
}
const session = await browserUseService.userPressKey(requireUser(req), readParam(req.params.sessionId), key);
res.json({ success: true, data: { session } }); res.json({ success: true, data: { session } });
} catch (error) { } catch (error) {
res.status(400).json({ res.status(400).json({

View File

@@ -7,7 +7,7 @@ import os from 'node:os';
import net from 'node:net'; import net from 'node:net';
import path from 'node:path'; import path from 'node:path';
import { appConfigDb } from '@/modules/database/repositories/app-config.js'; import { appConfigDb } from '@/modules/database/index.js';
import { providerMcpService } from '@/modules/providers/services/mcp.service.js'; import { providerMcpService } from '@/modules/providers/services/mcp.service.js';
import { getModuleDir } from '@/utils/runtime-paths.js'; import { getModuleDir } from '@/utils/runtime-paths.js';
@@ -220,6 +220,11 @@ function getRuntimeReadiness(): RuntimeReadiness {
return readiness; return readiness;
} }
const INSTALL_COMMAND_TIMEOUT_MS = Number.parseInt(
process.env.CLOUDCLI_BROWSER_USE_INSTALL_TIMEOUT_MS || String(10 * 60 * 1000),
10,
);
function runCommand(command: string, args: string[]): Promise<void> { function runCommand(command: string, args: string[]): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const child = spawn(command, args, { const child = spawn(command, args, {
@@ -229,18 +234,36 @@ function runCommand(command: string, args: string[]): Promise<void> {
stdio: ['ignore', 'pipe', 'pipe'], stdio: ['ignore', 'pipe', 'pipe'],
}); });
const output: string[] = []; const output: string[] = [];
let settled = false;
const finish = (fn: () => void) => {
if (settled) {
return;
}
settled = true;
clearTimeout(timer);
fn();
};
// Guard against a stuck npm/playwright process hanging the install request forever.
const timer = setTimeout(() => {
child.kill('SIGKILL');
finish(() => reject(new Error(
`${command} ${args.join(' ')} timed out after ${INSTALL_COMMAND_TIMEOUT_MS}ms.`,
)));
}, INSTALL_COMMAND_TIMEOUT_MS);
timer.unref?.();
child.stdout.on('data', (chunk) => output.push(String(chunk))); child.stdout.on('data', (chunk) => output.push(String(chunk)));
child.stderr.on('data', (chunk) => output.push(String(chunk))); child.stderr.on('data', (chunk) => output.push(String(chunk)));
child.on('error', reject); child.on('error', (error) => finish(() => reject(error)));
child.on('close', (code) => { child.on('close', (code) => finish(() => {
if (code === 0) { if (code === 0) {
resolve(); resolve();
return; return;
} }
reject(new Error(output.join('').trim() || `${command} ${args.join(' ')} exited with code ${code}`)); reject(new Error(output.join('').trim() || `${command} ${args.join(' ')} exited with code ${code}`));
}); }));
}); });
} }
@@ -386,8 +409,10 @@ async function assertAllowedBrowserRequest(rawUrl: string): Promise<void> {
await assertPublicHttpTarget(parsed); await assertPublicHttpTarget(parsed);
} }
async function attachRequestGuard(page: any): Promise<void> { async function attachRequestGuard(context: any): Promise<void> {
await page.route('**/*', async (route: any) => { // Attach at the context level so the guard also covers popups, window.open targets,
// and any replacement pages created during the session lifecycle.
await context.route('**/*', async (route: any) => {
try { try {
await assertAllowedBrowserRequest(route.request().url()); await assertAllowedBrowserRequest(route.request().url());
await route.continue(); await route.continue();
@@ -637,7 +662,7 @@ export const browserUseService = {
context = await browser.newContext(contextOptions); context = await browser.newContext(contextOptions);
page = await context.newPage(); page = await context.newPage();
} }
await attachRequestGuard(page); await attachRequestGuard(context);
session.status = 'ready'; session.status = 'ready';
session.message = 'Browser session is ready.'; session.message = 'Browser session is ready.';
sessions.set(session.id, session); sessions.set(session.id, session);
@@ -886,7 +911,7 @@ export const browserUseService = {
if (action === 'new') { if (action === 'new') {
const page = await handle.context.newPage(); const page = await handle.context.newPage();
handles.set(sessionId, { ...handle, page }); handles.set(sessionId, { ...handle, page });
await attachRequestGuard(page); // Request guard is attached at the context level, so new pages are already covered.
if (input.url) { if (input.url) {
await this.agentNavigate(sessionId, input.url); await this.agentNavigate(sessionId, input.url);
} }

View File

@@ -121,16 +121,12 @@ function AppContentInner() {
}, [refreshRunningSessions]); }, [refreshRunningSessions]);
useEffect(() => { useEffect(() => {
if (processingSessions.size === 0) {
return;
}
const interval = window.setInterval(() => { const interval = window.setInterval(() => {
void refreshRunningSessions(); void refreshRunningSessions();
}, 5000); }, 5000);
return () => window.clearInterval(interval); return () => window.clearInterval(interval);
}, [processingSessions.size, refreshRunningSessions]); }, [refreshRunningSessions]);
usePaletteOpsRegister({ usePaletteOpsRegister({
openSettings, openSettings,

View File

@@ -202,7 +202,9 @@ export function useChatRealtimeHandlers({
// indicator derives from the processing map, so deleting the entry // indicator derives from the processing map, so deleting the entry
// hides it immediately and atomically. // hides it immediately and atomically.
onSessionIdle?.(sid); onSessionIdle?.(sid);
setPendingPermissionRequests([]); if (sid === activeViewSessionId) {
setPendingPermissionRequests([]);
}
if (msg.aborted) { if (msg.aborted) {
// Abort was requested — the complete event confirms it. No // Abort was requested — the complete event confirms it. No
@@ -232,17 +234,19 @@ export function useChatRealtimeHandlers({
case 'permission_request': { case 'permission_request': {
if (!msg.requestId) break; if (!msg.requestId) break;
setPendingPermissionRequests((prev) => { if (sid === activeViewSessionId) {
if (prev.some((r: PendingPermissionRequest) => r.requestId === msg.requestId)) return prev; setPendingPermissionRequests((prev) => {
return [...prev, { if (prev.some((r: PendingPermissionRequest) => r.requestId === msg.requestId)) return prev;
requestId: msg.requestId as string, return [...prev, {
toolName: (msg.toolName as string) || 'UnknownTool', requestId: msg.requestId as string,
input: msg.input, toolName: (msg.toolName as string) || 'UnknownTool',
context: msg.context, input: msg.input,
sessionId: sid || null, context: msg.context,
receivedAt: new Date(), sessionId: sid || null,
}]; receivedAt: new Date(),
}); }];
});
}
if (sid) { if (sid) {
onSessionProcessing?.(sid); onSessionProcessing?.(sid);
} }
@@ -250,7 +254,7 @@ export function useChatRealtimeHandlers({
} }
case 'permission_cancelled': { case 'permission_cancelled': {
if (msg.requestId) { if (msg.requestId && sid === activeViewSessionId) {
setPendingPermissionRequests((prev) => prev.filter((r: PendingPermissionRequest) => r.requestId !== msg.requestId)); setPendingPermissionRequests((prev) => prev.filter((r: PendingPermissionRequest) => r.requestId !== msg.requestId));
} }
break; break;

View File

@@ -452,14 +452,31 @@ export function useChatSessionState({
return; return;
} }
const sessionKey = `${selectedSession.id}:${selectedProject.projectId}`; const selectedSessionId = selectedSession.id;
const sessionKey = `${selectedSessionId}:${selectedProject.projectId}`;
const subscribeToSelectedSession = () => {
if (!ws) {
return;
}
statusCheckSentAtRef.current.set(selectedSessionId, Date.now());
sendMessage({
type: 'chat.subscribe',
sessions: [{
sessionId: selectedSessionId,
lastSeq: lastSeqRef.current.get(selectedSessionId) ?? 0,
}],
});
};
// Skip if already loaded and fresh // Skip if already loaded and fresh
if (lastLoadedSessionKeyRef.current === sessionKey && sessionStore.has(selectedSession.id) && !sessionStore.isStale(selectedSession.id)) { if (lastLoadedSessionKeyRef.current === sessionKey && sessionStore.has(selectedSessionId) && !sessionStore.isStale(selectedSessionId)) {
subscribeToSelectedSession();
return; return;
} }
const sessionChanged = currentSessionId !== null && currentSessionId !== selectedSession.id; const sessionChanged = currentSessionId !== null && currentSessionId !== selectedSessionId;
if (sessionChanged) { if (sessionChanged) {
resetStreamingState(); resetStreamingState();
} }
@@ -482,29 +499,20 @@ export function useChatSessionState({
setTokenBudget(null); setTokenBudget(null);
} }
setCurrentSessionId(selectedSession.id); setCurrentSessionId(selectedSessionId);
// Subscribe to the session's live run (if any): the ack reconciles the // Subscribe to the session's live run (if any): the ack reconciles the
// processing indicator, re-attaches a mid-flight stream to this socket, // processing indicator, re-attaches a mid-flight stream to this socket,
// and replays any live events missed since `lastSeq`. Recording the send // and replays any live events missed since `lastSeq`. Recording the send
// time lets the ack handler discard idle acks that a newer request has // time lets the ack handler discard idle acks that a newer request has
// since outdated. // since outdated.
if (ws) { subscribeToSelectedSession();
statusCheckSentAtRef.current.set(selectedSession.id, Date.now());
sendMessage({
type: 'chat.subscribe',
sessions: [{
sessionId: selectedSession.id,
lastSeq: lastSeqRef.current.get(selectedSession.id) ?? 0,
}],
});
}
lastLoadedSessionKeyRef.current = sessionKey; lastLoadedSessionKeyRef.current = sessionKey;
// Fetch from server → store updates → chatMessages re-derives automatically // Fetch from server → store updates → chatMessages re-derives automatically
setIsLoadingSessionMessages(true); setIsLoadingSessionMessages(true);
sessionStore.fetchFromServer(selectedSession.id, { sessionStore.fetchFromServer(selectedSessionId, {
limit: MESSAGES_PER_PAGE, limit: MESSAGES_PER_PAGE,
offset: 0, offset: 0,
}).then(slot => { }).then(slot => {

View File

@@ -6,7 +6,7 @@ export interface SessionActivity {
canInterrupt: boolean; canInterrupt: boolean;
/** /**
* When this request was first marked as processing (client clock). Drives * When this request was first marked as processing (client clock). Drives
* the elapsed-time display and the stale `session-status` reply guard. * the elapsed-time display and the stale `chat_subscribed` idle-ack guard.
*/ */
startedAt: number; startedAt: number;
} }

View File

@@ -14,6 +14,11 @@ export const languages = [
label: 'English', label: 'English',
nativeName: 'English', nativeName: 'English',
}, },
{
value: 'fr',
label: 'French',
nativeName: 'Français',
},
{ {
value: 'ko', value: 'ko',
label: 'Korean', label: 'Korean',
@@ -48,6 +53,8 @@ export const languages = [
value: 'tr', value: 'tr',
label: 'Turkish', label: 'Turkish',
nativeName: 'Türkçe', nativeName: 'Türkçe',
},
{
value: 'it', value: 'it',
label: 'Italian', label: 'Italian',
nativeName: 'Italiano', nativeName: 'Italiano',

View File

@@ -0,0 +1,37 @@
{
"login": {
"title": "Bon retour",
"description": "Connectez-vous à votre compte CloudCLI auto-hébergé",
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"submit": "Se connecter",
"loading": "Connexion en cours...",
"errors": {
"invalidCredentials": "Nom d'utilisateur ou mot de passe incorrect",
"requiredFields": "Veuillez remplir tous les champs",
"networkError": "Erreur réseau. Veuillez réessayer."
},
"placeholders": {
"username": "Entrez votre nom d'utilisateur",
"password": "Entrez votre mot de passe"
}
},
"register": {
"title": "Créer un compte",
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"confirmPassword": "Confirmer le mot de passe",
"submit": "Créer le compte",
"loading": "Création du compte...",
"errors": {
"passwordMismatch": "Les mots de passe ne correspondent pas",
"usernameTaken": "Ce nom d'utilisateur est déjà pris",
"weakPassword": "Le mot de passe est trop faible"
}
},
"logout": {
"title": "Se déconnecter",
"confirm": "Êtes-vous sûr de vouloir vous déconnecter ?",
"button": "Se déconnecter"
}
}

View File

@@ -0,0 +1,241 @@
{
"codeBlock": {
"copy": "Copier",
"copied": "Copié",
"copyCode": "Copier le code"
},
"copyMessage": {
"copy": "Copier le message",
"copied": "Message copié",
"selectFormat": "Sélectionner le format de copie",
"copyAsMarkdown": "Copier en markdown",
"copyAsText": "Copier en texte brut"
},
"messageTypes": {
"user": "U",
"error": "Erreur",
"tool": "Outil",
"claude": "Claude",
"cursor": "Cursor",
"codex": "Codex",
"gemini": "Gemini",
"opencode": "OpenCode"
},
"tools": {
"settings": "Paramètres de l'outil",
"error": "Erreur de l'outil",
"result": "Résultat de l'outil",
"viewParams": "Voir les paramètres d'entrée",
"viewRawParams": "Voir les paramètres bruts",
"viewDiff": "Voir les différences pour",
"creatingFile": "Création du fichier :",
"updatingTodo": "Mise à jour de la liste de tâches",
"read": "Lire",
"readFile": "Lire le fichier",
"updateTodo": "Mettre à jour la liste de tâches",
"readTodo": "Lire la liste de tâches",
"searchResults": "résultats"
},
"search": {
"found": "{{count}} {{type}} trouvé(s)",
"file": "fichier",
"files": "fichiers",
"pattern": "motif :",
"in": "dans :"
},
"fileOperations": {
"updated": "Fichier mis à jour avec succès",
"created": "Fichier créé avec succès",
"written": "Fichier écrit avec succès",
"diff": "Diff",
"newFile": "Nouveau fichier",
"viewContent": "Voir le contenu du fichier",
"viewFullOutput": "Voir la sortie complète ({{count}} caractères)",
"contentDisplayed": "Le contenu du fichier est affiché dans la vue diff ci-dessus"
},
"interactive": {
"title": "Invite interactive",
"waiting": "En attente de votre réponse dans le CLI",
"instruction": "Veuillez sélectionner une option dans votre terminal où Claude s'exécute.",
"selectedOption": "✓ Claude a sélectionné l'option {{number}}",
"instructionDetail": "Dans le CLI, vous sélectionneriez cette option de manière interactive avec les touches fléchées ou en tapant le numéro."
},
"thinking": {
"title": "Réflexion...",
"emoji": "💭 Réflexion..."
},
"json": {
"response": "Réponse JSON"
},
"permissions": {
"grant": "Autoriser {{tool}}",
"added": "Permission ajoutée",
"addTo": "Ajoute {{entry}} aux outils autorisés.",
"retry": "Permission enregistrée. Relancez la requête pour utiliser l'outil.",
"error": "Impossible de mettre à jour les permissions. Veuillez réessayer.",
"openSettings": "Ouvrir les paramètres"
},
"todo": {
"updated": "La liste de tâches a été mise à jour avec succès",
"current": "Liste de tâches actuelle"
},
"plan": {
"viewPlan": "📋 Voir le plan d'implémentation",
"title": "Plan d'implémentation"
},
"usageLimit": {
"resetAt": "Limite d'utilisation Claude atteinte. Votre limite sera réinitialisée à **{{time}} {{timezone}}** - {{date}}"
},
"codex": {
"permissionMode": "Mode de permission",
"modes": {
"default": "Mode par défaut",
"auto": "Mode automatique",
"acceptEdits": "Accepter les modifications",
"bypassPermissions": "Contourner les permissions",
"plan": "Mode planification"
},
"descriptions": {
"default": "Seules les commandes de confiance (ls, cat, grep, git status, etc.) s'exécutent automatiquement. Les autres commandes sont ignorées. Peut écrire dans l'espace de travail.",
"auto": "Un classifieur de modèle décide pour chaque appel d'outil d'approuver ou refuser. Mode mains libres, mais plus sûr que le contournement — des refus peuvent toujours se produire.",
"acceptEdits": "Toutes les commandes s'exécutent automatiquement dans l'espace de travail. Mode entièrement automatique avec exécution sandboxée.",
"bypassPermissions": "Accès système complet sans restrictions. Toutes les commandes s'exécutent automatiquement avec accès disque et réseau complet. À utiliser avec précaution.",
"plan": "Mode planification - aucune commande n'est exécutée"
},
"technicalDetails": "Détails techniques"
},
"gemini": {
"permissionMode": "Mode de permission Gemini",
"description": "Contrôlez comment Gemini CLI gère les approbations d'opérations.",
"modes": {
"default": {
"title": "Standard (demander l'approbation)",
"description": "Gemini demandera une approbation avant d'exécuter des commandes, d'écrire des fichiers et de récupérer des ressources web."
},
"autoEdit": {
"title": "Modification automatique (ignorer les approbations de fichiers)",
"description": "Gemini approuvera automatiquement les modifications de fichiers et les récupérations web, mais demandera toujours pour les commandes shell."
},
"yolo": {
"title": "YOLO (contourner toutes les permissions)",
"description": "Gemini exécutera toutes les opérations sans demander d'approbation. Utilisez avec prudence."
}
}
},
"input": {
"placeholder": "Tapez / pour les commandes, @ pour les fichiers, ou posez une question à {{provider}}...",
"placeholderDefault": "Tapez votre message...",
"disabled": "Saisie désactivée",
"attachFiles": "Joindre des fichiers",
"attachImages": "Joindre des images",
"send": "Envoyer",
"stop": "Arrêter",
"hintText": {
"ctrlEnter": "Ctrl+Entrée pour envoyer • Maj+Entrée pour nouvelle ligne • Tab pour changer de mode • / pour les commandes slash",
"enter": "Entrée pour envoyer • Maj+Entrée pour nouvelle ligne • Tab pour changer de mode • / pour les commandes slash"
},
"clickToChangeMode": "Cliquez pour changer le mode de permission (ou appuyez sur Tab dans la saisie)",
"showAllCommands": "Afficher toutes les commandes",
"clearInput": "Effacer la saisie",
"scrollToBottom": "Défiler vers le bas"
},
"providerSelection": {
"title": "Choisissez votre assistant IA",
"description": "Sélectionnez un fournisseur pour démarrer une nouvelle conversation",
"selectModel": "Sélectionner un modèle",
"providerInfo": {
"anthropic": "par Anthropic",
"openai": "par OpenAI",
"cursorEditor": "Éditeur de code IA",
"google": "par Google"
},
"readyPrompt": {
"claude": "Prêt à utiliser Claude avec {{model}}. Commencez à taper votre message ci-dessous.",
"cursor": "Prêt à utiliser Cursor avec {{model}}. Commencez à taper votre message ci-dessous.",
"codex": "Prêt à utiliser Codex avec {{model}}. Commencez à taper votre message ci-dessous.",
"gemini": "Prêt à utiliser Gemini avec {{model}}. Commencez à taper votre message ci-dessous.",
"opencode": "Prêt à utiliser OpenCode avec {{model}}. Commencez à taper votre message ci-dessous.",
"default": "Sélectionnez un fournisseur ci-dessus pour commencer"
},
"pressToSearch": "Appuyez sur <kbd>{{shortcut}}</kbd> pour rechercher sessions, fichiers et commits"
},
"session": {
"continue": {
"title": "Continuer votre conversation",
"description": "Posez des questions sur votre code, demandez des modifications ou obtenez de l'aide pour vos tâches de développement"
},
"loading": {
"olderMessages": "Chargement des messages précédents...",
"sessionMessages": "Chargement des messages de la session..."
},
"messages": {
"showingOf": "Affichage de {{shown}} sur {{total}} messages",
"scrollToLoad": "Faites défiler vers le haut pour charger plus",
"showingLast": "Affichage des {{count}} derniers messages ({{total}} au total)",
"loadEarlier": "Charger les messages précédents",
"loadAll": "Charger tous les messages",
"loadingAll": "Chargement de tous les messages...",
"allLoaded": "Tous les messages chargés",
"perfWarning": "Tous les messages chargés — le défilement peut être plus lent. Cliquez sur « Défiler vers le bas » pour rétablir les performances."
}
},
"shell": {
"selectProject": {
"title": "Sélectionner un projet",
"description": "Choisissez un projet pour ouvrir un shell interactif dans ce répertoire"
},
"status": {
"newSession": "Nouvelle session",
"initializing": "Initialisation...",
"restarting": "Redémarrage..."
},
"actions": {
"disconnect": "Déconnecter",
"disconnectTitle": "Se déconnecter du shell",
"restart": "Redémarrer",
"restartTitle": "Redémarrer le shell",
"connect": "Continuer dans le shell",
"connectTitle": "Se connecter au shell"
},
"loading": "Chargement du terminal...",
"connecting": "Connexion au shell...",
"startSession": "Démarrer une nouvelle session Claude",
"resumeSession": "Reprendre la session : {{displayName}}...",
"runCommand": "Exécuter {{command}} dans {{projectName}}",
"startCli": "Démarrage du CLI Claude dans {{projectName}}",
"defaultCommand": "commande"
},
"claudeStatus": {
"actions": {
"thinking": "Réflexion",
"processing": "Traitement",
"analyzing": "Analyse",
"working": "Travail",
"computing": "Calcul",
"reasoning": "Raisonnement"
},
"state": {
"live": "En direct",
"paused": "En pause"
},
"elapsed": {
"seconds": "{{count}}s",
"minutesSeconds": "{{minutes}}m {{seconds}}s",
"label": "{{time}} écoulé",
"startingNow": "Démarrage"
},
"controls": {
"stopGeneration": "Arrêter la génération",
"pressEscToStop": "Appuyez sur Échap à tout moment pour arrêter"
},
"providers": {
"assistant": "Assistant"
}
},
"projectSelection": {
"startChatWithProvider": "Sélectionnez un projet pour commencer à chatter avec {{provider}}"
},
"tasks": {
"nextTaskPrompt": "Commencer la prochaine tâche"
}
}

View File

@@ -0,0 +1,36 @@
{
"toolbar": {
"changes": "modifications",
"previousChange": "Modification précédente",
"nextChange": "Modification suivante",
"hideDiff": "Masquer la mise en évidence des différences",
"showDiff": "Afficher la mise en évidence des différences",
"settings": "Paramètres de l'éditeur",
"collapse": "Réduire l'éditeur",
"expand": "Étendre l'éditeur en pleine largeur"
},
"loading": "Chargement de {{fileName}}...",
"header": {
"showingChanges": "Affichage des modifications"
},
"actions": {
"download": "Télécharger le fichier",
"save": "Enregistrer",
"saving": "Enregistrement...",
"saved": "Enregistré !",
"exitFullscreen": "Quitter le plein écran",
"fullscreen": "Plein écran",
"close": "Fermer",
"previewMarkdown": "Aperçu markdown",
"editMarkdown": "Modifier le markdown"
},
"footer": {
"lines": "Lignes :",
"characters": "Caractères :",
"shortcuts": "Ctrl+S pour enregistrer • Échap pour fermer"
},
"binaryFile": {
"title": "Fichier binaire",
"message": "Le fichier \"{{fileName}}\" ne peut pas être affiché dans l'éditeur de texte car c'est un fichier binaire."
}
}

View File

@@ -0,0 +1,267 @@
{
"buttons": {
"save": "Enregistrer",
"cancel": "Annuler",
"delete": "Supprimer",
"create": "Créer",
"edit": "Modifier",
"close": "Fermer",
"confirm": "Confirmer",
"submit": "Soumettre",
"retry": "Réessayer",
"refresh": "Actualiser",
"search": "Rechercher",
"clear": "Effacer",
"copy": "Copier",
"download": "Télécharger",
"upload": "Envoyer",
"browse": "Parcourir"
},
"tabs": {
"chat": "Chat",
"shell": "Terminal",
"files": "Fichiers",
"git": "Contrôle de source",
"tasks": "Tâches"
},
"status": {
"loading": "Chargement...",
"success": "Succès",
"error": "Erreur",
"failed": "Échec",
"pending": "En attente",
"completed": "Terminé",
"inProgress": "En cours"
},
"messages": {
"savedSuccessfully": "Enregistré avec succès",
"deletedSuccessfully": "Supprimé avec succès",
"updatedSuccessfully": "Mis à jour avec succès",
"operationFailed": "Opération échouée",
"networkError": "Erreur réseau. Vérifiez votre connexion.",
"unauthorized": "Non autorisé. Veuillez vous connecter.",
"notFound": "Introuvable",
"invalidInput": "Entrée invalide",
"requiredField": "Ce champ est obligatoire",
"unknownError": "Une erreur inconnue s'est produite"
},
"navigation": {
"settings": "Paramètres",
"home": "Accueil",
"back": "Retour",
"next": "Suivant",
"previous": "Précédent",
"logout": "Déconnexion"
},
"common": {
"language": "Langue",
"theme": "Thème",
"darkMode": "Mode sombre",
"lightMode": "Mode clair",
"name": "Nom",
"description": "Description",
"enabled": "Activé",
"disabled": "Désactivé",
"optional": "Optionnel",
"version": "Version",
"select": "Sélectionner",
"selectAll": "Tout sélectionner",
"deselectAll": "Tout désélectionner"
},
"time": {
"justNow": "À l'instant",
"minutesAgo": "Il y a {{count}} min",
"hoursAgo": "Il y a {{count}} h",
"daysAgo": "Il y a {{count}} j",
"yesterday": "Hier"
},
"fileOperations": {
"newFile": "Nouveau fichier",
"newFolder": "Nouveau dossier",
"rename": "Renommer",
"move": "Déplacer",
"copyPath": "Copier le chemin",
"openInEditor": "Ouvrir dans l'éditeur"
},
"mainContent": {
"loading": "Chargement de CloudCLI",
"settingUpWorkspace": "Préparation de votre espace de travail...",
"chooseProject": "Choisissez votre projet",
"selectProjectDescription": "Sélectionnez un projet dans la barre latérale pour commencer à coder avec Claude. Chaque projet contient vos sessions de chat et l'historique des fichiers.",
"tip": "Astuce",
"createProjectMobile": "Appuyez sur le bouton menu ci-dessus pour accéder aux projets",
"createProjectDesktop": "Créez un nouveau projet en cliquant sur l'icône de dossier dans la barre latérale",
"newSession": "Nouvelle session",
"untitledSession": "Session sans titre",
"projectFiles": "Fichiers du projet"
},
"fileTree": {
"loading": "Chargement des fichiers...",
"files": "Fichiers",
"simpleView": "Vue simple",
"compactView": "Vue compacte",
"detailedView": "Vue détaillée",
"searchPlaceholder": "Rechercher fichiers et dossiers...",
"clearSearch": "Effacer la recherche",
"name": "Nom",
"size": "Taille",
"modified": "Modifié",
"permissions": "Permissions",
"noFilesFound": "Aucun fichier trouvé",
"checkProjectPath": "Vérifiez si le chemin du projet est accessible",
"noMatchesFound": "Aucun résultat",
"tryDifferentSearch": "Essayez un autre terme ou effacez la recherche",
"justNow": "à l'instant",
"minAgo": "il y a {{count}} min",
"hoursAgo": "il y a {{count}} h",
"daysAgo": "il y a {{count}} j",
"newFile": "Nouveau fichier (Cmd+N)",
"newFolder": "Nouveau dossier (Cmd+Maj+N)",
"refresh": "Actualiser",
"collapseAll": "Tout réduire",
"context": {
"rename": "Renommer",
"delete": "Supprimer",
"copyPath": "Copier le chemin",
"download": "Télécharger",
"newFile": "Nouveau fichier",
"newFolder": "Nouveau dossier",
"refresh": "Actualiser",
"menuLabel": "Menu contextuel du fichier",
"loading": "Chargement..."
}
},
"projectWizard": {
"title": "Créer un nouveau projet",
"steps": {
"type": "Type",
"configure": "Configurer",
"confirm": "Confirmer"
},
"step1": {
"question": "Avez-vous déjà un espace de travail, ou souhaitez-vous en créer un nouveau ?",
"existing": {
"title": "Espace de travail existant",
"description": "J'ai déjà un espace de travail sur mon serveur et je veux juste l'ajouter à la liste des projets"
},
"new": {
"title": "Nouvel espace de travail",
"description": "Créer un nouvel espace de travail, éventuellement cloné depuis un dépôt GitHub"
}
},
"step2": {
"existingPath": "Chemin de l'espace de travail",
"newPath": "Chemin de l'espace de travail",
"existingPlaceholder": "/chemin/vers/espace-de-travail",
"newPlaceholder": "/chemin/vers/nouvel-espace",
"existingHelp": "Chemin complet vers votre répertoire d'espace de travail existant",
"newHelp": "Chemin complet vers votre répertoire d'espace de travail",
"githubUrl": "URL GitHub (optionnel)",
"githubPlaceholder": "https://github.com/utilisateur/depot",
"githubHelp": "Optionnel : fournissez une URL GitHub pour cloner un dépôt",
"githubAuth": "Authentification GitHub (optionnel)",
"githubAuthHelp": "Uniquement requis pour les dépôts privés. Les dépôts publics peuvent être clonés sans authentification.",
"loadingTokens": "Chargement des tokens enregistrés...",
"storedToken": "Token enregistré",
"newToken": "Nouveau token",
"nonePublic": "Aucun (Public)",
"selectToken": "Sélectionner un token",
"selectTokenPlaceholder": "-- Sélectionner un token --",
"tokenPlaceholder": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"tokenHelp": "Ce token sera utilisé uniquement pour cette opération",
"publicRepoInfo": "Les dépôts publics ne nécessitent pas d'authentification. Vous pouvez ignorer le token pour cloner un dépôt public.",
"noTokensHelp": "Aucun token enregistré. Vous pouvez en ajouter dans Paramètres → Clés API.",
"optionalTokenPublic": "Token GitHub (optionnel pour les dépôts publics)",
"tokenPublicPlaceholder": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (laisser vide pour les dépôts publics)"
},
"step3": {
"reviewConfig": "Vérifiez votre configuration",
"existingWorkspace": "Espace de travail existant",
"newWorkspace": "Nouvel espace de travail",
"path": "Chemin :",
"cloneFrom": "Cloner depuis :",
"authentication": "Authentification :",
"usingStoredToken": "Utilisation du token enregistré :",
"usingProvidedToken": "Utilisation du token fourni",
"noAuthentication": "Sans authentification",
"sshKey": "Clé SSH",
"existingInfo": "L'espace de travail sera ajouté à votre liste de projets et disponible pour les sessions Claude/Cursor.",
"newWithClone": "Le dépôt sera cloné depuis ce dossier.",
"newEmpty": "L'espace de travail sera ajouté à votre liste de projets et disponible pour les sessions Claude/Cursor.",
"cloningRepository": "Clonage du dépôt..."
},
"buttons": {
"cancel": "Annuler",
"back": "Retour",
"next": "Suivant",
"createProject": "Créer le projet",
"creating": "Création...",
"cloning": "Clonage..."
},
"errors": {
"selectType": "Veuillez indiquer si vous avez un espace de travail existant ou si vous souhaitez en créer un nouveau",
"providePath": "Veuillez fournir un chemin d'espace de travail",
"failedToCreate": "Échec de la création de l'espace de travail",
"failedToCreateFolder": "Échec de la création du dossier"
}
},
"notifications": {
"genericTool": "un outil",
"codes": {
"generic": {
"info": {
"title": "Notification"
}
},
"permission": {
"required": {
"title": "Action requise",
"body": "{{toolName}} attend votre décision."
}
},
"run": {
"stopped": {
"title": "Exécution arrêtée",
"body": "Raison : {{reason}}"
},
"failed": {
"title": "Exécution échouée"
}
},
"agent": {
"notification": {
"title": "Notification de l'agent"
}
}
}
},
"versionUpdate": {
"title": "Mise à jour disponible",
"newVersionReady": "Une nouvelle version est prête",
"currentVersion": "Version actuelle",
"latestVersion": "Dernière version",
"whatsNew": "Nouveautés :",
"viewFullRelease": "Voir les notes de version complètes",
"updateProgress": "Progression de la mise à jour :",
"manualUpgrade": "Mise à jour manuelle :",
"npmUpgradeCommand": "npm install -g @cloudcli-ai/cloudcli@latest",
"manualUpgradeHint": "Ou cliquez sur « Mettre à jour maintenant » pour lancer la mise à jour automatiquement.",
"updateCompleted": "Mise à jour effectuée avec succès !",
"restartServer": "Veuillez redémarrer le serveur pour appliquer les modifications.",
"updateFailed": "Échec de la mise à jour",
"buttons": {
"close": "Fermer",
"later": "Plus tard",
"copyCommand": "Copier la commande",
"updateNow": "Mettre à jour maintenant",
"updating": "Mise à jour..."
},
"ariaLabels": {
"closeModal": "Fermer la fenêtre de mise à jour",
"showSidebar": "Afficher la barre latérale",
"settings": "Paramètres",
"updateAvailable": "Mise à jour disponible",
"closeSidebar": "Masquer la barre latérale"
}
}
}

View File

@@ -0,0 +1,548 @@
{
"title": "Paramètres",
"tabs": {
"account": "Compte",
"permissions": "Permissions",
"mcpServers": "Serveurs MCP",
"appearance": "Apparence"
},
"account": {
"title": "Compte",
"language": "Langue",
"languageLabel": "Langue d'affichage",
"languageDescription": "Choisissez votre langue préférée pour l'interface",
"username": "Nom d'utilisateur",
"email": "E-mail",
"profile": "Profil",
"changePassword": "Changer le mot de passe"
},
"mcp": {
"title": "Serveurs MCP",
"addServer": "Ajouter un serveur",
"editServer": "Modifier le serveur",
"deleteServer": "Supprimer le serveur",
"serverName": "Nom du serveur",
"serverType": "Type de serveur",
"config": "Configuration",
"testConnection": "Tester la connexion",
"status": "Statut",
"connected": "Connecté",
"disconnected": "Déconnecté",
"scope": {
"label": "Portée",
"user": "Utilisateur",
"project": "Projet"
}
},
"appearance": {
"title": "Apparence",
"theme": "Thème",
"codeEditor": "Éditeur de code",
"editorTheme": "Thème de l'éditeur",
"wordWrap": "Retour à la ligne",
"showMinimap": "Afficher la minimap",
"lineNumbers": "Numéros de ligne",
"fontSize": "Taille de police"
},
"actions": {
"saveChanges": "Enregistrer les modifications",
"resetToDefaults": "Rétablir les valeurs par défaut",
"cancelChanges": "Annuler les modifications"
},
"quickSettings": {
"title": "Paramètres rapides",
"sections": {
"appearance": "Apparence",
"toolDisplay": "Affichage des outils",
"viewOptions": "Options d'affichage",
"inputSettings": "Paramètres de saisie"
},
"darkMode": "Mode sombre",
"autoExpandTools": "Développer automatiquement les outils",
"showRawParameters": "Afficher les paramètres bruts",
"showThinking": "Afficher la réflexion",
"autoScrollToBottom": "Défilement automatique vers le bas",
"sendByCtrlEnter": "Envoyer avec Ctrl+Entrée",
"sendByCtrlEnterDescription": "Lorsqu'activé, appuyer sur Ctrl+Entrée envoie le message au lieu de simplement Entrée. Utile pour les utilisateurs IME pour éviter les envois accidentels.",
"dragHandle": {
"dragging": "Glissement en cours",
"closePanel": "Fermer le panneau de paramètres",
"openPanel": "Ouvrir le panneau de paramètres",
"draggingStatus": "Glissement...",
"toggleAndMove": "Cliquer pour basculer, glisser pour déplacer"
}
},
"terminalShortcuts": {
"title": "Raccourcis terminal",
"sectionKeys": "Touches",
"sectionNavigation": "Navigation",
"escape": "Échap",
"tab": "Tab",
"shiftTab": "Maj+Tab",
"arrowUp": "Flèche haut",
"arrowDown": "Flèche bas",
"scrollDown": "Défiler vers le bas",
"handle": {
"closePanel": "Fermer le panneau de raccourcis",
"openPanel": "Ouvrir le panneau de raccourcis"
}
},
"mainTabs": {
"label": "Paramètres",
"agents": "Agents",
"appearance": "Apparence",
"git": "Git",
"apiTokens": "API & Tokens",
"tasks": "Tâches",
"notifications": "Notifications",
"plugins": "Plugins",
"about": "À propos"
},
"notifications": {
"title": "Notifications",
"description": "Contrôlez les événements de notification que vous recevez.",
"webPush": {
"title": "Notifications push web",
"enable": "Activer les notifications push",
"disable": "Désactiver les notifications push",
"enabled": "Les notifications push sont activées",
"loading": "Mise à jour...",
"unsupported": "Les notifications push ne sont pas prises en charge dans ce navigateur.",
"denied": "Les notifications push sont bloquées. Veuillez les autoriser dans les paramètres de votre navigateur."
},
"sound": {
"title": "Son",
"description": "Jouer un court son lorsqu'une exécution de chat se termine.",
"enabled": "Activé",
"test": "Tester le son"
},
"events": {
"title": "Types d'événements",
"actionRequired": "Action requise",
"stop": "Exécution arrêtée",
"error": "Exécution échouée"
}
},
"appearanceSettings": {
"darkMode": {
"label": "Mode sombre",
"description": "Basculer entre les thèmes clair et sombre"
},
"projectSorting": {
"label": "Tri des projets",
"description": "Ordre d'affichage des projets dans la barre latérale",
"alphabetical": "Alphabétique",
"recentActivity": "Activité récente"
},
"codeEditor": {
"title": "Éditeur de code",
"theme": {
"label": "Thème de l'éditeur",
"description": "Thème par défaut pour l'éditeur de code"
},
"wordWrap": {
"label": "Retour à la ligne",
"description": "Activer le retour à la ligne par défaut dans l'éditeur"
},
"showMinimap": {
"label": "Afficher la minimap",
"description": "Afficher une minimap pour une navigation plus facile en vue diff"
},
"lineNumbers": {
"label": "Afficher les numéros de ligne",
"description": "Afficher les numéros de ligne dans l'éditeur"
},
"fontSize": {
"label": "Taille de police",
"description": "Taille de police de l'éditeur en pixels"
}
}
},
"mcpForm": {
"title": {
"add": "Ajouter un serveur MCP",
"edit": "Modifier le serveur MCP"
},
"importMode": {
"form": "Saisie via formulaire",
"json": "Import JSON"
},
"scope": {
"label": "Portée",
"userGlobal": "Utilisateur (global)",
"projectLocal": "Projet (local)",
"userDescription": "Portée utilisateur : Disponible dans tous les projets sur votre machine",
"projectDescription": "Portée locale : Disponible uniquement dans le projet sélectionné",
"cannotChange": "La portée ne peut pas être modifiée lors de la modification d'un serveur existant"
},
"fields": {
"serverName": "Nom du serveur",
"transportType": "Type de transport",
"command": "Commande",
"arguments": "Arguments (un par ligne)",
"jsonConfig": "Configuration JSON",
"url": "URL",
"envVars": "Variables d'environnement (CLÉ=valeur, une par ligne)",
"headers": "En-têtes (CLÉ=valeur, un par ligne)",
"selectProject": "Sélectionner un projet..."
},
"placeholders": {
"serverName": "mon-serveur"
},
"validation": {
"missingType": "Champ requis manquant : type",
"stdioRequiresCommand": "Le type stdio nécessite un champ command",
"httpRequiresUrl": "Le type {{type}} nécessite un champ url",
"invalidJson": "Format JSON invalide",
"jsonHelp": "Collez la configuration de votre serveur MCP en format JSON. Exemples :",
"jsonExampleStdio": "• stdio : {\"type\":\"stdio\",\"command\":\"npx\",\"args\":[\"@upstash/context7-mcp\"]}",
"jsonExampleHttp": "• http/sse : {\"type\":\"http\",\"url\":\"https://api.exemple.com/mcp\"}"
},
"configDetails": "Détails de configuration (depuis {{configFile}})",
"projectPath": "Chemin : {{path}}",
"actions": {
"cancel": "Annuler",
"saving": "Enregistrement...",
"addServer": "Ajouter le serveur",
"updateServer": "Mettre à jour le serveur"
}
},
"saveStatus": {
"success": "Paramètres enregistrés avec succès !",
"error": "Échec de l'enregistrement des paramètres",
"saving": "Enregistrement..."
},
"footerActions": {
"save": "Enregistrer les paramètres",
"cancel": "Annuler"
},
"git": {
"title": "Configuration Git",
"description": "Configurez votre identité git pour les commits. Ces paramètres seront appliqués globalement via git config --global",
"name": {
"label": "Nom Git",
"help": "Votre nom pour les commits git"
},
"email": {
"label": "E-mail Git",
"help": "Votre e-mail pour les commits git"
},
"actions": {
"save": "Enregistrer la configuration",
"saving": "Enregistrement..."
},
"status": {
"success": "Enregistré avec succès"
}
},
"apiKeys": {
"title": "Clés API",
"description": "Générez des clés API pour accéder à l'API externe depuis d'autres applications.",
"newKey": {
"alertTitle": "⚠️ Sauvegardez votre clé API",
"alertMessage": "C'est la seule fois que vous verrez cette clé. Stockez-la en lieu sûr.",
"iveSavedIt": "Je l'ai sauvegardée"
},
"form": {
"placeholder": "Nom de la clé API (ex. : Serveur de production)",
"createButton": "Créer",
"cancelButton": "Annuler"
},
"newButton": "Nouvelle clé API",
"empty": "Aucune clé API créée pour l'instant.",
"list": {
"created": "Créée :",
"lastUsed": "Dernière utilisation :"
},
"confirmDelete": "Êtes-vous sûr de vouloir supprimer cette clé API ?",
"status": {
"active": "Active",
"inactive": "Inactive"
},
"github": {
"title": "Tokens GitHub",
"description": "Ajoutez des tokens d'accès personnel GitHub pour cloner des dépôts privés via l'API externe.",
"descriptionAlt": "Ajoutez des tokens d'accès personnel GitHub pour cloner des dépôts privés. Vous pouvez aussi passer des tokens directement dans les requêtes API sans les stocker.",
"addButton": "Ajouter un token",
"form": {
"namePlaceholder": "Nom du token (ex. : Dépôts personnels)",
"tokenPlaceholder": "Token d'accès personnel GitHub (ghp_...)",
"descriptionPlaceholder": "Description (optionnel)",
"addButton": "Ajouter le token",
"cancelButton": "Annuler",
"howToCreate": "Comment créer un token d'accès personnel GitHub →"
},
"empty": "Aucun token GitHub ajouté pour l'instant.",
"added": "Ajouté :",
"confirmDelete": "Êtes-vous sûr de vouloir supprimer ce token GitHub ?"
},
"apiDocsLink": "Documentation API",
"documentation": {
"title": "Documentation de l'API externe",
"description": "Apprenez à utiliser l'API externe pour déclencher des sessions Claude/Cursor depuis vos applications.",
"viewLink": "Voir la documentation API →"
},
"loading": "Chargement...",
"version": {
"updateAvailable": "Mise à jour disponible : v{{version}}"
}
},
"tasks": {
"checking": "Vérification de l'installation TaskMaster...",
"notInstalled": {
"title": "CLI TaskMaster AI non installé",
"description": "Le CLI TaskMaster est requis pour utiliser les fonctionnalités de gestion des tâches. Installez-le pour commencer :",
"installCommand": "npm install -g task-master-ai",
"viewOnGitHub": "Voir sur GitHub",
"afterInstallation": "Après l'installation :",
"steps": {
"restart": "Redémarrez cette application",
"autoAvailable": "Les fonctionnalités TaskMaster deviendront automatiquement disponibles",
"initCommand": "Utilisez task-master init dans votre répertoire de projet"
}
},
"settings": {
"enableLabel": "Activer l'intégration TaskMaster",
"enableDescription": "Afficher les tâches TaskMaster, les bannières et les indicateurs dans la barre latérale"
}
},
"agents": {
"authStatus": {
"checking": "Vérification...",
"connected": "Connecté",
"notConnected": "Non connecté",
"disconnected": "Déconnecté",
"checkingAuth": "Vérification du statut d'authentification...",
"loggedInAs": "Connecté en tant que {{email}}",
"authenticatedUser": "utilisateur authentifié"
},
"account": {
"claude": {
"description": "Assistant IA Claude d'Anthropic"
},
"cursor": {
"description": "Éditeur de code IA Cursor"
},
"codex": {
"description": "Assistant IA Codex d'OpenAI"
},
"gemini": {
"description": "Assistant IA Gemini de Google"
},
"opencode": {
"description": "Assistant CLI OpenCode"
}
},
"connectionStatus": "Statut de la connexion",
"login": {
"title": "Connexion",
"reAuthenticate": "Se ré-authentifier",
"description": "Connectez-vous à votre compte {{agent}} pour activer les fonctionnalités IA",
"reAuthDescription": "Connectez-vous avec un autre compte ou actualisez les identifiants",
"button": "Se connecter",
"reLoginButton": "Se reconnecter"
},
"error": "Erreur : {{error}}"
},
"permissions": {
"title": "Paramètres de permission",
"skipPermissions": {
"label": "Ignorer les invites de permission (à utiliser avec précaution)",
"claudeDescription": "Équivalent au flag --dangerously-skip-permissions",
"cursorDescription": "Équivalent au flag -f dans le CLI Cursor"
},
"allowedTools": {
"title": "Outils autorisés",
"description": "Outils automatiquement autorisés sans demande de permission",
"placeholder": "ex. : \"Bash(git log:*)\" ou \"Write\"",
"quickAdd": "Ajout rapide d'outils courants :",
"empty": "Aucun outil autorisé configuré"
},
"blockedTools": {
"title": "Outils bloqués",
"description": "Outils automatiquement bloqués sans demande de permission",
"placeholder": "ex. : \"Bash(rm:*)\"",
"empty": "Aucun outil bloqué configuré"
},
"allowedCommands": {
"title": "Commandes shell autorisées",
"description": "Commandes shell automatiquement autorisées sans demande",
"placeholder": "ex. : \"Shell(ls)\" ou \"Shell(git status)\"",
"quickAdd": "Ajout rapide de commandes courantes :",
"empty": "Aucune commande autorisée configurée"
},
"blockedCommands": {
"title": "Commandes shell bloquées",
"description": "Commandes shell automatiquement bloquées",
"placeholder": "ex. : \"Shell(rm -rf)\" ou \"Shell(sudo)\"",
"empty": "Aucune commande bloquée configurée"
},
"toolExamples": {
"title": "Exemples de motifs d'outils :",
"bashGitLog": "- Autoriser toutes les commandes git log",
"bashGitDiff": "- Autoriser toutes les commandes git diff",
"write": "- Autoriser toutes les utilisations de l'outil Write",
"bashRm": "- Bloquer toutes les commandes rm (dangereux)"
},
"shellExamples": {
"title": "Exemples de commandes shell :",
"ls": "- Autoriser la commande ls",
"gitStatus": "- Autoriser git status",
"npmInstall": "- Autoriser npm install",
"rmRf": "- Bloquer la suppression récursive"
},
"codex": {
"permissionMode": "Mode de permission",
"description": "Contrôle comment Codex gère les modifications de fichiers et l'exécution des commandes",
"modes": {
"default": {
"title": "Par défaut",
"description": "Seules les commandes de confiance (ls, cat, grep, git status, etc.) s'exécutent automatiquement. Les autres sont ignorées. Peut écrire dans l'espace de travail."
},
"acceptEdits": {
"title": "Accepter les modifications",
"description": "Toutes les commandes s'exécutent automatiquement dans l'espace de travail. Mode entièrement automatique avec exécution sandboxée."
},
"bypassPermissions": {
"title": "Contourner les permissions",
"description": "Accès système complet sans restrictions. Toutes les commandes s'exécutent automatiquement avec accès disque et réseau complet. À utiliser avec précaution."
}
},
"technicalDetails": "Détails techniques",
"technicalInfo": {
"default": "sandboxMode=workspace-write, approvalPolicy=untrusted. Commandes de confiance : cat, cd, grep, head, ls, pwd, tail, git status/log/diff/show, find (sans -exec), etc.",
"acceptEdits": "sandboxMode=workspace-write, approvalPolicy=never. Toutes les commandes s'exécutent automatiquement dans le répertoire du projet.",
"bypassPermissions": "sandboxMode=danger-full-access, approvalPolicy=never. Accès système complet, à utiliser uniquement dans des environnements de confiance.",
"overrideNote": "Vous pouvez remplacer ce mode par session via le bouton de mode dans l'interface de chat."
}
},
"actions": {
"add": "Ajouter"
}
},
"mcpServers": {
"title": "Serveurs MCP",
"description": {
"claude": "Les serveurs Model Context Protocol fournissent des outils et sources de données supplémentaires à Claude",
"cursor": "Les serveurs Model Context Protocol fournissent des outils et sources de données supplémentaires à Cursor",
"codex": "Les serveurs Model Context Protocol fournissent des outils et sources de données supplémentaires à Codex",
"opencode": "Les serveurs Model Context Protocol fournissent des outils et sources de données supplémentaires à OpenCode"
},
"addButton": "Ajouter un serveur MCP",
"empty": "Aucun serveur MCP configuré",
"serverType": "Type",
"scope": {
"local": "local",
"user": "utilisateur"
},
"config": {
"command": "Commande",
"url": "URL",
"args": "Arguments",
"environment": "Environnement"
},
"tools": {
"title": "Outils",
"count": "({{count}}) :",
"more": "+{{count}} de plus"
},
"actions": {
"edit": "Modifier le serveur",
"delete": "Supprimer le serveur"
},
"help": {
"title": "À propos de Codex MCP",
"description": "Codex prend en charge les serveurs MCP basés sur stdio. Vous pouvez ajouter des serveurs qui étendent les capacités de Codex avec des outils et ressources supplémentaires."
}
},
"pluginSettings": {
"title": "Plugins",
"description": "Étendez l'interface avec des plugins personnalisés. Installez depuis git ou déposez un dossier dans ~/.claude-code-ui/plugins/",
"installPlaceholder": "https://github.com/utilisateur/mon-plugin",
"installButton": "Installer",
"installing": "Installation…",
"securityWarning": "N'installez que des plugins dont vous avez examiné le code source ou provenant d'auteurs de confiance.",
"scanningPlugins": "Analyse des plugins…",
"noPluginsInstalled": "Aucun plugin installé",
"pullLatest": "Récupérer la dernière version depuis git",
"noGitRemote": "Pas de remote git — mise à jour indisponible",
"uninstallPlugin": "Désinstaller le plugin",
"confirmUninstall": "Cliquez à nouveau pour confirmer",
"confirmUninstallMessage": "Supprimer {{name}} ? Cette action est irréversible.",
"cancel": "Annuler",
"remove": "Supprimer",
"updateFailed": "Échec de la mise à jour",
"installFailed": "Échec de l'installation",
"uninstallFailed": "Échec de la désinstallation",
"toggleFailed": "Échec du basculement",
"starterPluginLabel": "Plugin de démarrage",
"starter": "Démarrage",
"docs": "Docs",
"sections": {
"officialTitle": "Plugins officiels",
"officialDescription": "Maintenus par l'équipe CloudCLI et prêts à être installés directement.",
"unofficialTitle": "Autres plugins",
"unofficialDescription": "Plugins non officiels et intégrations d'autres utilisateurs. Examinez le code source avant d'installer."
},
"starterPlugin": {
"name": "Statistiques du projet",
"badge": "démarrage",
"description": "Nombre de fichiers, lignes de code, répartition par type de fichier et activité récente pour votre projet.",
"install": "Installer"
},
"terminalPlugin": {
"name": "Terminal",
"badge": "officiel",
"description": "Terminal intégré avec accès shell complet directement dans l'interface.",
"install": "Installer"
},
"scheduledPromptPlugin": {
"name": "Invites planifiées",
"badge": "non officiel",
"description": "Planifiez des invites d'espace de travail, consultez l'historique des exécutions et gérez les tâches locales récurrentes.",
"install": "Installer"
},
"claudeWatchPlugin": {
"name": "Claude Watch",
"badge": "non officiel",
"description": "Surveillez les sessions Claude Code longues pour détecter les blocages et exposez les contrôles de processus.",
"install": "Installer"
},
"prismCloudCLI": {
"name": "PRISM CloudCLI",
"badge": "non officiel",
"description": "Intelligence de session pour Claude Code, dans CloudCLI. Voyez pourquoi vos sessions consomment des tokens sans quitter le navigateur.",
"install": "Installer"
},
"sessionManagerPlugin": {
"name": "Sessions",
"badge": "non officiel",
"description": "Visualisez, gérez et terminez les sessions Claude Code actives.",
"install": "Installer"
},
"tokenCostCalculatorPlugin": {
"name": "Calculateur de coût en tokens",
"badge": "non officiel",
"description": "Calculez les coûts API à partir des prix des modèles et de l'utilisation des tokens, avec prise en charge des tarifs préréglés.",
"install": "Installer"
},
"taskQueuePlugin": {
"name": "File de tâches",
"badge": "non officiel",
"description": "Tableau de bord de file de tâches pour visualiser, filtrer et lancer des tâches d'agent.",
"install": "Installer"
},
"githubIssuesBoardPlugin": {
"name": "Tableau des issues GitHub",
"badge": "non officiel",
"description": "Tableau Kanban pour les issues GitHub avec synchronisation bidirectionnelle TaskMaster et installation automatique de la compétence CLI /github-task",
"install": "Installer"
},
"morePlugins": "Plus",
"enable": "Activer",
"disable": "Désactiver",
"installAriaLabel": "URL du dépôt git du plugin",
"tab": "onglet",
"runningStatus": "en cours"
}
}

View File

@@ -0,0 +1,137 @@
{
"projects": {
"title": "Projets",
"newProject": "Nouveau projet",
"deleteProject": "Supprimer le projet",
"renameProject": "Renommer le projet",
"noProjects": "Aucun projet trouvé",
"loadingProjects": "Chargement des projets...",
"searchPlaceholder": "Rechercher des projets...",
"projectNamePlaceholder": "Nom du projet",
"starred": "Favoris",
"all": "Tous",
"untitledSession": "Session sans titre",
"newSession": "Nouvelle session",
"codexSession": "Session Codex",
"fetchingProjects": "Récupération de vos projets et sessions Claude",
"projects": "projets",
"noMatchingProjects": "Aucun projet correspondant",
"tryDifferentSearch": "Essayez d'ajuster votre terme de recherche",
"runClaudeCli": "Exécutez le CLI Claude dans un répertoire de projet pour commencer"
},
"app": {
"title": "CloudCLI",
"subtitle": "Interface d'assistant de codage IA"
},
"sessions": {
"title": "Sessions",
"newSession": "Nouvelle session",
"deleteSession": "Supprimer la session",
"renameSession": "Renommer la session",
"noSessions": "Aucune session pour l'instant",
"loadingSessions": "Chargement des sessions...",
"unnamed": "Sans nom",
"loading": "Chargement...",
"showMore": "Afficher plus de sessions"
},
"tooltips": {
"viewEnvironments": "Voir les environnements",
"hideSidebar": "Masquer la barre latérale",
"createProject": "Créer un nouveau projet",
"refresh": "Actualiser les projets et sessions (Ctrl+R)",
"renameProject": "Renommer le projet (F2)",
"deleteProject": "Retirer le projet de la barre latérale (Suppr)",
"addToFavorites": "Ajouter aux favoris",
"removeFromFavorites": "Retirer des favoris",
"editSessionName": "Modifier manuellement le nom de la session",
"deleteSession": "Supprimer définitivement cette session",
"activeSessionIndicator": "Session récemment active (10 dernières minutes)",
"save": "Enregistrer",
"cancel": "Annuler",
"clearSearch": "Effacer la recherche",
"openCommandPalette": "Ouvrir la palette de commandes"
},
"navigation": {
"chat": "Chat",
"files": "Fichiers",
"git": "Git",
"terminal": "Terminal",
"tasks": "Tâches"
},
"actions": {
"refresh": "Actualiser",
"settings": "Paramètres",
"collapseAll": "Tout réduire",
"expandAll": "Tout développer",
"cancel": "Annuler",
"save": "Enregistrer",
"delete": "Supprimer",
"rename": "Renommer",
"joinCommunity": "Rejoindre la communauté",
"reportIssue": "Signaler un problème",
"starOnGithub": "Étoile sur GitHub"
},
"branding": {
"openSource": "Open Source"
},
"status": {
"active": "Actif",
"inactive": "Inactif",
"thinking": "Réflexion...",
"error": "Erreur",
"aborted": "Annulé",
"unknown": "Inconnu"
},
"time": {
"justNow": "À l'instant",
"oneMinuteAgo": "Il y a 1 min",
"minutesAgo": "Il y a {{count}} min",
"oneHourAgo": "Il y a 1 heure",
"hoursAgo": "Il y a {{count}} heures",
"oneDayAgo": "Il y a 1 jour",
"daysAgo": "Il y a {{count}} jours"
},
"messages": {
"deleteConfirm": "Êtes-vous sûr de vouloir supprimer ceci ?",
"renameSuccess": "Renommé avec succès",
"deleteSuccess": "Supprimé avec succès",
"errorOccurred": "Une erreur s'est produite",
"deleteSessionConfirm": "Êtes-vous sûr de vouloir supprimer cette session ? Cette action est irréversible.",
"deleteProjectConfirm": "Retirer ce projet de la barre latérale ? Vos fichiers, mémoires et données de session ne seront pas supprimés.",
"enterProjectPath": "Veuillez entrer un chemin de projet",
"deleteSessionFailed": "Échec de la suppression de la session. Veuillez réessayer.",
"deleteSessionError": "Erreur lors de la suppression de la session. Veuillez réessayer.",
"renameSessionFailed": "Échec du renommage de la session. Veuillez réessayer.",
"renameSessionError": "Erreur lors du renommage de la session. Veuillez réessayer.",
"deleteProjectFailed": "Échec de la suppression du projet. Veuillez réessayer.",
"deleteProjectError": "Erreur lors de la suppression du projet. Veuillez réessayer.",
"createProjectFailed": "Échec de la création du projet. Veuillez réessayer.",
"createProjectError": "Erreur lors de la création du projet. Veuillez réessayer."
},
"version": {
"updateAvailable": "Mise à jour disponible"
},
"search": {
"modeProjects": "Projets",
"modeConversations": "Conversations",
"conversationsPlaceholder": "Rechercher dans les conversations...",
"searching": "Recherche en cours...",
"noResults": "Aucun résultat trouvé",
"tryDifferentQuery": "Essayez une autre requête de recherche",
"matches_one": "{{count}} résultat",
"matches_other": "{{count}} résultats",
"projectsScanned_one": "{{count}} projet analysé",
"projectsScanned_other": "{{count}} projets analysés"
},
"deleteConfirmation": {
"deleteProject": "Supprimer le projet",
"deleteSession": "Supprimer la session",
"confirmDelete": "Que souhaitez-vous faire avec",
"sessionCount_one": "Ce projet contient {{count}} conversation.",
"sessionCount_other": "Ce projet contient {{count}} conversations.",
"removeFromSidebar": "Retirer de la barre latérale uniquement",
"deleteAllData": "Supprimer toutes les données définitivement",
"allConversationsDeleted": "Le projet sera retiré de la barre latérale. Vos fichiers, mémoires et données de session seront conservés.",
"cannotUndo": "Vous pourrez rajouter le projet ultérieurement."
}
}

View File

@@ -0,0 +1,142 @@
{
"notConfigured": {
"title": "TaskMaster AI n'est pas configuré",
"description": "TaskMaster aide à décomposer des projets complexes en tâches gérables avec une assistance IA",
"whatIsTitle": "🎯 Qu'est-ce que TaskMaster ?",
"features": {
"aiPowered": "Gestion des tâches assistée par IA : Décomposez des projets complexes en sous-tâches gérables",
"prdTemplates": "Modèles PRD : Générez des tâches à partir de documents d'exigences produit",
"dependencyTracking": "Suivi des dépendances : Comprenez les relations entre tâches et l'ordre d'exécution",
"progressVisualization": "Visualisation de l'avancement : Tableaux Kanban et analyses détaillées des tâches",
"cliIntegration": "Intégration CLI : Utilisez les commandes taskmaster pour des flux de travail avancés"
},
"initializeButton": "Initialiser TaskMaster AI"
},
"gettingStarted": {
"title": "Démarrer avec TaskMaster",
"subtitle": "TaskMaster est initialisé ! Voici la suite :",
"steps": {
"createPRD": {
"title": "Créer un document d'exigences produit (PRD)",
"description": "Discutez de votre idée de projet et créez un PRD décrivant ce que vous voulez construire.",
"addButton": "Ajouter un PRD",
"existingPRDs": "PRDs existants :"
},
"generateTasks": {
"title": "Générer des tâches à partir du PRD",
"description": "Une fois votre PRD prêt, demandez à votre assistant IA de l'analyser et TaskMaster le décomposera automatiquement en tâches gérables avec des détails d'implémentation."
},
"analyzeTasks": {
"title": "Analyser et développer les tâches",
"description": "Demandez à votre assistant IA d'analyser la complexité des tâches et de les développer en sous-tâches détaillées pour une implémentation plus facile."
},
"startBuilding": {
"title": "Commencer à construire",
"description": "Demandez à votre assistant IA de commencer à travailler sur les tâches, mettre à jour leur statut et ajouter de nouvelles tâches au fur et à mesure."
}
},
"tip": "💡 Astuce : Commencez par un PRD pour tirer le meilleur parti de la génération de tâches IA de TaskMaster"
},
"setupModal": {
"title": "Configuration TaskMaster",
"subtitle": "CLI interactif pour {{projectName}}",
"willStart": "L'initialisation de TaskMaster démarrera automatiquement",
"completed": "Configuration TaskMaster terminée ! Vous pouvez fermer cette fenêtre.",
"closeButton": "Fermer",
"closeContinueButton": "Fermer et continuer"
},
"helpGuide": {
"title": "Démarrer avec TaskMaster",
"subtitle": "Votre guide pour une gestion productive des tâches",
"examples": {
"parsePRD": "💬 Exemple :\n« Je viens d'initialiser un nouveau projet avec Claude Task Master. J'ai un PRD dans .taskmaster/docs/prd.txt. Pouvez-vous m'aider à l'analyser et configurer les tâches initiales ? »",
"expandTask": "💬 Exemple :\n« La tâche 5 semble complexe. Pouvez-vous la décomposer en sous-tâches ? »",
"addTask": "💬 Exemple :\n« Veuillez ajouter une nouvelle tâche pour implémenter le téléchargement d'images de profil utilisateur avec Cloudinary, recherchez la meilleure approche. »"
},
"moreExamples": "Voir plus d'exemples et de patterns d'utilisation →",
"proTips": {
"title": "💡 Conseils pro",
"search": "Utilisez la barre de recherche pour trouver rapidement des tâches spécifiques",
"views": "Basculez entre les vues Kanban, Liste et Grille via les boutons de vue",
"filters": "Utilisez les filtres pour vous concentrer sur des statuts ou priorités de tâches spécifiques",
"details": "Cliquez sur une tâche pour voir les détails et gérer les sous-tâches"
},
"learnMore": {
"title": "📚 En savoir plus",
"description": "TaskMaster AI est un système avancé de gestion des tâches conçu pour les développeurs. Documentation, exemples et contributions au projet.",
"githubButton": "Voir sur GitHub"
}
},
"search": {
"placeholder": "Rechercher des tâches..."
},
"filters": {
"button": "Filtres",
"status": "Statut",
"priority": "Priorité",
"sortBy": "Trier par",
"allStatuses": "Tous les statuts",
"allPriorities": "Toutes les priorités",
"showing": "Affichage de {{filtered}} sur {{total}} tâches",
"clearFilters": "Effacer les filtres"
},
"sort": {
"id": "ID",
"status": "Statut",
"priority": "Priorité",
"idAsc": "ID (croissant)",
"idDesc": "ID (décroissant)",
"titleAsc": "Titre (A-Z)",
"titleDesc": "Titre (Z-A)",
"statusAsc": "Statut (en attente en premier)",
"statusDesc": "Statut (terminé en premier)",
"priorityAsc": "Priorité (haute en premier)",
"priorityDesc": "Priorité (basse en premier)"
},
"views": {
"kanban": "Vue Kanban",
"list": "Vue liste",
"grid": "Vue grille"
},
"kanban": {
"pending": "📋 À faire",
"inProgress": "🚀 En cours",
"done": "✅ Terminé",
"blocked": "🚫 Bloqué",
"deferred": "⏳ Différé",
"cancelled": "❌ Annulé",
"noTasksYet": "Aucune tâche pour l'instant",
"tasksWillAppear": "Les tâches apparaîtront ici",
"moveTasksHere": "Déplacez les tâches ici au démarrage",
"completedTasksHere": "Les tâches terminées apparaissent ici",
"statusTasksHere": "Les tâches avec ce statut apparaîtront ici"
},
"buttons": {
"help": "Guide de démarrage TaskMaster",
"prds": "PRDs",
"addPRD": "Ajouter un PRD",
"addTask": "Ajouter une tâche",
"createNewPRD": "Créer un nouveau PRD",
"prdsAvailable": "{{count}} PRD(s) disponible(s)"
},
"prd": {
"modified": "Modifié : {{date}}"
},
"statuses": {
"pending": "En attente",
"in-progress": "En cours",
"done": "Terminé",
"blocked": "Bloqué",
"deferred": "Différé",
"cancelled": "Annulé"
},
"priorities": {
"high": "Haute",
"medium": "Moyenne",
"low": "Basse"
},
"noMatchingTasks": {
"title": "Aucune tâche ne correspond à vos filtres",
"description": "Essayez d'ajuster votre recherche ou vos critères de filtre."
}
}