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:
@@ -0,0 +1,303 @@
|
||||
// English is the source of truth. Other locales mirror this shape; missing keys
|
||||
// fall back to English at lookup time. Placeholders use {name} interpolation.
|
||||
// Keep Transloco-compatible (nested namespaces, {param}) so a later migration
|
||||
// to a real i18n library is a drop-in of these dictionaries.
|
||||
|
||||
export const en = {
|
||||
common: {
|
||||
cancel: "Cancel",
|
||||
save: "Save",
|
||||
close: "Close",
|
||||
delete: "Delete",
|
||||
rename: "Rename",
|
||||
create: "Create",
|
||||
apply: "Apply",
|
||||
choose: "Choose…",
|
||||
connect: "Connect",
|
||||
local: "Local",
|
||||
},
|
||||
app: {
|
||||
notConnectedTitle: "Not connected to the app",
|
||||
notConnectedBody: "This page is running in a plain browser. Launch the desktop app to use it.",
|
||||
managerOpen: "The connection manager is open.",
|
||||
showConnections: "Show Connections",
|
||||
},
|
||||
tabBar: {
|
||||
connections: "Connections",
|
||||
manageConnections: "Manage connections",
|
||||
newTab: "New tab ({mod}T)",
|
||||
closeTab: "Close tab ({mod}W)",
|
||||
options: "Options",
|
||||
},
|
||||
options: {
|
||||
title: "Options",
|
||||
language: "Language",
|
||||
system: "System",
|
||||
theme: { label: "Theme", system: "System", light: "Light", dark: "Dark" },
|
||||
tone: { label: "Tone", dark: "Dark", darker: "Darker" },
|
||||
accent: { label: "Accent" },
|
||||
accentNames: {
|
||||
graphite: "Graphite",
|
||||
blue: "Blue",
|
||||
indigo: "Indigo",
|
||||
purple: "Purple",
|
||||
rose: "Rose",
|
||||
orange: "Orange",
|
||||
green: "Green",
|
||||
teal: "Teal",
|
||||
},
|
||||
fileIcons: { label: "File icons", plain: "Plain", multi: "Multi" },
|
||||
interface: { label: "Interface", compact: "Compact", default: "Default", large: "Large" },
|
||||
speedUnit: { label: "Speed unit" },
|
||||
transfers: { label: "Transfers", single: "Single", multi: "Multi" },
|
||||
atOnce: "At once",
|
||||
editor: { label: "Editor", builtin: "Built-in", custom: "Custom" },
|
||||
systemDefault: "System default",
|
||||
resetDefault: "Reset to system default",
|
||||
finishedTransfers: { label: "Finished transfers", autoClear: "Auto-clear", keep: "Keep" },
|
||||
},
|
||||
kinds: {
|
||||
folder: "Folder",
|
||||
symlink: "Symlink",
|
||||
file: "File",
|
||||
image: "Image",
|
||||
audio: "Audio",
|
||||
video: "Video",
|
||||
archive: "Archive",
|
||||
code: "Code",
|
||||
document: "Document",
|
||||
},
|
||||
columns: {
|
||||
name: "Name",
|
||||
size: "Size",
|
||||
mtime: "Modified",
|
||||
kind: "Kind",
|
||||
rights: "Rights",
|
||||
owner: "Owner",
|
||||
group: "Group",
|
||||
},
|
||||
panel: {
|
||||
choosePanel: "Choose what this panel shows",
|
||||
copy: "Copy",
|
||||
copyToRight: "Copy to right panel (F5)",
|
||||
copyToLeft: "Copy to left panel (F5)",
|
||||
edit: "Edit",
|
||||
editFile: "Edit file (F4)",
|
||||
rename: "Rename (F2)",
|
||||
newFolder: "New folder (F7)",
|
||||
newFile: "New file",
|
||||
permissions: "Permissions",
|
||||
delete: "Delete",
|
||||
deleteHint: "Delete (F8 / Del)",
|
||||
parentDir: "Parent directory (Backspace)",
|
||||
refresh: "Refresh",
|
||||
clearFilter: "Clear filter (Esc)",
|
||||
sshConfig: "~/.ssh/config",
|
||||
connectingTo: "Connecting to {name}…",
|
||||
emptyDir: "Empty directory",
|
||||
noMatches: "No matches",
|
||||
matches: { one: "{count} match · Esc", other: "{count} matches · Esc" },
|
||||
},
|
||||
connMgr: {
|
||||
title: "Connections",
|
||||
importWinscp: "Import from WinSCP…",
|
||||
importWinscpHint: "Import sessions from a WinSCP.ini export",
|
||||
newConnection: "Connection",
|
||||
newConnectionHint: "New connection",
|
||||
newStack: "Stack",
|
||||
newStackHint: "New stack",
|
||||
expand: "Expand",
|
||||
collapse: "Collapse",
|
||||
renameStack: "Rename stack",
|
||||
deleteStack: "Delete stack",
|
||||
openStack: "Open stack in tabs",
|
||||
empty: "empty",
|
||||
connectionsHeader: "Connections",
|
||||
noneTitle: "No saved connections yet.",
|
||||
noneBody: "Create one, or import your WinSCP sessions.",
|
||||
name: "Name",
|
||||
namePlaceholder: "my server",
|
||||
host: "Host",
|
||||
port: "Port",
|
||||
stack: "Stack",
|
||||
stackNone: "(none)",
|
||||
username: "Username",
|
||||
remotePath: "Remote path (optional)",
|
||||
remotePathPlaceholder: "home directory",
|
||||
auth: "Auth",
|
||||
authPassword: "Password",
|
||||
authKey: "Private key",
|
||||
password: "Password",
|
||||
privateKeyFile: "Private key file",
|
||||
passphrase: "Passphrase (optional)",
|
||||
localDir: "Local directory (optional)",
|
||||
localDirPlaceholder: "~/projects/my-server",
|
||||
localDirHint: "The other panel opens here when it's on Local and hasn't been navigated.",
|
||||
more: "More…",
|
||||
less: "Less…",
|
||||
selectPrompt: "Select a connection or create a new one.",
|
||||
newStackTitle: "New stack",
|
||||
newStackPrompt: "stack name",
|
||||
renameStackTitle: "Rename stack",
|
||||
deleteStackTitle: "Delete stack",
|
||||
deleteStackBody: 'Delete stack "{name}"? Its connections stay, ungrouped.',
|
||||
deleteConnectionTitle: "Delete connection",
|
||||
deleteConnectionBody: 'Delete "{name}"?',
|
||||
},
|
||||
editor: {
|
||||
noFileTitle: "No file to edit",
|
||||
noFileBody: "This window was opened without an editor context. Launch files from the app.",
|
||||
wrap: "Wrap",
|
||||
wrapHint: "Wrap long lines — hides line numbers ({alt}Z)",
|
||||
findHint: "Find ({mod}F)",
|
||||
findPlaceholder: "Find in file",
|
||||
prevMatch: "Previous match (⇧↵)",
|
||||
nextMatch: "Next match (↵)",
|
||||
closeHint: "Close (Esc)",
|
||||
disconnected: "Disconnected from {name} — saving will fail, but your text is preserved in this window.",
|
||||
cantOpen: "Can't open {name}",
|
||||
closeWindow: "Close Window",
|
||||
shortcuts: "{mod}S save · {mod}F find · {mod}W close",
|
||||
lines: { one: "{count} line", other: "{count} lines" },
|
||||
close: "Close",
|
||||
save: "Save",
|
||||
saveChangesTitle: 'Save changes to "{name}"?',
|
||||
saveChangesBody: "Your changes will be lost if you don't save them.",
|
||||
dontSave: "Don't Save",
|
||||
countLabel: "{current} of {total}",
|
||||
noMatches: "No matches",
|
||||
},
|
||||
transfer: {
|
||||
summary: "{active} active · {finished} finished",
|
||||
clearFinished: "Clear finished",
|
||||
cancel: "Cancel",
|
||||
dismiss: "Dismiss",
|
||||
showDetails: "Show transfer details",
|
||||
copying: "Copying {name} · {percent}%{speed}",
|
||||
queued: "{count} queued",
|
||||
failed: { one: "{count} transfer failed", other: "{count} transfers failed" },
|
||||
finished: "Transfers finished",
|
||||
ready: "Ready",
|
||||
items: "{count} items",
|
||||
selected: "{count} selected",
|
||||
queuedState: "Queued",
|
||||
scanning: "Scanning…",
|
||||
failedState: "Failed",
|
||||
cancelled: "Cancelled",
|
||||
completedFiles: { one: "{count} file · {size}", other: "{count} files · {size}" },
|
||||
left: "{eta} left",
|
||||
},
|
||||
dialogs: {
|
||||
chmodTitle: "Permissions",
|
||||
chmodItems: "{count} items",
|
||||
read: "Read",
|
||||
write: "Write",
|
||||
execute: "Execute",
|
||||
roleOwner: "Owner",
|
||||
roleGroup: "Group",
|
||||
roleOthers: "Others",
|
||||
octal: "Octal",
|
||||
mergeFolder: "Merge folder",
|
||||
replace: "Replace",
|
||||
merge: "Merge",
|
||||
mergeAll: "Merge all",
|
||||
replaceAll: "Replace all",
|
||||
skip: "Skip",
|
||||
skipAll: "Skip all",
|
||||
ofTotal: "{index} of {total}",
|
||||
overwriteDirBody: "The destination already has a folder with this name. Merging may replace files inside it.",
|
||||
overwriteFileBody: "The destination already has a file with this name.",
|
||||
source: "Source",
|
||||
destination: "Destination",
|
||||
newer: "newer",
|
||||
},
|
||||
toasts: {
|
||||
movedToTrash: 'Moved "{name}" to Trash',
|
||||
editingExternal: "Editing {name} — saves upload automatically",
|
||||
nothingToCopy: "Nothing to copy (symlinks are skipped)",
|
||||
nothingToCopyAllSkipped: "Nothing to copy — all conflicting items were skipped",
|
||||
sameFolder: "Both panels show the same folder",
|
||||
sameFile: 'Skipped "{name}" — it is the same file',
|
||||
typeMismatch: 'Skipped "{name}" — a different kind of item is in the way',
|
||||
transferFailed: 'Transfer "{name}" failed: {error}',
|
||||
unknownError: "unknown error",
|
||||
saved: "Saved {name}",
|
||||
disconnectedReason: "{title}: disconnected ({reason})",
|
||||
imported: { one: "Imported {count} connection", other: "Imported {count} connections" },
|
||||
importedIntoStacks: { one: " into {count} new stack", other: " into {count} new stacks" },
|
||||
importedSkipped: " ({skipped} skipped)",
|
||||
leftoverTitle: "Leftover edited files",
|
||||
leftoverBody: {
|
||||
one: "A previous session left {count} temporary edited file behind ({names}). Clear them?",
|
||||
other: "A previous session left {count} temporary edited files behind ({names}). Clear them?",
|
||||
},
|
||||
leftoverMore: ", +{count} more",
|
||||
leftoverClear: "Clear",
|
||||
},
|
||||
fileActions: {
|
||||
deleteTitle: "Delete",
|
||||
deleteBody: "Delete {what}? Folders are deleted recursively. This cannot be undone.",
|
||||
deleteConfirm: "Delete",
|
||||
renameTitle: 'Rename "{name}"',
|
||||
renameConfirm: "Rename",
|
||||
newFolderTitle: "New folder",
|
||||
newFolderPlaceholder: "folder name",
|
||||
newFileTitle: "New file",
|
||||
newFilePlaceholder: "file name",
|
||||
createConfirm: "Create",
|
||||
itemsCount: "{count} items",
|
||||
},
|
||||
menu: {
|
||||
about: "About {app}",
|
||||
quit: "Quit",
|
||||
file: "File",
|
||||
newTab: "New Tab",
|
||||
closeTab: "Close Tab",
|
||||
newFolder: "New Folder",
|
||||
newFile: "New File",
|
||||
editFile: "Edit File",
|
||||
connect: "Connect",
|
||||
connectionManager: "Connection Manager…",
|
||||
edit: "Edit",
|
||||
view: "View",
|
||||
refresh: "Refresh",
|
||||
window: "Window",
|
||||
},
|
||||
ctx: {
|
||||
size: "Size",
|
||||
modified: "Modified",
|
||||
kind: "Kind",
|
||||
rights: "Rights",
|
||||
owner: "Owner",
|
||||
group: "Group",
|
||||
revealFinder: "Reveal in Finder",
|
||||
revealExplorer: "Show in Explorer",
|
||||
revealFileManager: "Show in File Manager",
|
||||
moveToTrash: "Move to Trash",
|
||||
moveToRecycleBin: "Move to Recycle Bin",
|
||||
newFolder: "New Folder",
|
||||
newFile: "New File",
|
||||
refresh: "Refresh",
|
||||
open: "Open",
|
||||
openDefault: "Open with the default application",
|
||||
edit: "Edit",
|
||||
copyToOther: "Copy to Other Panel",
|
||||
rename: "Rename",
|
||||
permissions: "Permissions",
|
||||
copyPath: "Copy Path",
|
||||
delete: "Delete",
|
||||
columns: "Columns",
|
||||
trashTooltip: "Finder's Restore is not available for items trashed from here",
|
||||
},
|
||||
errors: {
|
||||
sshAgentUnavailable: "ssh-agent not available (SSH_AUTH_SOCK is not set)",
|
||||
identityFileNotFound: "identity file not found: {path}",
|
||||
sessionNotConnected: 'Session "{id}" is not connected',
|
||||
localCannotDisconnect: "The local session cannot be disconnected",
|
||||
notADirectory: '"{path}" exists and is not a directory',
|
||||
sameFileTransfer: '"{name}": source and destination are the same file',
|
||||
sameFileNoName: "Source and destination are the same file",
|
||||
folderOntoItself: "Cannot copy a folder onto itself",
|
||||
folderIntoItself: "Cannot copy a folder into itself",
|
||||
},
|
||||
} as const;
|
||||
Reference in New Issue
Block a user