feat(i18n): translate five more systems, and check for updates more often
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Canceled after 0s
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Canceled after 0s
Update cadence: the desktop app checked once ten seconds after launch and
then every four hours. A release published while the app sits open went
unnoticed until the next poll — which is exactly what happened with 1.4.0.
It now polls every 30 minutes and also checks when the window regains focus,
throttled to one check per five minutes so alt-tabbing does not hammer the
feed. `lastCheckAt` is stamped from the `checking-for-update` event rather
than by the callers, so a manual check from the settings panel counts too.
The focus listener is on `app`, not on mainWindow, because the window is
destroyed and recreated when closed to tray.
Translations, five whole systems:
- Settings > Desktop (autostart, updates, instance)
- Settings > Voice: output device, which completes that tab
- Settings > Keybinds, including the action names and conflict prompt
- Settings > Account, the largest — profile, password, detached-instance
notice, danger zone
- Explore spaces
Two sentences wrap a literal that must not be translated (the `input` group
name, the `user@instance` example). Those are split around a placeholder
rather than interpolated as text, so a translation keeps control of the word
order on both sides of it. Keybind action labels gained a `labelKey`
alongside the English `label`, which stays for non-React callers.
Verified: web build (tsc + vite), desktop typecheck, 490 web tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -363,6 +363,135 @@ export const en = {
|
||||
'invite.fail.invalid_target': 'Invalid target',
|
||||
'invite.fail.network': "Couldn't reach your instance",
|
||||
'invite.fail.generic': "Couldn't send (server error)",
|
||||
// Settings — desktop
|
||||
'settings.desktop.title': 'Desktop',
|
||||
'settings.desktop.startAtBoot': 'Start at boot',
|
||||
'settings.desktop.startAtBoot.description': 'Automatically launch Backspace when you log in',
|
||||
'settings.desktop.startMinimized': 'Start minimized',
|
||||
'settings.desktop.startMinimized.description': 'Start hidden in the system tray instead of showing the window',
|
||||
'settings.desktop.version': 'Version {version}',
|
||||
'settings.desktop.appName': 'Backspace Desktop',
|
||||
'settings.desktop.update.description': 'Check for new versions of the desktop app',
|
||||
'settings.desktop.update.check': 'Check for Updates',
|
||||
'settings.desktop.update.checking': 'Checking...',
|
||||
'settings.desktop.instance.current': 'Currently connected instance',
|
||||
'settings.desktop.instance.change': 'Change Instance',
|
||||
|
||||
// Settings — voice: output
|
||||
'settings.output.title': 'Output Device',
|
||||
'settings.output.checking': 'Checking audio access…',
|
||||
'settings.output.systemDefault': 'System Default',
|
||||
'settings.output.unsupported': "This browser doesn't support choosing an output device. Audio plays to the system default.",
|
||||
'settings.output.needPermission': 'Grant microphone permission to list output devices (browsers gate output names behind microphone access).',
|
||||
'settings.output.enableAccess': 'Enable audio access',
|
||||
'settings.output.volume': 'Output Volume',
|
||||
'settings.output.testSound': 'Play test sound',
|
||||
|
||||
// Settings — keybinds
|
||||
'settings.keybinds.title': 'Keybinds',
|
||||
'settings.keybinds.section.voice': 'Voice Shortcuts',
|
||||
'settings.keybinds.pressCombo': 'Press a key combo...',
|
||||
'settings.keybinds.notBound': 'Not bound',
|
||||
'settings.keybinds.edit': 'Edit',
|
||||
'settings.keybinds.record': 'Record',
|
||||
'settings.keybinds.delete': 'Delete',
|
||||
'settings.keybinds.escToCancel': 'ESC to cancel',
|
||||
'settings.keybinds.accessibility.title': 'Accessibility Permission Required',
|
||||
'settings.keybinds.accessibility.description': 'Backspace needs Accessibility permission for global shortcuts to work outside the app.',
|
||||
'settings.keybinds.accessibility.grant': 'Grant Permission',
|
||||
'settings.keybinds.hookError.title': 'Global Shortcuts Unavailable',
|
||||
'settings.keybinds.hookError.description': 'Failed to start input listener. On Linux, your user may need to be in the {group} group.',
|
||||
'settings.keybinds.webNote': 'Shortcuts work while this tab is focused. For global shortcuts that work in other apps, use the desktop app.',
|
||||
'settings.keybinds.conflict.suffix': '. Overwrite?',
|
||||
'settings.keybinds.conflict.boundTo': 'is already bound to',
|
||||
'settings.keybinds.conflict.overwrite': 'Overwrite',
|
||||
'settings.keybinds.conflict.cancel': 'Cancel',
|
||||
'settings.keybinds.action.toggleMute': 'Toggle Mute',
|
||||
'settings.keybinds.action.toggleDeafen': 'Toggle Deafen',
|
||||
'settings.keybinds.action.pushToTalk': 'Push to Talk',
|
||||
'settings.keybinds.action.toggleCamera': 'Toggle Camera',
|
||||
'settings.keybinds.action.toggleScreenShare': 'Toggle Screen Share',
|
||||
'settings.keybinds.action.disconnect': 'Disconnect',
|
||||
|
||||
// Settings — account
|
||||
'settings.account.title': 'My Account',
|
||||
'settings.account.detached.lead': 'This account is detached from its home instance.',
|
||||
'settings.account.detached.body': '{instance} was reset or is no longer available, so this account now operates locally on this instance — your profile and password are managed here.',
|
||||
'settings.account.detached.relink': 'As {username} on {instance}, you can re-link this account — profile and presence will sync from there again.',
|
||||
'settings.account.detached.reattaching': 'Re-attaching…',
|
||||
'settings.account.detached.confirm': 'Confirm re-attach as {username}',
|
||||
'settings.account.detached.reattach': 'Re-attach to {instance}',
|
||||
'settings.account.detached.failed': 'Re-attach failed',
|
||||
'settings.account.section.profile': 'Profile Customization',
|
||||
'settings.account.section.account': 'Account',
|
||||
'settings.account.section.password': 'Password',
|
||||
'settings.account.section.danger': 'Danger Zone',
|
||||
'settings.account.avatar': 'Avatar',
|
||||
'settings.account.avatar.change': 'Change Avatar',
|
||||
'settings.account.banner': 'Banner',
|
||||
'settings.account.banner.change': 'Change Banner',
|
||||
'settings.account.banner.chooseGif': 'Choose GIF',
|
||||
'settings.account.remove': 'Remove',
|
||||
'settings.account.clear': 'Clear',
|
||||
'settings.account.avatarColor': 'Avatar Color',
|
||||
'settings.account.bannerColor': 'Banner Color',
|
||||
'settings.account.aboutMe': 'About Me',
|
||||
'settings.account.aboutMe.placeholder': 'Tell the world about yourself...',
|
||||
'settings.account.status': 'Status',
|
||||
'settings.account.status.online': 'Online',
|
||||
'settings.account.status.idle': 'Idle',
|
||||
'settings.account.status.dnd': 'Do Not Disturb',
|
||||
'settings.account.displayName': 'Display Name',
|
||||
'settings.account.customStatus': 'Custom Status',
|
||||
'settings.account.customStatus.placeholder': 'What are you up to?',
|
||||
'settings.account.password.current': 'Current Password',
|
||||
'settings.account.password.current.placeholder': 'Enter current password',
|
||||
'settings.account.password.new': 'New Password',
|
||||
'settings.account.password.new.placeholder': 'Minimum 6 characters',
|
||||
'settings.account.password.confirm': 'Confirm New Password',
|
||||
'settings.account.password.confirm.placeholder': 'Confirm new password',
|
||||
'settings.account.password.change': 'Change Password',
|
||||
'settings.account.password.changing': 'Changing...',
|
||||
'settings.account.password.changed': 'Password changed',
|
||||
'settings.account.password.tooShort': 'New password must be at least 8 characters',
|
||||
'settings.account.password.mismatch': 'Passwords do not match',
|
||||
'settings.account.password.failed': 'Failed to change password',
|
||||
'settings.account.sync.synced': 'Synced',
|
||||
'settings.account.sync.failed': 'Failed — will sync on reconnect',
|
||||
'settings.account.delete.warning': 'Once you delete your account, there is no going back. Your messages will remain but be attributed to "Deleted User".',
|
||||
'settings.account.delete': 'Delete Account',
|
||||
'settings.account.reset': 'Reset',
|
||||
'settings.account.save': 'Save',
|
||||
'settings.account.saving': 'Saving...',
|
||||
'settings.account.saved': 'Profile updated',
|
||||
'settings.account.saveFailed': 'Failed to update profile',
|
||||
'settings.account.avatar.uploadFailed': 'Failed to upload avatar',
|
||||
'settings.account.banner.uploadFailed': 'Failed to upload banner',
|
||||
'settings.account.crop.avatar': 'Crop Avatar',
|
||||
'settings.account.crop.banner': 'Crop Banner',
|
||||
|
||||
// Explore spaces
|
||||
'explore.title': 'Explore',
|
||||
'explore.searchPlaceholder': 'Search spaces...',
|
||||
'explore.disabled': 'Space discovery is disabled by the instance administrator.',
|
||||
'explore.empty.search': 'No spaces match your search.',
|
||||
'explore.empty.none': 'No discoverable spaces yet.',
|
||||
'explore.allJoined': "You're in all discoverable spaces",
|
||||
'explore.joined': 'Joined',
|
||||
'explore.badge.public': 'Public',
|
||||
'explore.badge.request': 'Request',
|
||||
'explore.noDescription': 'No description',
|
||||
'explore.member': 'member',
|
||||
'explore.members': 'members',
|
||||
'explore.view': 'View Space',
|
||||
'explore.join': 'Join Space',
|
||||
'explore.joining': 'Joining...',
|
||||
'explore.requestPending': 'Request Pending',
|
||||
'explore.requestToJoin': 'Request to Join',
|
||||
'explore.request.placeholder': 'Why do you want to join? (optional)',
|
||||
'explore.request.send': 'Send Request',
|
||||
'explore.request.sending': 'Sending...',
|
||||
'explore.request.cancel': 'Cancel',
|
||||
} as const;
|
||||
|
||||
export type TranslationKey = keyof typeof en;
|
||||
|
||||
Reference in New Issue
Block a user