feat: add Russian localization and independent STT languages
This commit is contained in:
+34
-16
@@ -214,6 +214,7 @@ function startServer(port) {
|
||||
PORT: String(port),
|
||||
HOSTNAME: "127.0.0.1", // the server defaults to 0.0.0.0 — never
|
||||
LH_DATA_DIR: DATA_DIR(),
|
||||
LH_SYSTEM_LANGUAGE: app.getPreferredSystemLanguages()[0] || "en",
|
||||
// The auth/keys store — the child reads AND writes this file itself
|
||||
// (no env injection anymore: a spawn-time copy would shadow every
|
||||
// settings-page key change until restart; env wins over file).
|
||||
@@ -456,11 +457,11 @@ function checkForUpdates({ manual = false } = {}) {
|
||||
// update-downloaded. Manual checks additionally answer directly.
|
||||
if (!manual) return;
|
||||
const version = result?.updateInfo?.version;
|
||||
if (!version) dialog.showMessageBox({ type: "info", message: "You're up to date.", detail: `LearnHarness ${app.getVersion()} is the latest version.` });
|
||||
if (!version) dialog.showMessageBox({ type: "info", message: nt("You're up to date."), detail: nt("LearnHarness {version} is the latest version.", { version: app.getVersion() }), buttons: [nt("OK")] });
|
||||
})
|
||||
.catch((err) => {
|
||||
log(`update check failed: ${err?.message ?? err}`);
|
||||
if (manual) dialog.showMessageBox({ type: "warning", message: "Couldn't check for updates.", detail: String(err?.message ?? err) });
|
||||
if (manual) dialog.showMessageBox({ type: "warning", message: nt("Couldn't check for updates."), buttons: [nt("OK")], detail: String(err?.message ?? err) });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -524,53 +525,63 @@ function applyZoom(win, factor) {
|
||||
|
||||
/** macOS needs an application menu with the Edit roles — without one,
|
||||
* Cmd+C/V/A do nothing in the renderer. Everything else stays minimal. */
|
||||
const { resolveLanguage: resolveShellLanguage, translate: translateShell } = require("./language.cjs");
|
||||
let shellLanguage;
|
||||
function nativeLanguage() {
|
||||
if (shellLanguage) return shellLanguage;
|
||||
let preference;
|
||||
try { preference = JSON.parse(fs.readFileSync(path.join(DATA_DIR(), "settings.json"), "utf8")).general?.language; } catch {}
|
||||
return resolveShellLanguage(preference, app.getPreferredSystemLanguages());
|
||||
}
|
||||
function nt(text, params) { return translateShell(nativeLanguage(), text, params); }
|
||||
|
||||
function installMenu() {
|
||||
const template = [
|
||||
{
|
||||
label: app.name,
|
||||
submenu: [
|
||||
{ role: "about" },
|
||||
{ role: "about", label: nt("About LearnHarness") },
|
||||
// Manual check (Wave C): answers via native dialog — no renderer
|
||||
// round-trip. The 24h automatic checks stay silent.
|
||||
{ label: "Check for Updates…", click: () => checkForUpdates({ manual: true }) },
|
||||
{ label: nt("Check for Updates…"), click: () => checkForUpdates({ manual: true }) },
|
||||
{ type: "separator" },
|
||||
{ role: "hide" },
|
||||
{ role: "hideOthers" },
|
||||
{ role: "hide", label: nt("Hide LearnHarness") },
|
||||
{ role: "hideOthers", label: nt("Hide Others") },
|
||||
{ type: "separator" },
|
||||
{ role: "quit" },
|
||||
{ role: "quit", label: nt("Quit LearnHarness") },
|
||||
],
|
||||
},
|
||||
{ label: "Edit", submenu: [{ role: "undo" }, { role: "redo" }, { type: "separator" }, { role: "cut" }, { role: "copy" }, { role: "paste" }, { role: "selectAll" }] },
|
||||
{ label: nt("Edit"), submenu: [{ role: "undo", label: nt("Undo") }, { role: "redo", label: nt("Redo") }, { type: "separator" }, { role: "cut", label: nt("Cut") }, { role: "copy", label: nt("Copy") }, { role: "paste", label: nt("Paste") }, { role: "selectAll", label: nt("Select All") }] },
|
||||
{
|
||||
label: "View",
|
||||
label: nt("View"),
|
||||
submenu: [
|
||||
{ role: "reload" },
|
||||
{ role: "toggleDevTools" },
|
||||
{ role: "reload", label: nt("Reload") },
|
||||
{ role: "toggleDevTools", label: nt("Developer Tools") },
|
||||
{ type: "separator" },
|
||||
// OWNED zoom (see applyZoom): the roles change zoomLevel
|
||||
// silently; these broadcast the factor and re-seat the
|
||||
// traffic lights. The renderer's --lh-zoom depends on
|
||||
// hearing every change.
|
||||
{
|
||||
label: "Actual Size",
|
||||
label: nt("Actual Size"),
|
||||
accelerator: "CommandOrControl+0",
|
||||
click: () => applyZoom(BrowserWindow.getAllWindows()[0], 1),
|
||||
},
|
||||
{
|
||||
label: "Zoom In",
|
||||
label: nt("Zoom In"),
|
||||
accelerator: "CommandOrControl+Plus",
|
||||
click: () => zoomStep(BrowserWindow.getAllWindows()[0], +1),
|
||||
},
|
||||
{
|
||||
label: "Zoom Out",
|
||||
label: nt("Zoom Out"),
|
||||
accelerator: "CommandOrControl+-",
|
||||
click: () => zoomStep(BrowserWindow.getAllWindows()[0], -1),
|
||||
},
|
||||
{ type: "separator" },
|
||||
{ role: "togglefullscreen" },
|
||||
{ role: "togglefullscreen", label: nt("Toggle Full Screen") },
|
||||
],
|
||||
},
|
||||
{ role: "windowMenu" },
|
||||
{ label: nt("Window"), submenu: [{ role: "minimize", label: nt("Minimize") }, { role: "close", label: nt("Close") }] },
|
||||
];
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
||||
}
|
||||
@@ -647,6 +658,13 @@ async function main() {
|
||||
});
|
||||
|
||||
installMenu();
|
||||
ipcMain.on("lh:language", (event, language) => {
|
||||
if (!trustedAppWindows.has(BrowserWindow.fromWebContents(event.sender)) || !trustedSender(event, new URL(url).origin, BrowserWindow)) return;
|
||||
if (language !== "en" && language !== "ru") return;
|
||||
if (shellLanguage === language) return;
|
||||
shellLanguage = language;
|
||||
installMenu();
|
||||
});
|
||||
|
||||
// Auto-update (Wave C): wire the updater before the window so the
|
||||
// banner channel is live from first paint; the first check and the 24h
|
||||
|
||||
Reference in New Issue
Block a user