@tensor.chat/companion 0.4.3 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/bin/tensor-companion.js +4 -4
- package/package.json +1 -1
- package/src/daemon.js +8 -86
- package/src/setup/i18n.js +10 -131
- package/src/setup/lang/de.js +68 -0
- package/src/setup/lang/en.js +68 -0
- package/src/setup/lang/es.js +68 -0
- package/src/setup/lang/fr.js +69 -0
- package/src/setup/lang/hi.js +68 -0
- package/src/setup/lang/pt.js +68 -0
- package/src/setup/lang/ru.js +69 -0
- package/src/setup/lang/zh.js +68 -0
- package/src/setup/layout.js +123 -5
- package/src/setup/term.js +4 -0
- package/src/setup/wizard.js +58 -33
- package/src/toolExecutor.js +85 -0
- package/src/toolRunner.js +85 -0
- package/src/toolWorker.js +47 -0
package/README.md
CHANGED
|
@@ -17,7 +17,9 @@ The command prints a code such as `AB3D-7KQ2`. In tensor.chat open
|
|
|
17
17
|
setup asks in the terminal what the AI may use on this computer: folders from a list of
|
|
18
18
|
suggestions or a folder browser, everything (not recommended), or nothing yet, and
|
|
19
19
|
whether it may run commands. Run it again any time with
|
|
20
|
-
`npx @tensor.chat/companion setup`.
|
|
20
|
+
`npx @tensor.chat/companion setup`. It speaks the computer's language (`LANG`) if that is
|
|
21
|
+
one of de, en, es, fr, hi, pt, ru or zh, English otherwise; `--lang` picks another.
|
|
22
|
+
Then keep the daemon running:
|
|
21
23
|
|
|
22
24
|
```
|
|
23
25
|
npx @tensor.chat/companion run
|
package/bin/tensor-companion.js
CHANGED
|
@@ -70,11 +70,11 @@ function run() {
|
|
|
70
70
|
});
|
|
71
71
|
daemon.events.on('call', (c) => out(`[${stamp()}] ${c.tool}: ${summarize(c.args)}`));
|
|
72
72
|
daemon.events.on('approval', (a) => out(`[${stamp()}] approval requested for ${a.tool}: ${a.summary || ''} — answer it in the chat`));
|
|
73
|
-
daemon.events.on('unauthorized', () => { out('the server rejected this device — pair again with "npx @tensor.chat/companion pair"'); process.exit(2); });
|
|
74
|
-
daemon.events.on('removed', () => { out('this device was removed in the account settings — exiting'); process.exit(3); });
|
|
73
|
+
daemon.events.on('unauthorized', async () => { out('the server rejected this device — pair again with "npx @tensor.chat/companion pair"'); await daemon.stop(); process.exit(2); });
|
|
74
|
+
daemon.events.on('removed', async () => { out('this device was removed in the account settings — exiting'); await daemon.stop(); process.exit(3); });
|
|
75
75
|
daemon.events.on('local_approval_request', (a) => out(`[${stamp()}] local approval requested: run npx @tensor.chat/companion allow ${a.code}`));
|
|
76
76
|
daemon.start();
|
|
77
|
-
const stop = () => { daemon.stop();
|
|
77
|
+
const stop = async () => { await daemon.stop(); process.exit(0); };
|
|
78
78
|
process.on('SIGINT', stop);
|
|
79
79
|
process.on('SIGTERM', stop);
|
|
80
80
|
}
|
|
@@ -165,7 +165,7 @@ function help() {
|
|
|
165
165
|
out(`tensor-companion ${VERSION}
|
|
166
166
|
|
|
167
167
|
tensor-companion pair [--server URL] [--name NAME] pair this machine with your account
|
|
168
|
-
tensor-companion setup [--lang
|
|
168
|
+
tensor-companion setup [--lang LANG] choose what the AI may use on this computer
|
|
169
169
|
tensor-companion run stay connected and run tools for the AI
|
|
170
170
|
tensor-companion status show the current pairing
|
|
171
171
|
tensor-companion shell <path|auto> choose the shell Bash commands run in
|
package/package.json
CHANGED
package/src/daemon.js
CHANGED
|
@@ -10,19 +10,13 @@ const EventEmitter = require('events');
|
|
|
10
10
|
const WebSocket = require('ws');
|
|
11
11
|
const { Roots } = require('./paths');
|
|
12
12
|
const bash = require('./tools/bash');
|
|
13
|
-
const
|
|
14
|
-
const { fetchUrl } = require('./tools/fetch');
|
|
15
|
-
const { extractTouched } = require('./tools/touched');
|
|
16
|
-
const { outsideFolders } = require('./tools/outside');
|
|
17
|
-
const { Jobs } = require('./tools/jobs');
|
|
13
|
+
const { ToolRunner } = require('./toolRunner');
|
|
18
14
|
const { createStallWatch } = require('./stalls');
|
|
19
15
|
|
|
20
16
|
const VERSION = require('../package.json').version;
|
|
21
17
|
const RECONNECT_MIN_MS = 1000;
|
|
22
18
|
const RECONNECT_MAX_MS = 60000;
|
|
23
19
|
const MAX_CONCURRENT_CALLS = 4;
|
|
24
|
-
const BACKGROUND_FIRST_LOOK_MS = 1500; // a background start still reports the first lines
|
|
25
|
-
const FOREGROUND_DEFAULT_MS = 600000;
|
|
26
20
|
const SLOW_SPAWN_MS = 2000;
|
|
27
21
|
|
|
28
22
|
// What a call works on, for the log: the command, the path or the address
|
|
@@ -108,7 +102,7 @@ function authorizeRequestedRoots(requestedRoots, labels, approvedRoots) {
|
|
|
108
102
|
return { roots, labels: keptLabels, rejected };
|
|
109
103
|
}
|
|
110
104
|
|
|
111
|
-
function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShell, approver = null, slowSpawnMs = SLOW_SPAWN_MS } = {}) {
|
|
105
|
+
function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShell, approver = null, slowSpawnMs = SLOW_SPAWN_MS, toolWorkerPath } = {}) {
|
|
112
106
|
if (!serverUrl || !token) throw new Error('serverUrl and token are required');
|
|
113
107
|
const emitter = new EventEmitter();
|
|
114
108
|
const say = typeof log === 'function' ? log : () => {};
|
|
@@ -116,7 +110,8 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
|
|
|
116
110
|
const localApprovals = typeof approvedRoots === 'function' ? approvedRoots : null;
|
|
117
111
|
const localShellApproval = typeof approvedShell === 'function' ? approvedShell : null;
|
|
118
112
|
const running = new Map(); // call id → AbortController
|
|
119
|
-
const
|
|
113
|
+
const tools = new ToolRunner({ shell, slowSpawnMs, workerPath: toolWorkerPath });
|
|
114
|
+
tools.on('log', say);
|
|
120
115
|
const unsentExits = []; // job endings the server has not heard yet (offline at the time)
|
|
121
116
|
const state = { connected: false, deviceId: null, name: null, mode: null, roots: [], labels: [], reconnectMs: RECONNECT_MIN_MS, stopped: false, ws: null, timer: null, connectedAt: null, lastPingAt: null };
|
|
122
117
|
const stalls = createStallWatch({ say });
|
|
@@ -160,79 +155,6 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
|
|
|
160
155
|
emitter.emit('config', { roots: [...roots.status(), ...authorized.rejected], mode: state.mode, name: state.name });
|
|
161
156
|
}
|
|
162
157
|
|
|
163
|
-
async function runTool(tool, args, signal, timeoutMs, callRoots = roots) {
|
|
164
|
-
const a = args && typeof args === 'object' ? args : {};
|
|
165
|
-
switch (tool) {
|
|
166
|
-
case 'bash': {
|
|
167
|
-
const cwdRes = a.cwd ? callRoots.resolve(a.cwd) : null;
|
|
168
|
-
if (cwdRes && cwdRes.error) return { ok: false, text: `(cannot run there — ${cwdRes.error})` };
|
|
169
|
-
const cwd = cwdRes ? cwdRes.abs : callRoots.primary();
|
|
170
|
-
if (!cwd) return { ok: false, text: '(no shared folder is available on this device — the user has to add one in the device settings)' };
|
|
171
|
-
// Starting a process holds this whole process until Windows has it running. A
|
|
172
|
-
// scanner that inspects the program first can make that last long enough for
|
|
173
|
-
// the server to stop hearing from this computer.
|
|
174
|
-
const spawnAt = Date.now();
|
|
175
|
-
const job = jobs.start(String(a.command || ''), { cwd, shell });
|
|
176
|
-
const spawnMs = Date.now() - spawnAt;
|
|
177
|
-
if (spawnMs >= slowSpawnMs) say(`starting the shell took ${Math.round(spawnMs / 1000)} s`);
|
|
178
|
-
// A cancelled call (turn aborted, server timeout) takes its foreground job with
|
|
179
|
-
// it; a background job belongs to the chat, not to the call, and lives on.
|
|
180
|
-
const onAbort = () => { if (!a.background) jobs.kill(job.id); };
|
|
181
|
-
if (signal) signal.addEventListener('abort', onAbort, { once: true });
|
|
182
|
-
// The server cancels a call at timeoutMs; answering a little earlier keeps the job
|
|
183
|
-
// alive as a background job instead of letting the cancel kill it.
|
|
184
|
-
const limit = timeoutMs || FOREGROUND_DEFAULT_MS;
|
|
185
|
-
const waitMs = a.background ? BACKGROUND_FIRST_LOOK_MS : Math.max(1000, limit - Math.min(5000, Math.floor(limit / 4)));
|
|
186
|
-
const ended = await jobs.wait(job, waitMs);
|
|
187
|
-
if (signal) signal.removeEventListener('abort', onAbort);
|
|
188
|
-
if (ended) {
|
|
189
|
-
const r = jobs.result(job);
|
|
190
|
-
r.touched = extractTouched(callRoots, a.command, r.stdout, r.stderr);
|
|
191
|
-
delete r.stdout;
|
|
192
|
-
delete r.stderr;
|
|
193
|
-
jobs.poll(job.id); // the one-shot result already carried every line
|
|
194
|
-
return r;
|
|
195
|
-
}
|
|
196
|
-
// Still running: it becomes (or already was) a background job.
|
|
197
|
-
const p = jobs.poll(job.id);
|
|
198
|
-
return { ok: true, running: true, jobId: job.id, text: p.text, exitCode: null, truncated: false, timedOut: false, touched: [] };
|
|
199
|
-
}
|
|
200
|
-
case 'job': {
|
|
201
|
-
const job = jobs.get(a.jobId);
|
|
202
|
-
if (!job) return { ok: false, text: '(no such job on this device — it may have been forgotten after many newer ones)' };
|
|
203
|
-
if (a.action === 'kill') {
|
|
204
|
-
jobs.kill(job.id);
|
|
205
|
-
await jobs.wait(job, 3000);
|
|
206
|
-
}
|
|
207
|
-
const p = jobs.poll(job.id);
|
|
208
|
-
return { ok: true, jobId: job.id, running: p.status === 'running', status: p.status, exitCode: p.exitCode, jobMs: p.ms, text: p.text, skipped: p.skipped };
|
|
209
|
-
}
|
|
210
|
-
case 'read': return files.read(callRoots, a.path, { lines: a.lines });
|
|
211
|
-
case 'write': return files.write(callRoots, a.path, a.content, { overwrite: a.overwrite === true });
|
|
212
|
-
case 'edit': return files.edit(callRoots, a.path, a.edits);
|
|
213
|
-
case 'glob': return files.glob(callRoots, a.pattern);
|
|
214
|
-
// The server asks before it judges a shell command: what of it lies outside
|
|
215
|
-
// the shared folders, read with this machine's own variables. Nothing runs.
|
|
216
|
-
case 'outside': {
|
|
217
|
-
const at = a.cwd ? callRoots.resolve(a.cwd) : null;
|
|
218
|
-
return outsideFolders(callRoots, a.command, { cwd: at && !at.error ? at.abs : callRoots.primary() || os.homedir() });
|
|
219
|
-
}
|
|
220
|
-
case 'grep': return files.grep(callRoots, a.command);
|
|
221
|
-
case 'ls': return files.ls(callRoots, a.path, { max: a.max });
|
|
222
|
-
case 'fetchfile': return files.fetchfile(callRoots, a.path, { maxBytes: a.maxBytes });
|
|
223
|
-
case 'fetchpart': return files.fetchpart(callRoots, a.path, { offset: a.offset, length: a.length });
|
|
224
|
-
// Das eingehaengte Laufwerk schreibt ueber diese vier zurueck. Sie stehen der KI
|
|
225
|
-
// NICHT offen - sie kommen nur aus dem Adapter, der das Laufwerk bedient.
|
|
226
|
-
case 'writepart': return files.writepart(callRoots, a.path, { offset: a.offset, base64: a.base64, truncate: a.truncate });
|
|
227
|
-
case 'mkdirpath': return files.mkdirpath(callRoots, a.path);
|
|
228
|
-
case 'removepath': return await files.removepath(callRoots, a.path, { endgueltig: a.endgueltig === true });
|
|
229
|
-
case 'renamepath': return files.renamepath(callRoots, a.from, a.to);
|
|
230
|
-
case 'statpath': return files.statpath(callRoots, a.path);
|
|
231
|
-
case 'fetch': return fetchUrl(a.url, { signal, raw: a.raw === true });
|
|
232
|
-
default: return { ok: false, text: `(unknown tool "${tool}" on the companion device)` };
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
158
|
async function onCall(msg) {
|
|
237
159
|
const id = String(msg.id);
|
|
238
160
|
const tool = String(msg.tool || '');
|
|
@@ -258,7 +180,7 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
|
|
|
258
180
|
const scoped = authorizeRequestedRoots(msg.roots, msg.labels, approved);
|
|
259
181
|
callRoots = new Roots(scoped.roots, scoped.labels);
|
|
260
182
|
}
|
|
261
|
-
r = await
|
|
183
|
+
r = await tools.run(tool, msg.args, ctrl.signal, Number(msg.timeoutMs) || undefined, callRoots);
|
|
262
184
|
} catch (e) {
|
|
263
185
|
r = { ok: false, text: `(tool error on the companion device: ${e && e.message ? e.message : e})` };
|
|
264
186
|
}
|
|
@@ -275,7 +197,7 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
|
|
|
275
197
|
unsentExits.shift();
|
|
276
198
|
}
|
|
277
199
|
}
|
|
278
|
-
|
|
200
|
+
tools.on('exit', (r) => {
|
|
279
201
|
const m = { t: 'job_exit', jobId: r.jobId, status: r.status, exitCode: r.exitCode, ms: r.ms, tail: r.tail };
|
|
280
202
|
unsentExits.push(m);
|
|
281
203
|
flushExits();
|
|
@@ -376,11 +298,11 @@ function createDaemon({ serverUrl, token, shell, log, approvedRoots, approvedShe
|
|
|
376
298
|
stalls.stop();
|
|
377
299
|
if (state.timer) { clearTimeout(state.timer); state.timer = null; }
|
|
378
300
|
for (const c of running.values()) c.abort();
|
|
379
|
-
|
|
301
|
+
const stopped = tools.stop();
|
|
380
302
|
if (state.ws) { try { state.ws.close(1000, 'daemon stopped'); } catch (_) {} try { state.ws.terminate(); } catch (_) {} state.ws = null; }
|
|
303
|
+
return stopped;
|
|
381
304
|
},
|
|
382
305
|
replyApproval(approvalId, decision) { return send({ t: 'approval_reply', approvalId, decision }); },
|
|
383
|
-
jobs,
|
|
384
306
|
};
|
|
385
307
|
}
|
|
386
308
|
|
package/src/setup/i18n.js
CHANGED
|
@@ -1,138 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
// The setup's words, in the language the computer is set to (LC_ALL, LC_MESSAGES,
|
|
3
|
-
// LANG - an SSH session usually brings the caller's), English otherwise.
|
|
3
|
+
// LANG - an SSH session usually brings the caller's), English otherwise. The site
|
|
4
|
+
// speaks the same eight.
|
|
4
5
|
|
|
5
6
|
const STRINGS = {
|
|
6
|
-
en:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'start.now': 'Approved now:',
|
|
15
|
-
'start.later': 'Change it any time:',
|
|
16
|
-
'places.title': 'Which folders may the AI use?',
|
|
17
|
-
'places.loading': 'Looking for folders…',
|
|
18
|
-
'places.none': 'No suggestions on this computer.',
|
|
19
|
-
'places.browse': 'Browse all folders…', 'places.type': 'Type a path…', 'places.continue': 'Continue',
|
|
20
|
-
selected: '{n} selected',
|
|
21
|
-
'note.git': '{n} Git projects', 'note.git1': '1 Git project', 'note.project': 'Git project',
|
|
22
|
-
'note.web': 'websites', 'note.srv': 'server data', 'note.opt': 'installed programs',
|
|
23
|
-
'note.etc': 'system settings', 'note.log': 'logs', 'note.home': 'your whole home folder',
|
|
24
|
-
'note.readOnly': 'read-only for you', 'note.added': 'added by you', 'note.earlier': 'approved earlier',
|
|
25
|
-
'note.inside': 'inside {path}',
|
|
26
|
-
'help.home': 'Everything in {home}, also hidden settings and keys such as ~/.ssh.',
|
|
27
|
-
'help.etc': 'Settings of the whole system. A wrong change there can stop the server.',
|
|
28
|
-
'help.log': 'Log files of the system and its services. Good for finding errors.',
|
|
29
|
-
'help.web': 'Files of the websites this server delivers.',
|
|
30
|
-
'help.srv': 'Data of the services on this server.',
|
|
31
|
-
'help.opt': 'Programs installed outside the package manager.',
|
|
32
|
-
'help.code': 'Your code projects.',
|
|
33
|
-
'help.readOnly': 'You can read here but not write. The AI can do the same, no more.',
|
|
34
|
-
'help.browse': 'Pick any folder on this computer, like in a file manager.',
|
|
35
|
-
'help.type': 'Enter a full path. Tab completes it.',
|
|
36
|
-
'help.continue': 'Next: commands.',
|
|
37
|
-
'browse.title': 'Browse', 'browse.thisPc': 'This PC',
|
|
38
|
-
'browse.loading': 'loading…', 'browse.empty': 'no folders', 'browse.noAccess': 'no access', 'browse.link': 'link',
|
|
39
|
-
'browse.blocked': '{path} is inside {parent}, which is already selected.',
|
|
40
|
-
'browse.hidden': 'Hidden folders are shown.', 'browse.hiddenOff': 'Hidden folders are hidden.',
|
|
41
|
-
'type.title': 'Type a folder path', 'type.label': 'Path',
|
|
42
|
-
'type.notFound': 'There is no folder {path}.',
|
|
43
|
-
'everything.title': 'Share everything?',
|
|
44
|
-
'everything.body': 'The AI can read, change and delete every file that {user} can reach on this computer.',
|
|
45
|
-
'everything.root': 'You are root: that is every file on the system, the system itself included.',
|
|
46
|
-
'everything.free': 'It lets the AI work freely on this server. Not recommended.',
|
|
47
|
-
'everything.no': 'No, go back', 'everything.yes': 'Yes, share everything',
|
|
48
|
-
'everything.summary': 'Everything on this computer',
|
|
49
|
-
'shell.title': 'May the AI run commands here?',
|
|
50
|
-
'shell.body': 'Commands run as {user}, with full internet access. They are not limited to the shared folders.',
|
|
51
|
-
'shell.keep': 'Keep as it is', 'shell.keepNote': 'until {date}',
|
|
52
|
-
'shell.no': 'Not now', 'shell.noNote': 'a chat asks first when it needs them',
|
|
53
|
-
'shell.off': 'Turn off', 'shell.offNote': 'a chat asks first when it needs them',
|
|
54
|
-
'shell.d7': 'Yes, for 7 days', 'shell.d30': 'Yes, for 30 days', 'shell.d365': 'Yes, for a year',
|
|
55
|
-
'review.title': 'Check and save',
|
|
56
|
-
'review.folders': 'Folders', 'review.commands': 'Commands',
|
|
57
|
-
'review.noFolders': 'none — a chat asks when it needs one',
|
|
58
|
-
'review.removed': 'approval removed',
|
|
59
|
-
'review.cmdNo': 'not approved — a chat asks first',
|
|
60
|
-
'review.cmdUntil': 'approved until {date}',
|
|
61
|
-
'review.save': 'Save', 'review.back': 'Back', 'review.quit': 'Quit without saving',
|
|
62
|
-
'review.where': 'Saved only on this computer, in {file}',
|
|
63
|
-
keyNames: { space: 'space', enter: 'enter', esc: 'esc', tab: 'tab', backspace: 'backspace' },
|
|
64
|
-
keys: { move: 'move', select: 'select', toggle: 'select', back: 'back', quit: 'quit', open: 'open', close: 'close', openClose: 'open/close', up: 'folder up', hidden: 'hidden', done: 'done', tab: 'complete', add: 'add' },
|
|
65
|
-
'done.saved': 'Saved on this computer.',
|
|
66
|
-
'done.unchanged': 'Nothing changed.',
|
|
67
|
-
'done.web': 'When a chat on tensor.chat shares one of these folders, this computer confirms it by itself.',
|
|
68
|
-
'done.run': 'Start the companion with:',
|
|
69
|
-
'done.noTty': 'The setup needs an interactive terminal. Without one, use approve-folder and approve-shell.',
|
|
70
|
-
},
|
|
71
|
-
de: {
|
|
72
|
-
setup: 'Einrichtung',
|
|
73
|
-
steps: ['Was', 'Ordner', 'Befehle', 'Speichern'],
|
|
74
|
-
'start.title': 'Was darf die KI auf diesem Computer nutzen?',
|
|
75
|
-
'start.folders': 'Ordner auswählen', 'start.foldersNote': 'empfohlen',
|
|
76
|
-
'start.nothing': 'Noch nichts', 'start.nothingNote': 'ein Chat fragt, wenn er einen Ordner braucht',
|
|
77
|
-
'start.everything': 'Alles', 'start.everythingNote': 'nicht empfohlen',
|
|
78
|
-
'start.help': 'Die KI liest, ändert und löscht Dateien nur dort, wo du es hier erlaubst.',
|
|
79
|
-
'start.now': 'Jetzt freigegeben:',
|
|
80
|
-
'start.later': 'Jederzeit änderbar:',
|
|
81
|
-
'places.title': 'Welche Ordner darf die KI nutzen?',
|
|
82
|
-
'places.loading': 'Suche Ordner…',
|
|
83
|
-
'places.none': 'Keine Vorschläge auf diesem Computer.',
|
|
84
|
-
'places.browse': 'Alle Ordner durchsuchen…', 'places.type': 'Pfad eingeben…', 'places.continue': 'Weiter',
|
|
85
|
-
selected: '{n} ausgewählt',
|
|
86
|
-
'note.git': '{n} Git-Projekte', 'note.git1': '1 Git-Projekt', 'note.project': 'Git-Projekt',
|
|
87
|
-
'note.web': 'Webseiten', 'note.srv': 'Serverdaten', 'note.opt': 'installierte Programme',
|
|
88
|
-
'note.etc': 'Systemeinstellungen', 'note.log': 'Protokolle', 'note.home': 'dein ganzer Home-Ordner',
|
|
89
|
-
'note.readOnly': 'für dich nur lesbar', 'note.added': 'von dir hinzugefügt', 'note.earlier': 'schon freigegeben',
|
|
90
|
-
'note.inside': 'liegt in {path}',
|
|
91
|
-
'help.home': 'Alles in {home}, auch versteckte Einstellungen und Schlüssel wie ~/.ssh.',
|
|
92
|
-
'help.etc': 'Einstellungen des ganzen Systems. Eine falsche Änderung dort kann den Server lahmlegen.',
|
|
93
|
-
'help.log': 'Protokolle des Systems und seiner Dienste. Gut für die Fehlersuche.',
|
|
94
|
-
'help.web': 'Dateien der Webseiten, die dieser Server ausliefert.',
|
|
95
|
-
'help.srv': 'Daten der Dienste auf diesem Server.',
|
|
96
|
-
'help.opt': 'Programme, die neben der Paketverwaltung installiert sind.',
|
|
97
|
-
'help.code': 'Deine Code-Projekte.',
|
|
98
|
-
'help.readOnly': 'Du kannst hier lesen, aber nicht schreiben. Die KI kann dasselbe, nicht mehr.',
|
|
99
|
-
'help.browse': 'Wähle einen beliebigen Ordner auf diesem Computer, wie in einem Dateimanager.',
|
|
100
|
-
'help.type': 'Gib einen vollständigen Pfad ein. Tab ergänzt ihn.',
|
|
101
|
-
'help.continue': 'Weiter zu den Befehlen.',
|
|
102
|
-
'browse.title': 'Durchsuchen', 'browse.thisPc': 'Dieser PC',
|
|
103
|
-
'browse.loading': 'lädt…', 'browse.empty': 'keine Ordner', 'browse.noAccess': 'kein Zugriff', 'browse.link': 'Verknüpfung',
|
|
104
|
-
'browse.blocked': '{path} liegt in {parent}, und der ist schon ausgewählt.',
|
|
105
|
-
'browse.hidden': 'Versteckte Ordner werden angezeigt.', 'browse.hiddenOff': 'Versteckte Ordner sind ausgeblendet.',
|
|
106
|
-
'type.title': 'Ordnerpfad eingeben', 'type.label': 'Pfad',
|
|
107
|
-
'type.notFound': 'Den Ordner {path} gibt es nicht.',
|
|
108
|
-
'everything.title': 'Alles freigeben?',
|
|
109
|
-
'everything.body': 'Die KI kann jede Datei lesen, ändern und löschen, an die {user} auf diesem Computer herankommt.',
|
|
110
|
-
'everything.root': 'Du bist root: Das ist jede Datei des Systems, das System selbst eingeschlossen.',
|
|
111
|
-
'everything.free': 'So kann die KI frei auf diesem Server arbeiten. Nicht empfohlen.',
|
|
112
|
-
'everything.no': 'Nein, zurück', 'everything.yes': 'Ja, alles freigeben',
|
|
113
|
-
'everything.summary': 'Alles auf diesem Computer',
|
|
114
|
-
'shell.title': 'Darf die KI hier Befehle ausführen?',
|
|
115
|
-
'shell.body': 'Befehle laufen als {user}, mit vollem Internetzugang. Sie sind nicht auf die freigegebenen Ordner beschränkt.',
|
|
116
|
-
'shell.keep': 'So lassen', 'shell.keepNote': 'bis {date}',
|
|
117
|
-
'shell.no': 'Jetzt nicht', 'shell.noNote': 'ein Chat fragt vorher, wenn er sie braucht',
|
|
118
|
-
'shell.off': 'Ausschalten', 'shell.offNote': 'ein Chat fragt vorher, wenn er sie braucht',
|
|
119
|
-
'shell.d7': 'Ja, für 7 Tage', 'shell.d30': 'Ja, für 30 Tage', 'shell.d365': 'Ja, für ein Jahr',
|
|
120
|
-
'review.title': 'Prüfen und speichern',
|
|
121
|
-
'review.folders': 'Ordner', 'review.commands': 'Befehle',
|
|
122
|
-
'review.noFolders': 'keine – ein Chat fragt, wenn er einen braucht',
|
|
123
|
-
'review.removed': 'Freigabe entfernt',
|
|
124
|
-
'review.cmdNo': 'nicht freigegeben – ein Chat fragt vorher',
|
|
125
|
-
'review.cmdUntil': 'freigegeben bis {date}',
|
|
126
|
-
'review.save': 'Speichern', 'review.back': 'Zurück', 'review.quit': 'Beenden ohne zu speichern',
|
|
127
|
-
'review.where': 'Wird nur auf diesem Computer gespeichert, in {file}',
|
|
128
|
-
keyNames: { space: 'Leertaste', enter: 'Enter', esc: 'Esc', tab: 'Tab', backspace: 'Rücktaste' },
|
|
129
|
-
keys: { move: 'wählen', select: 'bestätigen', toggle: 'auswählen', back: 'zurück', quit: 'beenden', open: 'öffnen', close: 'schließen', openClose: 'öffnen/schließen', up: 'Ordner hoch', hidden: 'versteckte', done: 'fertig', tab: 'ergänzen', add: 'hinzufügen' },
|
|
130
|
-
'done.saved': 'Auf diesem Computer gespeichert.',
|
|
131
|
-
'done.unchanged': 'Nichts geändert.',
|
|
132
|
-
'done.web': 'Gibt ein Chat auf tensor.chat einen dieser Ordner frei, bestätigt dieser Computer das selbst.',
|
|
133
|
-
'done.run': 'Den Companion starten mit:',
|
|
134
|
-
'done.noTty': 'Die Einrichtung braucht ein interaktives Terminal. Ohne eines gehen approve-folder und approve-shell.',
|
|
135
|
-
},
|
|
7
|
+
en: require('./lang/en'),
|
|
8
|
+
de: require('./lang/de'),
|
|
9
|
+
es: require('./lang/es'),
|
|
10
|
+
fr: require('./lang/fr'),
|
|
11
|
+
hi: require('./lang/hi'),
|
|
12
|
+
pt: require('./lang/pt'),
|
|
13
|
+
ru: require('./lang/ru'),
|
|
14
|
+
zh: require('./lang/zh'),
|
|
136
15
|
};
|
|
137
16
|
|
|
138
17
|
function pickLanguage(env = process.env, override) {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// The setup's words in German.
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
setup: 'Einrichtung',
|
|
6
|
+
steps: ['Was', 'Ordner', 'Befehle', 'Speichern'],
|
|
7
|
+
'start.title': 'Was darf die KI auf diesem Computer nutzen?',
|
|
8
|
+
'start.folders': 'Ordner auswählen', 'start.foldersNote': 'empfohlen',
|
|
9
|
+
'start.nothing': 'Noch nichts', 'start.nothingNote': 'ein Chat fragt, wenn er einen Ordner braucht',
|
|
10
|
+
'start.everything': 'Alles', 'start.everythingNote': 'nicht empfohlen',
|
|
11
|
+
'start.help': 'Die KI liest, ändert und löscht Dateien nur dort, wo du es hier erlaubst.',
|
|
12
|
+
'start.now': 'Jetzt freigegeben:',
|
|
13
|
+
'start.later': 'Jederzeit änderbar:',
|
|
14
|
+
'places.title': 'Welche Ordner darf die KI nutzen?',
|
|
15
|
+
'places.loading': 'Suche Ordner…',
|
|
16
|
+
'places.none': 'Keine Vorschläge auf diesem Computer.',
|
|
17
|
+
'places.browse': 'Alle Ordner durchsuchen…', 'places.type': 'Pfad eingeben…', 'places.continue': 'Weiter',
|
|
18
|
+
selected: '{n} ausgewählt',
|
|
19
|
+
'note.git': '{n} Git-Projekte', 'note.git1': '1 Git-Projekt', 'note.project': 'Git-Projekt',
|
|
20
|
+
'note.web': 'Webseiten', 'note.srv': 'Serverdaten', 'note.opt': 'installierte Programme',
|
|
21
|
+
'note.etc': 'Systemeinstellungen', 'note.log': 'Protokolle', 'note.home': 'dein ganzer Home-Ordner',
|
|
22
|
+
'note.readOnly': 'für dich nur lesbar', 'note.added': 'von dir hinzugefügt', 'note.earlier': 'schon freigegeben',
|
|
23
|
+
'note.inside': 'liegt in {path}',
|
|
24
|
+
'help.home': 'Alles in {home}, auch versteckte Einstellungen und Schlüssel wie ~/.ssh.',
|
|
25
|
+
'help.etc': 'Einstellungen des ganzen Systems. Eine falsche Änderung dort kann den Server lahmlegen.',
|
|
26
|
+
'help.log': 'Protokolle des Systems und seiner Dienste. Gut für die Fehlersuche.',
|
|
27
|
+
'help.web': 'Dateien der Webseiten, die dieser Server ausliefert.',
|
|
28
|
+
'help.srv': 'Daten der Dienste auf diesem Server.',
|
|
29
|
+
'help.opt': 'Programme, die neben der Paketverwaltung installiert sind.',
|
|
30
|
+
'help.code': 'Deine Code-Projekte.',
|
|
31
|
+
'help.readOnly': 'Du kannst hier lesen, aber nicht schreiben. Die KI kann dasselbe, nicht mehr.',
|
|
32
|
+
'help.browse': 'Wähle einen beliebigen Ordner auf diesem Computer, wie in einem Dateimanager.',
|
|
33
|
+
'help.type': 'Gib einen vollständigen Pfad ein. Tab ergänzt ihn.',
|
|
34
|
+
'help.continue': 'Weiter zu den Befehlen.',
|
|
35
|
+
'browse.title': 'Durchsuchen', 'browse.thisPc': 'Dieser PC',
|
|
36
|
+
'browse.loading': 'lädt…', 'browse.empty': 'keine Ordner', 'browse.noAccess': 'kein Zugriff', 'browse.link': 'Verknüpfung',
|
|
37
|
+
'browse.blocked': '{path} liegt in {parent}, und der ist schon ausgewählt.',
|
|
38
|
+
'browse.hidden': 'Versteckte Ordner werden angezeigt.', 'browse.hiddenOff': 'Versteckte Ordner sind ausgeblendet.',
|
|
39
|
+
'type.title': 'Ordnerpfad eingeben', 'type.label': 'Pfad',
|
|
40
|
+
'type.notFound': 'Den Ordner {path} gibt es nicht.',
|
|
41
|
+
'everything.title': 'Alles freigeben?',
|
|
42
|
+
'everything.body': 'Die KI kann jede Datei lesen, ändern und löschen, an die {user} auf diesem Computer herankommt.',
|
|
43
|
+
'everything.root': 'Du bist root: Das ist jede Datei des Systems, das System selbst eingeschlossen.',
|
|
44
|
+
'everything.free': 'So kann die KI frei auf diesem Server arbeiten. Nicht empfohlen.',
|
|
45
|
+
'everything.no': 'Nein, zurück', 'everything.yes': 'Ja, alles freigeben',
|
|
46
|
+
'everything.summary': 'Alles auf diesem Computer',
|
|
47
|
+
'shell.title': 'Darf die KI hier Befehle ausführen?',
|
|
48
|
+
'shell.body': 'Befehle laufen als {user}, mit vollem Internetzugang. Sie sind nicht auf die freigegebenen Ordner beschränkt.',
|
|
49
|
+
'shell.keep': 'So lassen', 'shell.keepNote': 'bis {date}',
|
|
50
|
+
'shell.no': 'Jetzt nicht', 'shell.noNote': 'ein Chat fragt vorher, wenn er sie braucht',
|
|
51
|
+
'shell.off': 'Ausschalten', 'shell.offNote': 'ein Chat fragt vorher, wenn er sie braucht',
|
|
52
|
+
'shell.d7': 'Ja, für 7 Tage', 'shell.d30': 'Ja, für 30 Tage', 'shell.d365': 'Ja, für ein Jahr',
|
|
53
|
+
'review.title': 'Prüfen und speichern',
|
|
54
|
+
'review.folders': 'Ordner', 'review.commands': 'Befehle',
|
|
55
|
+
'review.noFolders': 'keine – ein Chat fragt, wenn er einen braucht',
|
|
56
|
+
'review.removed': 'Freigabe entfernt',
|
|
57
|
+
'review.cmdNo': 'nicht freigegeben – ein Chat fragt vorher',
|
|
58
|
+
'review.cmdUntil': 'freigegeben bis {date}',
|
|
59
|
+
'review.save': 'Speichern', 'review.back': 'Zurück', 'review.quit': 'Beenden ohne zu speichern',
|
|
60
|
+
'review.where': 'Wird nur auf diesem Computer gespeichert, in {file}',
|
|
61
|
+
keyNames: { space: 'Leertaste', enter: 'Enter', esc: 'Esc', tab: 'Tab', backspace: 'Rücktaste' },
|
|
62
|
+
keys: { move: 'wählen', select: 'bestätigen', toggle: 'auswählen', back: 'zurück', quit: 'beenden', open: 'öffnen', close: 'schließen', openClose: 'öffnen/schließen', up: 'Ordner hoch', hidden: 'versteckte', done: 'fertig', tab: 'ergänzen', add: 'hinzufügen' },
|
|
63
|
+
'done.saved': 'Auf diesem Computer gespeichert.',
|
|
64
|
+
'done.unchanged': 'Nichts geändert.',
|
|
65
|
+
'done.web': 'Gibt ein Chat auf tensor.chat einen dieser Ordner frei, bestätigt dieser Computer das selbst.',
|
|
66
|
+
'done.run': 'Den Companion starten mit:',
|
|
67
|
+
'done.noTty': 'Die Einrichtung braucht ein interaktives Terminal. Ohne eines gehen approve-folder und approve-shell.',
|
|
68
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// The setup's words in English.
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
setup: 'setup',
|
|
6
|
+
steps: ['What', 'Folders', 'Commands', 'Save'],
|
|
7
|
+
'start.title': 'What may the AI use on this computer?',
|
|
8
|
+
'start.folders': 'Choose folders', 'start.foldersNote': 'recommended',
|
|
9
|
+
'start.nothing': 'Nothing yet', 'start.nothingNote': 'a chat asks when it needs a folder',
|
|
10
|
+
'start.everything': 'Everything', 'start.everythingNote': 'not recommended',
|
|
11
|
+
'start.help': 'The AI reads, changes and deletes files only where you allow it here.',
|
|
12
|
+
'start.now': 'Approved now:',
|
|
13
|
+
'start.later': 'Change it any time:',
|
|
14
|
+
'places.title': 'Which folders may the AI use?',
|
|
15
|
+
'places.loading': 'Looking for folders…',
|
|
16
|
+
'places.none': 'No suggestions on this computer.',
|
|
17
|
+
'places.browse': 'Browse all folders…', 'places.type': 'Type a path…', 'places.continue': 'Continue',
|
|
18
|
+
selected: '{n} selected',
|
|
19
|
+
'note.git': '{n} Git projects', 'note.git1': '1 Git project', 'note.project': 'Git project',
|
|
20
|
+
'note.web': 'websites', 'note.srv': 'server data', 'note.opt': 'installed programs',
|
|
21
|
+
'note.etc': 'system settings', 'note.log': 'logs', 'note.home': 'your whole home folder',
|
|
22
|
+
'note.readOnly': 'read-only for you', 'note.added': 'added by you', 'note.earlier': 'approved earlier',
|
|
23
|
+
'note.inside': 'inside {path}',
|
|
24
|
+
'help.home': 'Everything in {home}, also hidden settings and keys such as ~/.ssh.',
|
|
25
|
+
'help.etc': 'Settings of the whole system. A wrong change there can stop the server.',
|
|
26
|
+
'help.log': 'Log files of the system and its services. Good for finding errors.',
|
|
27
|
+
'help.web': 'Files of the websites this server delivers.',
|
|
28
|
+
'help.srv': 'Data of the services on this server.',
|
|
29
|
+
'help.opt': 'Programs installed outside the package manager.',
|
|
30
|
+
'help.code': 'Your code projects.',
|
|
31
|
+
'help.readOnly': 'You can read here but not write. The AI can do the same, no more.',
|
|
32
|
+
'help.browse': 'Pick any folder on this computer, like in a file manager.',
|
|
33
|
+
'help.type': 'Enter a full path. Tab completes it.',
|
|
34
|
+
'help.continue': 'Next: commands.',
|
|
35
|
+
'browse.title': 'Browse', 'browse.thisPc': 'This PC',
|
|
36
|
+
'browse.loading': 'loading…', 'browse.empty': 'no folders', 'browse.noAccess': 'no access', 'browse.link': 'link',
|
|
37
|
+
'browse.blocked': '{path} is inside {parent}, which is already selected.',
|
|
38
|
+
'browse.hidden': 'Hidden folders are shown.', 'browse.hiddenOff': 'Hidden folders are hidden.',
|
|
39
|
+
'type.title': 'Type a folder path', 'type.label': 'Path',
|
|
40
|
+
'type.notFound': 'There is no folder {path}.',
|
|
41
|
+
'everything.title': 'Share everything?',
|
|
42
|
+
'everything.body': 'The AI can read, change and delete every file that {user} can reach on this computer.',
|
|
43
|
+
'everything.root': 'You are root: that is every file on the system, the system itself included.',
|
|
44
|
+
'everything.free': 'It lets the AI work freely on this server. Not recommended.',
|
|
45
|
+
'everything.no': 'No, go back', 'everything.yes': 'Yes, share everything',
|
|
46
|
+
'everything.summary': 'Everything on this computer',
|
|
47
|
+
'shell.title': 'May the AI run commands here?',
|
|
48
|
+
'shell.body': 'Commands run as {user}, with full internet access. They are not limited to the shared folders.',
|
|
49
|
+
'shell.keep': 'Keep as it is', 'shell.keepNote': 'until {date}',
|
|
50
|
+
'shell.no': 'Not now', 'shell.noNote': 'a chat asks first when it needs them',
|
|
51
|
+
'shell.off': 'Turn off', 'shell.offNote': 'a chat asks first when it needs them',
|
|
52
|
+
'shell.d7': 'Yes, for 7 days', 'shell.d30': 'Yes, for 30 days', 'shell.d365': 'Yes, for a year',
|
|
53
|
+
'review.title': 'Check and save',
|
|
54
|
+
'review.folders': 'Folders', 'review.commands': 'Commands',
|
|
55
|
+
'review.noFolders': 'none — a chat asks when it needs one',
|
|
56
|
+
'review.removed': 'approval removed',
|
|
57
|
+
'review.cmdNo': 'not approved — a chat asks first',
|
|
58
|
+
'review.cmdUntil': 'approved until {date}',
|
|
59
|
+
'review.save': 'Save', 'review.back': 'Back', 'review.quit': 'Quit without saving',
|
|
60
|
+
'review.where': 'Saved only on this computer, in {file}',
|
|
61
|
+
keyNames: { space: 'space', enter: 'enter', esc: 'esc', tab: 'tab', backspace: 'backspace' },
|
|
62
|
+
keys: { move: 'move', select: 'select', toggle: 'select', back: 'back', quit: 'quit', open: 'open', close: 'close', openClose: 'open/close', up: 'folder up', hidden: 'hidden', done: 'done', tab: 'complete', add: 'add' },
|
|
63
|
+
'done.saved': 'Saved on this computer.',
|
|
64
|
+
'done.unchanged': 'Nothing changed.',
|
|
65
|
+
'done.web': 'When a chat on tensor.chat shares one of these folders, this computer confirms it by itself.',
|
|
66
|
+
'done.run': 'Start the companion with:',
|
|
67
|
+
'done.noTty': 'The setup needs an interactive terminal. Without one, use approve-folder and approve-shell.',
|
|
68
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// The setup's words in Spanish.
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
setup: 'configuración',
|
|
6
|
+
steps: ['Qué', 'Carpetas', 'Comandos', 'Guardar'],
|
|
7
|
+
'start.title': '¿Qué puede usar la IA en este ordenador?',
|
|
8
|
+
'start.folders': 'Elegir carpetas', 'start.foldersNote': 'recomendado',
|
|
9
|
+
'start.nothing': 'Nada por ahora', 'start.nothingNote': 'un chat pregunta cuando necesita una carpeta',
|
|
10
|
+
'start.everything': 'Todo', 'start.everythingNote': 'no recomendado',
|
|
11
|
+
'start.help': 'La IA lee, modifica y borra archivos solo donde tú lo permitas aquí.',
|
|
12
|
+
'start.now': 'Aprobado ahora:',
|
|
13
|
+
'start.later': 'Puedes cambiarlo cuando quieras:',
|
|
14
|
+
'places.title': '¿Qué carpetas puede usar la IA?',
|
|
15
|
+
'places.loading': 'Buscando carpetas…',
|
|
16
|
+
'places.none': 'No hay sugerencias en este ordenador.',
|
|
17
|
+
'places.browse': 'Explorar todas las carpetas…', 'places.type': 'Escribir una ruta…', 'places.continue': 'Continuar',
|
|
18
|
+
selected: 'Selección: {n}',
|
|
19
|
+
'note.git': '{n} proyectos Git', 'note.git1': '1 proyecto Git', 'note.project': 'proyecto Git',
|
|
20
|
+
'note.web': 'sitios web', 'note.srv': 'datos del servidor', 'note.opt': 'programas instalados',
|
|
21
|
+
'note.etc': 'ajustes del sistema', 'note.log': 'registros', 'note.home': 'toda tu carpeta personal',
|
|
22
|
+
'note.readOnly': 'solo lectura para ti', 'note.added': 'añadida por ti', 'note.earlier': 'aprobada antes',
|
|
23
|
+
'note.inside': 'dentro de {path}',
|
|
24
|
+
'help.home': 'Todo lo que hay en {home}, también ajustes ocultos y claves como ~/.ssh.',
|
|
25
|
+
'help.etc': 'Ajustes de todo el sistema. Un cambio erróneo ahí puede dejar el servidor sin funcionar.',
|
|
26
|
+
'help.log': 'Registros del sistema y de sus servicios. Útiles para encontrar errores.',
|
|
27
|
+
'help.web': 'Archivos de los sitios web que sirve este servidor.',
|
|
28
|
+
'help.srv': 'Datos de los servicios de este servidor.',
|
|
29
|
+
'help.opt': 'Programas instalados fuera del gestor de paquetes.',
|
|
30
|
+
'help.code': 'Tus proyectos de código.',
|
|
31
|
+
'help.readOnly': 'Aquí puedes leer, pero no escribir. La IA puede hacer lo mismo, nada más.',
|
|
32
|
+
'help.browse': 'Elige cualquier carpeta de este ordenador, como en un explorador de archivos.',
|
|
33
|
+
'help.type': 'Escribe una ruta completa. Tab la completa.',
|
|
34
|
+
'help.continue': 'Siguiente: comandos.',
|
|
35
|
+
'browse.title': 'Explorar', 'browse.thisPc': 'Este equipo',
|
|
36
|
+
'browse.loading': 'cargando…', 'browse.empty': 'sin carpetas', 'browse.noAccess': 'sin acceso', 'browse.link': 'enlace',
|
|
37
|
+
'browse.blocked': '{path} está dentro de {parent}, que ya está seleccionada.',
|
|
38
|
+
'browse.hidden': 'Se muestran las carpetas ocultas.', 'browse.hiddenOff': 'Las carpetas ocultas no se muestran.',
|
|
39
|
+
'type.title': 'Escribe la ruta de una carpeta', 'type.label': 'Ruta',
|
|
40
|
+
'type.notFound': 'No existe la carpeta {path}.',
|
|
41
|
+
'everything.title': '¿Compartir todo?',
|
|
42
|
+
'everything.body': 'La IA puede leer, modificar y borrar cualquier archivo al que {user} tenga acceso en este ordenador.',
|
|
43
|
+
'everything.root': 'Eres root: eso incluye cada archivo del sistema, también el propio sistema.',
|
|
44
|
+
'everything.free': 'Así la IA puede trabajar libremente en este servidor. No recomendado.',
|
|
45
|
+
'everything.no': 'No, volver', 'everything.yes': 'Sí, compartir todo',
|
|
46
|
+
'everything.summary': 'Todo en este ordenador',
|
|
47
|
+
'shell.title': '¿Puede la IA ejecutar comandos aquí?',
|
|
48
|
+
'shell.body': 'Los comandos se ejecutan como {user}, con acceso completo a Internet. No se limitan a las carpetas compartidas.',
|
|
49
|
+
'shell.keep': 'Dejarlo así', 'shell.keepNote': 'hasta el {date}',
|
|
50
|
+
'shell.no': 'Ahora no', 'shell.noNote': 'un chat pregunta antes cuando los necesita',
|
|
51
|
+
'shell.off': 'Desactivar', 'shell.offNote': 'un chat pregunta antes cuando los necesita',
|
|
52
|
+
'shell.d7': 'Sí, durante 7 días', 'shell.d30': 'Sí, durante 30 días', 'shell.d365': 'Sí, durante un año',
|
|
53
|
+
'review.title': 'Revisar y guardar',
|
|
54
|
+
'review.folders': 'Carpetas', 'review.commands': 'Comandos',
|
|
55
|
+
'review.noFolders': 'ninguna — un chat pregunta cuando necesita una',
|
|
56
|
+
'review.removed': 'aprobación retirada',
|
|
57
|
+
'review.cmdNo': 'no aprobados — un chat pregunta antes',
|
|
58
|
+
'review.cmdUntil': 'aprobados hasta el {date}',
|
|
59
|
+
'review.save': 'Guardar', 'review.back': 'Atrás', 'review.quit': 'Salir sin guardar',
|
|
60
|
+
'review.where': 'Se guarda solo en este ordenador, en {file}',
|
|
61
|
+
keyNames: { space: 'espacio', enter: 'enter', esc: 'esc', tab: 'tab', backspace: 'retroceso' },
|
|
62
|
+
keys: { move: 'mover', select: 'elegir', toggle: 'marcar', back: 'atrás', quit: 'salir', open: 'abrir', close: 'cerrar', openClose: 'abrir/cerrar', up: 'carpeta superior', hidden: 'ocultas', done: 'listo', tab: 'completar', add: 'añadir' },
|
|
63
|
+
'done.saved': 'Guardado en este ordenador.',
|
|
64
|
+
'done.unchanged': 'No se ha cambiado nada.',
|
|
65
|
+
'done.web': 'Cuando un chat de tensor.chat comparta una de estas carpetas, este ordenador lo confirmará por sí solo.',
|
|
66
|
+
'done.run': 'Inicia el Companion con:',
|
|
67
|
+
'done.noTty': 'La configuración necesita un terminal interactivo. Sin él, usa approve-folder y approve-shell.',
|
|
68
|
+
};
|