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

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:
2026-09-02 13:38:28 -03:00
co-authored by Claude Opus 5
parent a54d998ba9
commit 115c81cba3
10 changed files with 448 additions and 132 deletions
+129
View File
@@ -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;