Mehrsprachigkeit (Englisch/Deutsch, Systemsprache oder Auswahl in den Einstellungen), README auf Englisch für Nutzer, Version 0.4.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-25 18:18:03 +02:00
co-authored by Claude Opus 5.5
parent b578a6c7ec
commit 2890844c81
14 changed files with 761 additions and 322 deletions
+16 -12
View File
@@ -9,6 +9,8 @@ const { SshManager } = require('./ssh');
const rdp = require('./rdp');
const { Updater } = require('./updater');
const { createEmbed, embedSupported } = require('./rdp-embed');
const i18n = require('../i18n');
const applyLanguage = () => i18n.setLanguage(store.get().settings.language, app.getLocale());
let win;
const store = new Store();
@@ -34,12 +36,12 @@ async function confirmHostKey(target, fingerprint, known) {
const changed = !!known;
const r = await dialog.showMessageBox(win, {
type: changed ? 'warning' : 'question',
title: changed ? 'Host-Schlüssel hat sich geändert!' : 'Unbekannter Host',
title: changed ? i18n.t('Host key has changed!') : i18n.t('Unknown host'),
message: changed
? `WARNUNG: Der Host-Schlüssel von ${target.host}:${target.port} hat sich geändert. Das kann auf einen Man-in-the-Middle-Angriff hindeuten.`
: `Die Echtheit von ${target.host}:${target.port} kann nicht bestätigt werden.`,
detail: `Fingerprint:\n${fingerprint}${changed ? `\n\nBisher gespeichert:\n${known.fingerprint}` : ''}`,
buttons: [changed ? 'Trotzdem verbinden & ersetzen' : 'Vertrauen & verbinden', 'Abbrechen'],
? i18n.t('WARNING: The host key of {target} has changed. This may indicate a man-in-the-middle attack.', { target: `${target.host}:${target.port}` })
: i18n.t('The authenticity of {target} cannot be verified.', { target: `${target.host}:${target.port}` }),
detail: `Fingerprint:\n${fingerprint}${changed ? `\n\n${i18n.t('Previously stored:')}\n${known.fingerprint}` : ''}`,
buttons: [changed ? i18n.t('Connect anyway & replace') : i18n.t('Trust & connect'), i18n.t('Cancel')],
defaultId: changed ? 1 : 0,
cancelId: 1,
});
@@ -104,6 +106,7 @@ handle('vault:upsert', (col, item) => store.upsert(col, item));
handle('vault:remove', (col, id) => store.remove(col, id));
handle('vault:settings', (s) => {
store.setSettings(s);
if ('language' in s) applyLanguage();
if ('rdpEmbed' in s) fs.writeFileSync(launchFile, JSON.stringify({ ...readLaunch(), x11: s.rdpEmbed !== false }));
});
handle('vault:forgetHost', (id) => { delete store.get().knownHosts[id]; store.save(); });
@@ -125,7 +128,7 @@ handle('vault:import', async () => {
// Import aus ~/.ssh/config
handle('vault:importSshConfig', () => {
const file = path.join(os.homedir(), '.ssh', 'config');
if (!fs.existsSync(file)) throw new Error('~/.ssh/config nicht gefunden');
if (!fs.existsSync(file)) throw new Error(i18n.t('~/.ssh/config not found'));
const entries = [];
let cur = null;
for (const raw of fs.readFileSync(file, 'utf8').split(/\r?\n/)) {
@@ -188,7 +191,7 @@ handle('key:pickFile', async () => {
function hostWithOverrides(hostOrId) {
if (typeof hostOrId === 'string') {
const h = store.resolveHost(hostOrId);
if (!h) throw new Error('Host nicht gefunden');
if (!h) throw new Error(i18n.t('Host not found'));
return h;
}
return hostOrId; // Quick-Connect: temporärer Host
@@ -245,7 +248,7 @@ handle('local:op', (op, a, b) => {
// ---------- Port-Forwarding ----------
handle('fw:start', (id) => {
const fw = store.get().forwards.find((f) => f.id === id);
if (!fw) throw new Error('Regel nicht gefunden');
if (!fw) throw new Error(i18n.t('Rule not found'));
return ssh.startForward(fw, (ev) => send('fw:event', id, ev));
});
handle('fw:stop', (id) => ssh.stopForward(id));
@@ -258,8 +261,8 @@ handle('rdp:launch', (hostId) => {
if (host.id) store.addHistory({ hostId: host.id, at: Date.now() });
const r = rdp.launch(host, store.get().settings);
if (r.process) {
r.process.on('exit', (code) => { if (code && code !== 0 && code !== 12) send('toast', `RDP beendet (Code ${code}): ${r.getErr().split('\n').filter(Boolean).slice(-1)[0] || ''}`, 'error'); });
r.process.on('error', (e) => send('toast', 'RDP-Start fehlgeschlagen: ' + e.message, 'error'));
r.process.on('exit', (code) => { if (code && code !== 0 && code !== 12) send('toast', i18n.t('RDP exited (code {code}): {err}', { code, err: r.getErr().split('\n').filter(Boolean).slice(-1)[0] || '' }), 'error'); });
r.process.on('error', (e) => send('toast', i18n.t('RDP launch failed: {msg}', { msg: e.message }), 'error'));
}
return r.client;
});
@@ -268,7 +271,7 @@ handle('rdp:launch', (hostId) => {
const rdpSessions = new Map();
handle('rdp:embedSupported', () => {
const s = embedSupported();
if (s.ok && process.platform === 'linux' && !rdp.linuxClient(null, true)) return { ok: false, reason: 'xfreerdp3 fehlt (sudo pacman -S freerdp)' };
if (s.ok && process.platform === 'linux' && !rdp.linuxClient(null, true)) return { ok: false, reason: i18n.t('xfreerdp3 missing (sudo pacman -S freerdp)') };
return s;
});
handle('rdp:open', async (id, hostRef, bounds) => {
@@ -320,7 +323,7 @@ handle('rdp:detach', (id) => {
// ---------- Import aus anderen Programmen (z. B. Devolutions RDM) ----------
handle('import:pickFile', async (title, extensions) => {
const r = await dialog.showOpenDialog(win, { title, filters: [{ name: title, extensions }, { name: 'Alle Dateien', extensions: ['*'] }], properties: ['openFile'] });
const r = await dialog.showOpenDialog(win, { title, filters: [{ name: title, extensions }, { name: i18n.t('All files'), extensions: ['*'] }], properties: ['openFile'] });
if (r.canceled) return null;
const buf = fs.readFileSync(r.filePaths[0]);
// UTF-16-Exporte (BOM) erkennen
@@ -364,6 +367,7 @@ handle('shell:open', (url) => shell.openExternal(url));
app.whenReady().then(() => {
store.load();
applyLanguage();
createWindow();
if (store.get().settings.updateAutoCheck && app.isPackaged) {
setTimeout(() => updater.check().then((r) => { if (r.available) send('update:available', r); }).catch(() => {}), 6000);