Internationalization: 7 languages with live switching
Custom signal-based runtime (no i18n dependency): shared src/shared/i18n dictionaries, ui I18nService.t() reactive by construction so language switches live, and a backend i18n.ts for native menus + thrown errors. Every user-facing string keyed across templates, toasts, dialogs, file kinds, columns, macOS menu/context menus, and errors. Language picker in Options (auto-detects OS locale, falls back to English); switching relabels the webview and native chrome instantly. English/Spanish/French/ German fully drafted; Chinese/Japanese/Basque drafted, flagged for native review. Docs updated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+29
-35
@@ -1,25 +1,21 @@
|
||||
import { ContextMenu, Utils, type ApplicationMenuItemConfig } from "electrobun/bun";
|
||||
import type { ColumnKey } from "../shared/types";
|
||||
import { t } from "./i18n";
|
||||
|
||||
/** Optional list columns, in display order (Name is fixed). */
|
||||
const COLUMNS: { key: ColumnKey; label: string }[] = [
|
||||
{ key: "size", label: "Size" },
|
||||
{ key: "mtime", label: "Modified" },
|
||||
{ key: "kind", label: "Kind" },
|
||||
{ key: "rights", label: "Rights" },
|
||||
{ key: "owner", label: "Owner" },
|
||||
{ key: "group", label: "Group" },
|
||||
];
|
||||
const COLUMN_KEYS: ColumnKey[] = ["size", "mtime", "kind", "rights", "owner", "group"];
|
||||
|
||||
// PORTABILITY: shell-integration wording per OS
|
||||
// PORTABILITY: shell-integration wording per OS (resolved at show() for locale)
|
||||
const IS_WIN = process.platform === "win32";
|
||||
const REVEAL_LABEL =
|
||||
process.platform === "darwin"
|
||||
? "Reveal in Finder"
|
||||
: IS_WIN
|
||||
? "Show in Explorer"
|
||||
: "Show in File Manager";
|
||||
const TRASH_LABEL = IS_WIN ? "Move to Recycle Bin" : "Move to Trash";
|
||||
const revealLabel = (): string =>
|
||||
t(
|
||||
process.platform === "darwin"
|
||||
? "ctx.revealFinder"
|
||||
: IS_WIN
|
||||
? "ctx.revealExplorer"
|
||||
: "ctx.revealFileManager",
|
||||
);
|
||||
const trashLabel = (): string => t(IS_WIN ? "ctx.moveToRecycleBin" : "ctx.moveToTrash");
|
||||
|
||||
export interface RowMenuContext {
|
||||
path: string;
|
||||
@@ -75,8 +71,8 @@ export class PanelContextMenu {
|
||||
/** Column picker for the list headers: checkable toggles per column. */
|
||||
showColumns(visible: ColumnKey[]): void {
|
||||
ContextMenu.showContextMenu(
|
||||
COLUMNS.map(({ key, label }) => ({
|
||||
label,
|
||||
COLUMN_KEYS.map((key) => ({
|
||||
label: t(`columns.${key}`),
|
||||
action: `col:${key}`,
|
||||
checked: visible.includes(key),
|
||||
})),
|
||||
@@ -88,47 +84,45 @@ export class PanelContextMenu {
|
||||
|
||||
if (!ctx.hasEntry || ctx.name === "..") {
|
||||
// empty space (or the parent row): folder-level actions
|
||||
items.push({ label: "New Folder", action: "ctx-new-folder" });
|
||||
items.push({ label: "New File", action: "ctx-new-file" });
|
||||
items.push({ label: t("ctx.newFolder"), action: "ctx-new-folder" });
|
||||
items.push({ label: t("ctx.newFile"), action: "ctx-new-file" });
|
||||
items.push({ type: "separator" });
|
||||
items.push({ label: "Refresh", action: "ctx-refresh" });
|
||||
items.push({ label: t("ctx.refresh"), action: "ctx-refresh" });
|
||||
if (ctx.isLocal) {
|
||||
items.push({ type: "separator" });
|
||||
items.push({ label: REVEAL_LABEL, action: "ctx-reveal" });
|
||||
items.push({ label: revealLabel(), action: "ctx-reveal" });
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
if (ctx.type === "dir") {
|
||||
items.push({ label: "Open", action: "ctx-open" });
|
||||
items.push({ label: t("ctx.open"), action: "ctx-open" });
|
||||
} else {
|
||||
if (ctx.isLocal) {
|
||||
items.push({ label: "Open", action: "ctx-open-default", tooltip: "Open with the default application" });
|
||||
items.push({ label: t("ctx.open"), action: "ctx-open-default", tooltip: t("ctx.openDefault") });
|
||||
}
|
||||
items.push({ label: "Edit", action: "ctx-edit", accelerator: "e" });
|
||||
items.push({ label: t("ctx.edit"), action: "ctx-edit", accelerator: "e" });
|
||||
}
|
||||
items.push({ label: "Copy to Other Panel", action: "ctx-copy" });
|
||||
items.push({ label: "Rename", action: "ctx-rename" });
|
||||
items.push({ label: t("ctx.copyToOther"), action: "ctx-copy" });
|
||||
items.push({ label: t("ctx.rename"), action: "ctx-rename" });
|
||||
// chmod on Windows local files only flips the read-only bit — hide the lie
|
||||
if (!(ctx.isLocal && IS_WIN)) {
|
||||
items.push({ label: "Permissions", action: "ctx-chmod" });
|
||||
items.push({ label: t("ctx.permissions"), action: "ctx-chmod" });
|
||||
}
|
||||
items.push({ type: "separator" });
|
||||
items.push({ label: "Copy Path", action: "ctx-copy-path" });
|
||||
items.push({ label: t("ctx.copyPath"), action: "ctx-copy-path" });
|
||||
if (ctx.isLocal) {
|
||||
items.push({ label: REVEAL_LABEL, action: "ctx-reveal" });
|
||||
items.push({ label: revealLabel(), action: "ctx-reveal" });
|
||||
}
|
||||
items.push({ type: "separator" });
|
||||
if (ctx.isLocal) {
|
||||
items.push({
|
||||
label: TRASH_LABEL,
|
||||
label: trashLabel(),
|
||||
action: "ctx-trash",
|
||||
...(process.platform === "darwin"
|
||||
? { tooltip: "Finder's Restore is not available for items trashed from here" }
|
||||
: {}),
|
||||
...(process.platform === "darwin" ? { tooltip: t("ctx.trashTooltip") } : {}),
|
||||
});
|
||||
}
|
||||
items.push({ label: "Delete", action: "ctx-delete" });
|
||||
items.push({ label: t("ctx.delete"), action: "ctx-delete" });
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user