feat(i18n): translate five more systems
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
- Settings > Voice: video — preview, permission states, camera picker.
This closes the Voice & Video tab; input, output and video are all done.
- Create channel / category / space
- Group DMs — new DM, add friends, group settings, leave
- Join a space — discovery, invite code, and both federated auth phases
- Delete account, transfer ownership, and the top-level error screen
Three patterns worth noting. Strings that are chosen in a helper or held in
state (the camera's hard-blocked copy, its preview errors, the space
visibility options, the invite failure reasons) now travel as translation
keys and are resolved where they render, so switching language re-labels
what is already on screen. Sentences that wrap an emphasised literal — the
host name when joining, the space and member names when transferring — are
split around their placeholder instead of interpolated, leaving word order
to the translation. And `CreateChannel` mapped over `(t) =>` for the channel
type, which shadowed the translator; the loop variable is now `channelType`.
The error boundary in main.tsx is a class component, so it reads the locale
once per render via `translate` rather than the hook — correct here anyway,
since an error screen has no reason to re-render on a language switch.
Two GroupDmSettings tests located the name input by its aria-label, which
this change translates. They now use the `data-group-dm-name-input`
attribute the input already carries, matching how the rest of that file
locates elements — and no longer break when copy changes.
Dictionary is at 595 keys, en and pt-BR at exact parity.
Verified: web build (tsc + vite), 490 web tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -492,6 +492,179 @@ export const en = {
|
||||
'explore.request.send': 'Send Request',
|
||||
'explore.request.sending': 'Sending...',
|
||||
'explore.request.cancel': 'Cancel',
|
||||
// Settings — voice: video
|
||||
'settings.video.title': 'Video',
|
||||
'settings.video.checking': 'Checking camera access…',
|
||||
'settings.video.needPermission': 'Grant camera permission to choose a camera.',
|
||||
'settings.video.promptNeeded': 'Camera permission needed to choose a camera and preview.',
|
||||
'settings.video.enablePreview': 'Enable camera preview',
|
||||
'settings.video.tryAgain': 'Try again',
|
||||
'settings.video.testCamera': 'Test camera',
|
||||
'settings.video.stopPreview': 'Stop preview',
|
||||
'settings.video.auto': 'Auto (system default)',
|
||||
'settings.video.currentlyUsing': 'Currently using: {label}',
|
||||
'settings.video.detectedCamera': 'detected camera',
|
||||
'settings.video.noCameras': 'No cameras detected.',
|
||||
'settings.video.error.inUse': 'Camera is in use by another application.',
|
||||
'settings.video.error.unavailable': 'Selected camera is unavailable.',
|
||||
'settings.video.error.notFound': 'No camera detected.',
|
||||
'settings.video.error.generic': 'Could not start camera preview.',
|
||||
'settings.video.blocked.mac': 'Grant camera access in System Settings → Privacy & Security → Camera, then restart the app.',
|
||||
'settings.video.blocked.windows': 'Grant camera access in Settings → Privacy & Security → Camera, then restart the app.',
|
||||
'settings.video.blocked.browser': 'Camera permission was denied. Reset it in your browser/Chromium prompt and try again.',
|
||||
|
||||
// Create channel / category / space
|
||||
'create.cancel': 'Cancel',
|
||||
'create.creating': 'Creating...',
|
||||
'create.channel.title': 'Create Channel',
|
||||
'create.channel.submit': 'Create Channel',
|
||||
'create.channel.type': 'Channel Type',
|
||||
'create.channel.type.text': 'Text',
|
||||
'create.channel.type.voice': 'Voice',
|
||||
'create.channel.type.text.description': 'Send messages, images, and files',
|
||||
'create.channel.type.voice.description': 'Hang out with voice and video',
|
||||
'create.channel.name': 'Channel Name',
|
||||
'create.channel.name.placeholder': 'new-channel',
|
||||
'create.channel.name.required': 'Channel name is required',
|
||||
'create.channel.topic': 'Topic (optional)',
|
||||
'create.channel.topic.placeholder': "What's this channel about?",
|
||||
'create.channel.category': 'Category',
|
||||
'create.channel.category.none': 'No Category',
|
||||
'create.channel.failed': 'Failed to create channel',
|
||||
'create.category.title': 'Create Category',
|
||||
'create.category.submit': 'Create Category',
|
||||
'create.category.name': 'Category Name',
|
||||
'create.category.name.placeholder': 'new-category',
|
||||
'create.category.name.required': 'Category name is required',
|
||||
'create.category.failed': 'Failed to create category',
|
||||
'create.noSpace': 'No space selected',
|
||||
'create.space.title': 'Create a Space',
|
||||
'create.space.submit': 'Create',
|
||||
'create.space.upload': 'Upload',
|
||||
'create.space.remove': 'Remove',
|
||||
'create.space.iconColor': 'Icon Color',
|
||||
'create.space.name': 'Space Name',
|
||||
'create.space.name.placeholder': 'My Awesome Space',
|
||||
'create.space.name.required': 'Space name is required',
|
||||
'create.space.description': 'Description',
|
||||
'create.space.description.placeholder': 'A short description for your space...',
|
||||
'create.space.visibility': 'Visibility',
|
||||
'create.space.visibility.private': 'Private',
|
||||
'create.space.visibility.private.description': 'Only people with an invite link can join',
|
||||
'create.space.visibility.request': 'Request to Join',
|
||||
'create.space.visibility.request.description': 'Visible in Explore — people can request to join',
|
||||
'create.space.visibility.public': 'Public',
|
||||
'create.space.visibility.public.description': 'Visible in Explore — anyone can join instantly',
|
||||
'create.space.iconFailed': 'Failed to upload icon',
|
||||
'create.space.failed': 'Failed to create space',
|
||||
'create.space.cropIcon': 'Crop Space Icon',
|
||||
|
||||
// Group DMs
|
||||
'groupDm.newDm.title': 'New Direct Message',
|
||||
'groupDm.newDm.search': 'Search for a user...',
|
||||
'groupDm.newDm.failed': 'Failed to create DM',
|
||||
'groupDm.add.title': 'Add Friends to DM',
|
||||
'groupDm.add.search': 'Search friends...',
|
||||
'groupDm.add.alreadyIn': 'Already in this DM',
|
||||
'groupDm.add.noMatch': 'No friends match your search',
|
||||
'groupDm.add.noFriends': 'No friends yet',
|
||||
'groupDm.add.select': 'Select Friends',
|
||||
'groupDm.add.submit': 'Add {count} Friend',
|
||||
'groupDm.add.submitPlural': 'Add {count} Friends',
|
||||
'groupDm.add.adding': 'Adding...',
|
||||
'groupDm.add.failed': 'Failed to add members',
|
||||
'groupDm.fallbackName': 'Group DM',
|
||||
'groupDm.icon.change': 'Change group icon',
|
||||
'groupDm.icon.label': 'Group icon',
|
||||
'groupDm.icon.crop': 'Crop Group Icon',
|
||||
'groupDm.name': 'Group Name',
|
||||
'groupDm.tab.general': 'General',
|
||||
'groupDm.tab.overview': 'Overview',
|
||||
'groupDm.tab.members': 'Members',
|
||||
'groupDm.back': 'Back to group DM settings menu',
|
||||
'groupDm.save': 'Save',
|
||||
'groupDm.saving': 'Saving...',
|
||||
'groupDm.cancel': 'Cancel',
|
||||
'groupDm.close': 'Close',
|
||||
'groupDm.saveFailed': 'Failed to save settings',
|
||||
'groupDm.members.title': 'Members',
|
||||
'groupDm.members.add': 'Add Member',
|
||||
'groupDm.members.full': 'Group is full',
|
||||
'groupDm.leave': 'Leave Group',
|
||||
'groupDm.leaving': 'Leaving...',
|
||||
'groupDm.leave.description': 'You will stop receiving messages from this conversation. Other members will see a system message.',
|
||||
'groupDm.leave.confirm': 'Leave "{name}"? You will stop receiving messages from this conversation.',
|
||||
'groupDm.leave.button': 'Leave',
|
||||
'groupDm.leave.failed': 'Failed to leave group',
|
||||
|
||||
// Join a space
|
||||
'join.title': 'Join a Space',
|
||||
'join.discover.lead': 'Discover spaces to join, or browse them all in Explore.',
|
||||
'join.discover.error': 'Couldn’t load spaces to discover right now. You can still join with an invite code below.',
|
||||
'join.discover.empty': 'No spaces to discover yet — try an invite code below, or check back later.',
|
||||
'join.discover.browse': 'Browse all in Explore',
|
||||
'join.haveCode': 'Have an invite code?',
|
||||
'join.code.placeholder': 'e.g. abc123 or https://instance.com/join/abc123',
|
||||
'join.cancel': 'Cancel',
|
||||
'join.back': 'Back',
|
||||
'join.submit': 'Join Space',
|
||||
'join.joining': 'Joining...',
|
||||
'join.failed': 'Failed to join space',
|
||||
'join.connect.lead': 'Connect to {host} to join this space.',
|
||||
'join.connect.password': 'Enter your password to connect',
|
||||
'join.connect.password.placeholder': 'Your account password',
|
||||
'join.connect.note': 'Your password is verified locally, then used to create or access your account on the remote instance.',
|
||||
'join.connect.submit': 'Connect & Join',
|
||||
'join.connect.connecting': 'Connecting...',
|
||||
'join.fallback.lead': 'An account already exists on {host} with a different password. Enter the credentials you used on that instance.',
|
||||
'join.fallback.username': 'Username',
|
||||
'join.fallback.username.placeholder': 'Your username on this instance',
|
||||
'join.fallback.password': 'Password for this instance',
|
||||
'join.fallback.password.placeholder': 'Password on the remote instance',
|
||||
'join.fallback.submit': 'Login & Join',
|
||||
'join.fallback.loggingIn': 'Logging in...',
|
||||
|
||||
// Delete account / transfer ownership / error screen
|
||||
'danger.delete.title': 'Delete Account',
|
||||
'danger.delete.lead': 'This will permanently delete your account.',
|
||||
'danger.delete.item.spaces': 'All space memberships will be removed',
|
||||
'danger.delete.item.friends': 'All friend connections will be removed',
|
||||
'danger.delete.item.dms': 'All DM memberships will be removed',
|
||||
'danger.delete.item.messages': 'Your messages will remain but be attributed to "Deleted User"',
|
||||
'danger.delete.owned': 'You own {count} space. Handle it before continuing:',
|
||||
'danger.delete.ownedPlural': 'You own {count} spaces. Handle each before continuing:',
|
||||
'danger.delete.transfer': 'Transfer',
|
||||
'danger.delete.deleteSpace': 'Delete Space',
|
||||
'danger.delete.selectOwner': 'Select new owner...',
|
||||
'danger.delete.continue': 'Continue',
|
||||
'danger.delete.processing': 'Processing...',
|
||||
'danger.delete.spacesFailed': 'Failed to process spaces',
|
||||
'danger.delete.permanent': 'This action is permanent and cannot be undone.',
|
||||
'danger.delete.password': 'Password',
|
||||
'danger.delete.password.placeholder': 'Enter your password',
|
||||
'danger.delete.back': 'Back',
|
||||
'danger.delete.confirm': 'Delete My Account',
|
||||
'danger.delete.deleting': 'Deleting...',
|
||||
'danger.delete.usernameMismatch': 'Username does not match',
|
||||
'danger.delete.failed': 'Failed to delete account',
|
||||
'danger.delete.federating': 'Removing your account from connected instances...',
|
||||
'danger.delete.now': 'Delete Account Now',
|
||||
'danger.delete.done': 'Account deleted',
|
||||
'danger.delete.redirecting': 'Redirecting to login...',
|
||||
'danger.transfer.title': 'Transfer Ownership',
|
||||
'danger.transfer.lead': 'Choose a member to become the new owner of {space}',
|
||||
'danger.transfer.confirm': 'Transfer ownership of {space} to {member}?',
|
||||
'danger.transfer.note': 'You will become a regular member.',
|
||||
'danger.transfer.search': 'Search members...',
|
||||
'danger.transfer.loading': 'Loading members...',
|
||||
'danger.transfer.empty': 'No members found',
|
||||
'danger.transfer.cancel': 'Cancel',
|
||||
'danger.transfer.submit': 'Transfer',
|
||||
'danger.transfer.transferring': 'Transferring...',
|
||||
'danger.transfer.failed': 'Failed to transfer ownership',
|
||||
'error.title': 'Something went wrong',
|
||||
'error.tryAgain': 'Try Again',
|
||||
'error.reload': 'Reload Page',
|
||||
} as const;
|
||||
|
||||
export type TranslationKey = keyof typeof en;
|
||||
|
||||
Reference in New Issue
Block a user