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
Three complaints, three causes. Decorations sat off-centre. <Avatar>'s box is `size + 2 × ringWidth`, but the overlay was positioned with a percentage inset of that box — so it drifted by exactly the ring whenever one was present. That is why the crown floated above the head on the profile card while looking fine on the editor tiles, which have no ring. DecoratedAvatar now takes `size` and `ringWidth` and computes the inset in pixels, so the picture's edge always lands at a known radius in the SVG viewBox and every decoration is drawn against it. The ugly ones were ugly. Flames were a blob with a black circle punched out of it; they are now fourteen tongues of alternating height on a gradient. The crown's band sits ON the picture's top edge instead of hovering above it. Frost, stars and hearts were placed by eye and are now on a computed ring. Nine more: holo disc (a conic gradient — the honest way to draw one, and CSS has it where SVG does not), chain, cyber brackets, lightning, spikes, butterflies, sakura, bubbles, flower. Twenty in total. Name effects were thin. Gradient was a two-stop ramp; it now has a lit middle. Neon was one shadow, now five layered with a buzz. Glitch was a keyframed text-shadow, now a real RGB split duplicated through ::before/::after from data-text. Added chrome and gold (banded metal with a specular line — the y2k look the reference is after), holo, rainbow, ice, 3D extrude, and outline. Sixteen effects, thirteen fonts with Audiowide, Bungee and Monoton added. The editor tiles now sample the user's own name rather than "Gg" or the effect label: an effect reads differently on a short word than on the name it will carry. Two per row instead of three, and section rows gained an icon. One test failed on the catalog growth and was right to: it used 'rainbow' as an example of an unknown id, and rainbow is now a real effect. It uses an absurd id instead, and a new test walks every catalog id through the normaliser so a catalog and the normaliser can never drift apart silently. Verified: shared build, web build, server typecheck, 518 web tests (1 new). Not verified: pixels — no browser here; the geometry is now computed rather than guessed, but a screenshot decides. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1182 lines
56 KiB
TypeScript
1182 lines
56 KiB
TypeScript
/**
|
||
* Source dictionary. Every key the app can translate is declared here, and its
|
||
* type is derived from this object — a typo or a missing key fails typecheck
|
||
* rather than silently rendering the raw key at runtime.
|
||
*
|
||
* Keys are flat and dot-namespaced by system (`settings.voice.*`), so a
|
||
* translation pass can take one system at a time.
|
||
*/
|
||
export const en = {
|
||
// Settings — navigation
|
||
'settings.tab.account': 'Account',
|
||
'settings.tab.voice': 'Voice & Video',
|
||
'settings.tab.privacy': 'Privacy',
|
||
'settings.tab.connections': 'Connections',
|
||
'settings.tab.keybinds': 'Keybinds',
|
||
'settings.tab.desktop': 'Desktop',
|
||
'settings.tab.instance': 'Instance',
|
||
'settings.tab.language': 'Language',
|
||
|
||
// Settings — language
|
||
'settings.language.title': 'Language',
|
||
'settings.language.description': 'Choose the language for the interface. Anything not yet translated stays in English.',
|
||
'settings.language.en': 'English',
|
||
'settings.language.ptBR': 'Portuguese (Brazil)',
|
||
|
||
// Settings — voice: input
|
||
'settings.voice.input.title': 'Input Device',
|
||
'settings.voice.input.volume': 'Input Volume',
|
||
'settings.voice.micTest.start': "Let's Check",
|
||
'settings.voice.micTest.stop': 'Stop Testing',
|
||
'settings.voice.micTest.playing': 'Playing your mic back to you — say something.',
|
||
'settings.voice.micTest.inCall': 'The level meter is live while you are in a call.',
|
||
'settings.voice.micTest.idle': 'Test your mic without joining a call.',
|
||
'settings.voice.micTest.failed': 'Could not open the microphone. Check the device and its permission.',
|
||
|
||
// Soundboard
|
||
'soundboard.title': 'Soundboard',
|
||
'soundboard.empty': 'No sounds yet.',
|
||
'soundboard.add': 'Add sound',
|
||
'soundboard.adding': 'Uploading...',
|
||
'soundboard.remove': 'Remove',
|
||
'soundboard.namePrompt': 'Name for this sound',
|
||
'soundboard.confirm': 'Add',
|
||
'soundboard.cancel': 'Cancel',
|
||
'soundboard.joinFirst': 'Join a voice channel to use the soundboard.',
|
||
'soundboard.tooLarge': 'Sound must be under 2 MB and a few seconds long.',
|
||
'soundboard.uploadFailed': 'Could not upload that file. Try a different one.',
|
||
|
||
// Account menu (own name in the user bar)
|
||
'accountMenu.editProfile': 'Edit Profile',
|
||
'accountMenu.status': 'Status',
|
||
'accountMenu.status.online': 'Online',
|
||
'accountMenu.status.idle': 'Idle',
|
||
'accountMenu.status.dnd': 'Do Not Disturb',
|
||
'accountMenu.status.offline': 'Invisible',
|
||
'accountMenu.copyId': 'Copy User ID',
|
||
'accountMenu.copied': 'Copied',
|
||
|
||
// In-app notifications
|
||
'notify.attachment': 'Sent an attachment',
|
||
'notify.jump': 'Open',
|
||
|
||
// Desktop update
|
||
'update.ready': 'Update ready',
|
||
'update.readyVersion': 'Version {version} is ready to install.',
|
||
'update.restart': 'Restart to update',
|
||
'update.later': 'Later',
|
||
|
||
// Custom emojis and stickers
|
||
'expressions.title': 'Emojis & Stickers',
|
||
'expressions.emojis': 'Emojis',
|
||
'expressions.stickers': 'Stickers',
|
||
'expressions.addEmoji': 'Add emoji',
|
||
'expressions.addSticker': 'Add sticker',
|
||
'expressions.emptyEmojis': 'No custom emojis yet.',
|
||
'expressions.emptyStickers': 'No stickers yet.',
|
||
'expressions.namePrompt': 'Name',
|
||
'expressions.nameHintEmoji': 'Letters, numbers and underscore. Used as :name: in messages.',
|
||
'expressions.confirm': 'Add',
|
||
'expressions.cancel': 'Cancel',
|
||
'expressions.remove': 'Remove',
|
||
'expressions.tooLarge': 'Image must be under 512 KB.',
|
||
'expressions.uploadFailed': 'Could not add that image. Try another one.',
|
||
'expressions.nameTaken': 'That name is already in use in this server.',
|
||
'expressions.uploading': 'Uploading…',
|
||
'expressions.pickerTitle': 'Stickers',
|
||
'expressions.pickerEmpty': 'This server has no stickers yet.',
|
||
|
||
// Search
|
||
'search.placeholder': 'Search messages…',
|
||
'search.filters': 'Filters',
|
||
'search.from': 'From',
|
||
'search.fromPlaceholder': 'username',
|
||
'search.has': 'Has',
|
||
'search.hasAny': 'Any',
|
||
'search.hasFile': 'File',
|
||
'search.hasImage': 'Image',
|
||
'search.hasLink': 'Link',
|
||
'search.before': 'Before',
|
||
'search.after': 'After',
|
||
'search.hint': 'Tip: type from:name, has:image, before:2026-01-31 straight into the search box.',
|
||
|
||
// Pinned messages
|
||
'pins.pin': 'Pin Message',
|
||
'pins.unpin': 'Unpin Message',
|
||
'pins.title': 'Pinned Messages',
|
||
'pins.empty': 'No pinned messages in this channel yet.',
|
||
'pins.limit': 'This channel has reached the pin limit.',
|
||
|
||
// Sidebar — spaces, channels and members
|
||
'sidebar.friends': 'Friends',
|
||
'sidebar.directMessages': 'Direct Messages',
|
||
'sidebar.noChannels': 'No channels',
|
||
'sidebar.comingSoon': 'Coming Soon',
|
||
'sidebar.loadingChannels': 'Loading channels',
|
||
'sidebar.loadingMembers': 'Loading members',
|
||
'sidebar.groupOnline': 'ONLINE',
|
||
'sidebar.groupOwner': 'OWNER',
|
||
'sidebar.createChannel': 'Create Channel',
|
||
'sidebar.createCategory': 'Create Category',
|
||
'sidebar.categorySettings': 'Category Settings',
|
||
'sidebar.deleteCategory': 'Delete Category',
|
||
'sidebar.invitePeople': 'Invite People',
|
||
'sidebar.spaceSettings': 'Space Settings',
|
||
'sidebar.leaveGroup': 'Leave Group',
|
||
'sidebar.voiceSettings': 'Voice Settings',
|
||
'sidebar.inputDevice': 'Input Device',
|
||
'sidebar.inputVolume': 'Input Volume',
|
||
'sidebar.outputDevice': 'Output Device',
|
||
'sidebar.outputVolume': 'Output Volume',
|
||
|
||
// Chat — composer and messages
|
||
'chat.composer.placeholder': 'Message {channel}',
|
||
'chat.composer.attach': 'Attach file',
|
||
'chat.composer.emoji': 'Emoji',
|
||
'chat.composer.gif': 'GIF',
|
||
'chat.composer.gifPicker': 'GIF picker',
|
||
'chat.composer.emojiPicker': 'Emoji picker',
|
||
'chat.composer.send': 'Send',
|
||
'chat.composer.sendMessage': 'Send message',
|
||
'chat.composer.uploading': 'Uploading',
|
||
'chat.composer.uploadingEllipsis': 'Uploading…',
|
||
'chat.composer.removeAttachment': 'Remove attachment',
|
||
'chat.composer.failedAttachment': 'Remove or retry the failed attachment to send',
|
||
'chat.composer.replyingTo': 'Replying to',
|
||
'chat.composer.cancelReply': 'Cancel reply',
|
||
'chat.message.addReaction': 'Add reaction',
|
||
'chat.message.edit': 'Edit',
|
||
'chat.message.reply': 'Reply',
|
||
|
||
// Statistics
|
||
'stats.title': 'Statistics',
|
||
'stats.range.7': 'Last 7 days',
|
||
'stats.range.30': 'Last 30 days',
|
||
'stats.range.365': 'Last year',
|
||
'stats.voice.title': 'Time in voice',
|
||
'stats.messages.title': 'Messages sent',
|
||
'stats.empty': 'Nothing recorded in this period yet.',
|
||
'stats.total.voice': '{value} total',
|
||
'stats.total.messages': '{value} messages in total',
|
||
'stats.hours': '{hours}h {minutes}m',
|
||
'stats.minutes': '{minutes}m',
|
||
'stats.note': 'Counting started when this feature was installed — earlier activity is not included.',
|
||
|
||
// Audit log
|
||
'audit.title': 'Audit Log',
|
||
'audit.empty': 'Nothing recorded yet. Changes to the server show up here.',
|
||
'audit.loadMore': 'Load more',
|
||
'audit.unknownActor': 'Deleted account',
|
||
'audit.action.space.update': '{actor} updated the server settings',
|
||
'audit.action.space.transfer_ownership': '{actor} transferred ownership of the server',
|
||
'audit.action.channel.create': '{actor} created the channel {name}',
|
||
'audit.action.channel.update': '{actor} updated the channel {name}',
|
||
'audit.action.channel.delete': '{actor} deleted the channel {name}',
|
||
'audit.action.member.kick': '{actor} removed a member',
|
||
'audit.action.member.leave': '{actor} left the server',
|
||
'audit.action.member.ban': '{actor} banned a member',
|
||
'audit.action.member.unban': '{actor} unbanned a member',
|
||
'audit.action.role.create': '{actor} created the role {name}',
|
||
'audit.action.role.update': '{actor} updated the role {name}',
|
||
'audit.action.role.delete': '{actor} deleted a role',
|
||
'audit.action.invite.create': '{actor} created an invite',
|
||
'audit.action.message.delete': '{actor} deleted a message',
|
||
'audit.action.unknown': '{actor} performed an action',
|
||
|
||
// GIF picker
|
||
'gif.search': 'Search GIFs',
|
||
'gif.tab.favorites': 'Favorites',
|
||
'gif.tab.trending': 'Trending',
|
||
'gif.empty.search': 'No GIFs found',
|
||
'gif.empty.trending': 'No trending GIFs',
|
||
'gif.empty.favorites': 'No favorites yet — tap the star on any GIF.',
|
||
'gif.favorite.add': 'Add to favorites',
|
||
'gif.favorite.remove': 'Remove from favorites',
|
||
'gif.category.hello': 'hello',
|
||
'gif.category.lol': 'lol',
|
||
'gif.category.love': 'love',
|
||
'gif.category.birthday': 'happy birthday',
|
||
'gif.category.dance': 'dance',
|
||
'gif.category.facepalm': 'facepalm',
|
||
|
||
// Settings — privacy
|
||
'privacy.title': 'Privacy',
|
||
'privacy.discoverable.label': 'Allow others to find my profile',
|
||
'privacy.discoverable.description': 'When enabled, your profile appears in Discover People. Others can always add you by exact username.',
|
||
'privacy.activity.label': 'Share Activity Status',
|
||
'privacy.activity.description': "Allow others to see what you're up to, like games you're playing or music you're listening to.",
|
||
|
||
// Settings — connections
|
||
'connections.title': 'Connections',
|
||
'connections.spotify.description': 'Show what you are listening to on your profile.',
|
||
'connections.spotify.connect': 'Connect Spotify',
|
||
'connections.spotify.disconnect': 'Disconnect',
|
||
'connections.spotify.connected': 'Connected',
|
||
'connections.spotify.notConfigured': 'This instance has no Spotify credentials configured.',
|
||
'connections.spotify.hint': 'Only what is playing is read — playback cannot be controlled.',
|
||
'connections.spotify.error.denied': 'Authorisation was cancelled on Spotify.',
|
||
'connections.spotify.error.invalid_state': 'The authorisation link expired. Try again.',
|
||
'connections.spotify.error.exchange_failed': 'Spotify refused the authorisation. Try again.',
|
||
|
||
// Profile card
|
||
'profile.aboutMe': 'About Me',
|
||
'profile.memberSince': 'Member Since',
|
||
'profile.sendMessage': 'Send Message',
|
||
'profile.activity.playing': 'Playing',
|
||
'profile.activity.listening': 'Listening to',
|
||
'profile.activity.watching': 'Watching',
|
||
'profile.activity.streaming': 'Streaming',
|
||
'profile.activity.elapsed': '{time} elapsed',
|
||
// Channel settings
|
||
'channel.settings.title': 'Channel Settings',
|
||
'channel.tab.overview': 'Overview',
|
||
'channel.tab.permissions': 'Permissions',
|
||
'channel.name.label': 'Channel Name',
|
||
'channel.name.placeholder': 'new-channel',
|
||
'channel.name.willBecome': 'Will be saved as {name}',
|
||
'channel.name.empty': 'The name cannot be empty.',
|
||
'channel.topic.label': 'Topic',
|
||
'channel.topic.placeholder': "What's this channel about?",
|
||
'channel.save': 'Save Changes',
|
||
'channel.saving': 'Saving...',
|
||
'channel.saved': 'Saved',
|
||
'channel.discard': 'Discard',
|
||
'channel.saveFailed': 'Could not save the channel.',
|
||
'channel.private.title': 'Private Channel',
|
||
'channel.private.description': 'Only selected members and roles will be able to view this channel.',
|
||
'channel.private.note': 'This channel is hidden from members without explicit access. Users with the Administrator permission or space owners can always see all channels.',
|
||
'channel.private.loadFailed': 'Failed to load channel overrides',
|
||
'channel.private.updateFailed': 'Failed to update channel privacy',
|
||
'channel.danger': 'Danger Zone',
|
||
'channel.delete': 'Delete Channel',
|
||
'channel.delete.title': 'Delete #{name}?',
|
||
'channel.delete.body': 'This will permanently delete #{name} and all of its messages.',
|
||
'channel.delete.voiceNote': 'Any users currently in this voice channel will be disconnected.',
|
||
'channel.delete.irreversible': 'This action cannot be undone.',
|
||
'channel.delete.failed': 'Failed to delete channel',
|
||
|
||
// Friends — tabs and lists
|
||
'friends.title': 'Friends',
|
||
'friends.tab.online': 'Online',
|
||
'friends.tab.all': 'All',
|
||
'friends.tab.pending': 'Pending',
|
||
'friends.tab.add': 'Add Friend',
|
||
'friends.tab.activity': 'Activity',
|
||
'friends.status.online': 'Online',
|
||
'friends.status.idle': 'Idle',
|
||
'friends.status.dnd': 'Do Not Disturb',
|
||
'friends.status.offline': 'Offline',
|
||
'friends.heading.online': 'Online — {count}',
|
||
'friends.heading.all': 'All Friends — {count}',
|
||
'friends.heading.pending': 'Pending — {count}',
|
||
'friends.heading.active': 'Active — {count}',
|
||
'friends.heading.offline': 'Offline — {count}',
|
||
'friends.empty.online': "No one's online right now.",
|
||
'friends.empty.all': 'No friends yet — add someone!',
|
||
'friends.empty.pending': 'No pending requests — Nori is napping.',
|
||
'friends.empty.activity': "It's quiet for now... When friends start an activity, we'll show it here!",
|
||
'friends.request.incoming': 'Incoming Friend Request',
|
||
'friends.request.outgoing': 'Outgoing Friend Request',
|
||
'friends.action.message': 'Message',
|
||
'friends.action.accept': 'Accept',
|
||
'friends.action.decline': 'Decline',
|
||
'friends.action.cancel': 'Cancel Request',
|
||
'friends.action.remove': 'Remove Friend',
|
||
'friends.remove.title': 'Remove Friend',
|
||
'friends.remove.body': 'Are you sure you want to remove {name} as a friend? You can always send them a new friend request later.',
|
||
'friends.remove.someone': 'this user',
|
||
'friends.remove.confirm': 'Remove',
|
||
|
||
// Friends — add tab
|
||
'friends.add.title': 'Find People',
|
||
'friends.add.hint': 'Search by username or use {handle} to add someone directly.',
|
||
'friends.add.searchPlaceholder': 'Search or add by username...',
|
||
'friends.add.directTo': 'Send friend request to',
|
||
'friends.add.send': 'Send Request',
|
||
'friends.add.sending': 'Sending...',
|
||
'friends.add.sent': 'Friend request sent!',
|
||
'friends.add.discover': 'Discover People',
|
||
'friends.add.empty.search': 'No users match your search.',
|
||
'friends.add.empty.discover': 'No discoverable users yet — invite people to join!',
|
||
'friends.add.sendRequest': 'Send Friend Request',
|
||
'friends.add.cancelling': 'Cancelling...',
|
||
'friends.add.pending': 'Request Pending',
|
||
'friends.add.failed': 'Failed to send request',
|
||
'friends.accept.failed': 'Failed to accept request',
|
||
'friends.decline.failed': 'Failed to decline request',
|
||
'friends.cancel.failed': 'Failed to cancel request',
|
||
|
||
// DM roster — group members
|
||
'dm.remove.title': 'Remove from Group',
|
||
'dm.remove.body': "Remove {name} from this group? They won't be able to see new messages.",
|
||
'dm.remove.confirm': 'Remove',
|
||
'dm.remove.failed': 'Failed to remove member',
|
||
'dm.transfer.title': 'Transfer Ownership',
|
||
'dm.transfer.body': "Transfer ownership to {name}? You'll lose owner privileges.",
|
||
'dm.transfer.confirm': 'Transfer',
|
||
'dm.transfer.failed': 'Failed to transfer ownership',
|
||
'dm.removeFriend.failed': 'Failed to remove friend',
|
||
|
||
// Invite modal
|
||
'invite.title': 'Invite Friends',
|
||
'invite.subtitle': 'Send to friends, or share a link.',
|
||
'invite.requestOnly': 'This space uses join requests — people join by requesting approval from a manager, so it has no invite link to share.',
|
||
'invite.gotIt': 'Got it',
|
||
'invite.searchPlaceholder': 'Search friends...',
|
||
'invite.remove': 'Remove {name}',
|
||
'invite.alreadyInSpace': 'Already in space',
|
||
'invite.noMatch': 'No friends match your search',
|
||
'invite.noFriends': 'No friends yet',
|
||
'invite.select': 'Select Friends',
|
||
'invite.send': 'Send {count} Invite',
|
||
'invite.sendPlural': 'Send {count} Invites',
|
||
'invite.sending': 'Sending...',
|
||
'invite.sent': 'Sent',
|
||
'invite.retryFailed': 'Retry failed',
|
||
'invite.done': 'Done',
|
||
'invite.orLink': 'Or share a link',
|
||
'invite.generating': 'Generating...',
|
||
'invite.copy': 'Copy',
|
||
'invite.copied': 'Copied!',
|
||
'invite.linkFailed': 'Failed to generate invite link',
|
||
'invite.fail.invite_invalid': 'Invite link no longer valid',
|
||
'invite.fail.not_a_friend': 'Not a friend',
|
||
'invite.fail.user_not_found': 'User not found',
|
||
'invite.fail.already_member': 'Already a member',
|
||
'invite.fail.upstream': "Couldn't verify invite — try again",
|
||
'invite.fail.cannot_invite_self': 'Cannot invite yourself',
|
||
'invite.fail.invalid_body': 'Invalid request',
|
||
'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 Resenhacord 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': 'Resenhacord 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': 'Resenhacord 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',
|
||
// 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',
|
||
// Permission labels — shared by the roles panel and the channel/category
|
||
// override editors, so a rename lands in all three at once.
|
||
'perm.group.general': 'General',
|
||
'perm.group.text': 'Text',
|
||
'perm.group.voice': 'Voice',
|
||
'perm.administrator': 'Administrator',
|
||
'perm.viewChannel': 'View Channel',
|
||
'perm.viewChannels': 'View Channels',
|
||
'perm.manageChannels': 'Manage Channels',
|
||
'perm.manageRoles': 'Manage Roles',
|
||
'perm.manageSpace': 'Manage Space',
|
||
'perm.createInvite': 'Create Invite',
|
||
'perm.kickMembers': 'Kick Members',
|
||
'perm.banMembers': 'Ban Members',
|
||
'perm.sendMessages': 'Send Messages',
|
||
'perm.manageMessages': 'Manage Messages',
|
||
'perm.attachFiles': 'Attach Files',
|
||
'perm.readHistory': 'Read Message History',
|
||
'perm.mentionEveryone': 'Mention @everyone',
|
||
'perm.addReactions': 'Add Reactions',
|
||
'perm.connect': 'Connect',
|
||
'perm.speak': 'Speak',
|
||
'perm.stream': 'Stream',
|
||
'perm.muteMembers': 'Mute Members',
|
||
'perm.deafenMembers': 'Deafen Members',
|
||
'perm.moveMembers': 'Move Members',
|
||
'perm.disconnectMembers': 'Disconnect Members',
|
||
|
||
// Space settings — overview
|
||
'space.overview.title': 'Overview',
|
||
'space.overview.identity': 'Space Identity',
|
||
'space.overview.icon': 'Space Icon',
|
||
'space.overview.iconAlt': 'Space icon',
|
||
'space.overview.iconColor': 'Icon Color',
|
||
'space.overview.banner': 'Space Banner',
|
||
'space.overview.bannerAlt': 'Space banner',
|
||
'space.overview.bannerUpload': 'Upload banner (16:9)',
|
||
'space.overview.bannerRemove': 'Remove banner',
|
||
'space.overview.remove': 'Remove',
|
||
'space.overview.name': 'Space Name',
|
||
'space.overview.save': 'Save',
|
||
'space.overview.saving': 'Saving...',
|
||
'space.overview.discard': 'Discard',
|
||
'space.overview.saved': 'Settings saved',
|
||
'space.overview.saveFailed': 'Failed to save',
|
||
'space.overview.iconFailed': 'Failed to upload icon',
|
||
'space.overview.bannerFailed': 'Failed to upload banner',
|
||
'space.overview.cropIcon': 'Crop Space Icon',
|
||
'space.overview.cropBanner': 'Crop Space Banner',
|
||
'space.overview.danger': 'Danger Zone',
|
||
'space.overview.transfer.description': 'Transfer ownership to another member. You will become a regular member.',
|
||
'space.overview.transfer.button': 'Transfer Ownership',
|
||
'space.overview.transfer.confirm': 'Transfer ownership to',
|
||
'space.overview.transfer.search': 'Search members...',
|
||
'space.overview.transfer.empty': 'No members found',
|
||
'space.overview.transfer.back': 'Back',
|
||
'space.overview.transfer.cancel': 'Cancel',
|
||
'space.overview.transfer.submit': 'Transfer',
|
||
'space.overview.transfer.transferring': 'Transferring...',
|
||
'space.overview.transfer.failed': 'Failed to transfer ownership',
|
||
'space.overview.delete.description': 'Permanently delete this space and all its data. This cannot be undone.',
|
||
'space.overview.delete': 'Delete Space',
|
||
'space.overview.delete.confirm': 'Click again to confirm deletion',
|
||
'space.overview.delete.failed': 'Failed to delete space',
|
||
|
||
// Space settings — members and bans
|
||
'space.members.title': 'Members',
|
||
'space.members.description': 'Manage members of this space. Click a member to edit their roles.',
|
||
'space.members.count': 'Members ({count})',
|
||
'space.members.noRoles': 'No roles',
|
||
'space.members.save': 'Save',
|
||
'space.members.cancel': 'Cancel',
|
||
'space.members.kick': 'Kick',
|
||
'space.members.ban': 'Ban',
|
||
'space.members.kick.title': 'Kick {name}',
|
||
'space.members.kick.description': 'They can rejoin with an invite link.',
|
||
'space.members.ban.title': 'Ban {name}',
|
||
'space.members.ban.description': 'They will be permanently banned from this space until unbanned.',
|
||
'space.members.rolesFailed': 'Failed to update roles',
|
||
'space.members.kickFailed': 'Failed to kick member',
|
||
'space.members.banFailed': 'Failed to ban member',
|
||
'space.bans.title': 'Bans',
|
||
'space.bans.count': 'Bans ({count})',
|
||
'space.bans.description': 'Banned users cannot rejoin this space until unbanned.',
|
||
'space.bans.loading': 'Loading bans...',
|
||
'space.bans.empty': 'No banned users',
|
||
'space.bans.by': 'Banned by {moderator} on {date}',
|
||
'space.bans.unban': 'Unban',
|
||
'space.bans.loadFailed': 'Failed to load bans',
|
||
'space.bans.unbanFailed': 'Failed to unban user',
|
||
|
||
// Space settings — roles
|
||
'space.roles.title': 'Roles',
|
||
'space.roles.description': 'Roles define what permissions members have. Higher roles take priority.',
|
||
'space.roles.create': 'Create Role',
|
||
'space.roles.creating': 'Creating...',
|
||
'space.roles.createFailed': 'Failed to create role',
|
||
'space.roles.created': 'Role created — customize it below',
|
||
'space.roles.back': 'Back to roles',
|
||
'space.roles.identity': 'Identity',
|
||
'space.roles.name': 'Role Name',
|
||
'space.roles.name.empty': 'Role name cannot be empty',
|
||
'space.roles.color': 'Role Color',
|
||
'space.roles.save': 'Save',
|
||
'space.roles.saving': 'Saving...',
|
||
'space.roles.saved': 'Role saved',
|
||
'space.roles.saveFailed': 'Failed to save role',
|
||
'space.roles.discard': 'Discard',
|
||
'space.roles.copy': 'Copy Role',
|
||
'space.roles.copying': 'Copying...',
|
||
'space.roles.copyFailed': 'Failed to copy role',
|
||
'space.roles.delete': 'Delete Role',
|
||
'space.roles.deleting': 'Deleting...',
|
||
'space.roles.deleteConfirm': 'Confirm?',
|
||
'space.roles.deleteFailed': 'Failed to delete role',
|
||
|
||
// Category settings
|
||
'category.settings.title': 'Category Settings',
|
||
'category.tab.overview': 'Overview',
|
||
'category.tab.permissions': 'Permissions',
|
||
'category.label': 'Category',
|
||
'category.private.title': 'Private Category',
|
||
'category.private.description': 'Only selected members and roles will be able to view channels in this category.',
|
||
'category.private.note': 'This category is hidden from members without explicit access. Channels inside inherit this restriction unless they explicitly override it.',
|
||
'category.private.loadFailed': 'Failed to load category overrides',
|
||
'category.private.updateFailed': 'Failed to update category privacy',
|
||
'category.renameFailed': 'Failed to rename category',
|
||
'category.danger': 'Danger Zone',
|
||
'category.delete': 'Delete Category',
|
||
'category.delete.title': 'Delete "{name}"?',
|
||
'category.delete.body': 'This will permanently delete the {name} category. Channels inside it will be moved to the top level. This action cannot be undone.',
|
||
'category.delete.failed': 'Failed to delete category',
|
||
|
||
// Instance settings — general
|
||
'instance.general.title': 'General',
|
||
'instance.general.description': 'Configure your Resenhacord instance. These settings affect all users.',
|
||
'instance.general.loading': 'Loading settings...',
|
||
'instance.general.name': 'Instance Name',
|
||
'instance.general.name.description': 'The name shown on the login page and to federated instances.',
|
||
'instance.general.discovery': 'Discovery',
|
||
'instance.general.discovery.title': 'Space Discovery',
|
||
'instance.general.discovery.description': 'Allow spaces to appear in the public Explore page',
|
||
'instance.general.gif': 'GIF Search',
|
||
'instance.general.gif.description': 'Enable GIF search powered by Klipy. Get a free API key from the Klipy developer portal.',
|
||
'instance.general.gif.keySaved': 'Key saved — enter new key to replace',
|
||
'instance.general.gif.keyPlaceholder': 'Klipy API key',
|
||
'instance.general.gif.enabled': 'Enabled',
|
||
'instance.general.gif.notConfigured': 'Not configured',
|
||
'instance.general.gif.clearKey': 'Clear key',
|
||
'instance.general.save': 'Save',
|
||
'instance.general.saving': 'Saving...',
|
||
'instance.general.reset': 'Reset',
|
||
'instance.general.saved': 'Settings saved',
|
||
'instance.general.saveFailed': 'Failed to save',
|
||
// System messages — timeline line and sidebar preview
|
||
'system.unknown': 'Unknown',
|
||
'system.someone': 'Someone',
|
||
'system.aMember': 'A member',
|
||
'system.memberAdded': '{actor} added {target} to the group',
|
||
'system.memberLeft': '{target} left the group',
|
||
'system.memberRemoved': '{actor} removed {target} from the group',
|
||
'system.ownerChanged': '{owner} is now the group owner',
|
||
'system.nameChanged': '{actor} renamed the group to "{name}"',
|
||
'system.nameCleared': '{actor} cleared the group name',
|
||
'system.iconChanged': '{actor} updated the group icon',
|
||
'system.preview.inviteTo': 'Sent invite to {space}',
|
||
'system.preview.invite': 'Sent a space invite',
|
||
'system.preview.memberAdded': '{actor} added {target}',
|
||
'system.preview.memberRemoved': '{actor} removed {target}',
|
||
'system.preview.nameChanged': '{actor} renamed the group',
|
||
'system.preview.generic': 'System message',
|
||
// Login, register and the invite landing page
|
||
'auth.username': 'Username',
|
||
'auth.password': 'Password',
|
||
'auth.username.required': 'Username is required',
|
||
'auth.password.required': 'Password is required',
|
||
'auth.login.title': 'Welcome back!',
|
||
'auth.login.subtitle': "We're so excited to see you again!",
|
||
'auth.login.submit': 'Log In',
|
||
'auth.login.loggingIn': 'Logging in...',
|
||
'auth.login.failed': 'Login failed',
|
||
'auth.login.rateLimited': 'Too many login attempts',
|
||
'auth.retryIn': 'Try again in {seconds}s',
|
||
'auth.needAccount': 'Need an account?',
|
||
'auth.register': 'Register',
|
||
'auth.haveAccount': 'Already have an account?',
|
||
'auth.register.title': 'Create an account',
|
||
'auth.register.confirmPassword': 'Confirm Password',
|
||
'auth.register.username.length': 'Username must be between 3 and 32 characters',
|
||
'auth.register.username.charset': 'Username can only contain lowercase letters, numbers, and underscores',
|
||
'auth.register.username.checking': 'Checking availability...',
|
||
'auth.register.username.available': 'Username is available',
|
||
'auth.register.username.taken': 'Username is already taken',
|
||
'auth.register.password.length': 'Password must be at least 6 characters',
|
||
'auth.register.password.mismatch': 'Passwords do not match',
|
||
'auth.register.failed': 'Registration failed',
|
||
'auth.register.rateLimited': 'Too many attempts',
|
||
'auth.register.continue': 'Continue',
|
||
'auth.register.invite': 'Invite code or link',
|
||
'auth.register.invite.checking': 'Checking...',
|
||
'auth.register.invite.validating': 'Validating invite...',
|
||
'auth.register.invite.expired': 'This invite link has expired. Ask the admin for a new one.',
|
||
'auth.register.invite.exhausted': 'This invite has reached its usage limit. Ask the admin to extend it.',
|
||
'auth.register.invite.invalid': 'Invalid invite code.',
|
||
'auth.register.invite.invalidLink': 'Invalid invite link — please request a new one',
|
||
'auth.register.profile.title': 'Make it yours',
|
||
'auth.register.profile.subtitle': 'Personalize your profile, or skip for now',
|
||
'auth.register.displayName': 'Display Name',
|
||
'auth.register.displayName.placeholder': 'Display name',
|
||
'auth.register.avatarColor': 'Avatar Color',
|
||
'auth.register.submit': 'Get Started',
|
||
'auth.register.creating': 'Creating account...',
|
||
'auth.register.back': 'Back',
|
||
'auth.register.skip': 'Skip for now',
|
||
'auth.register.cropAvatar': 'Crop Avatar',
|
||
'join.page.loading': 'Loading invite...',
|
||
'join.page.invalid.title': 'Invalid Invite',
|
||
'join.page.invalid.body': 'This invite link is invalid or has expired.',
|
||
'join.page.invitedTo': "You've been invited to join",
|
||
'join.page.noCode': 'No invite code provided',
|
||
'join.page.invalidCode': 'Invalid invite code',
|
||
'join.page.loadFailed': 'Failed to load invite',
|
||
'join.page.joinFailed': 'Failed to join space',
|
||
'join.page.connectFailed': 'Failed to connect',
|
||
'join.page.loginFailed': 'Failed to log in',
|
||
'join.page.instance.placeholder': 'e.g. my-instance.com',
|
||
'join.page.alreadyMember': "You're already in {space}!",
|
||
'join.page.goTo': 'Go to {space}',
|
||
'join.page.redirecting': 'Redirecting you now...',
|
||
// Voice — controls, connection info and the mobile join sheet
|
||
'voice.title': 'Voice & Video',
|
||
'voice.dmCall': 'DM Call',
|
||
'voice.channel': 'Voice Channel',
|
||
'voice.connected': 'Voice Connected',
|
||
'voice.connecting': 'Connecting...',
|
||
'voice.failed': 'Connection Failed',
|
||
'voice.goToCall': 'Go to call',
|
||
'voice.disconnect': 'Disconnect',
|
||
'voice.camera.on': 'Turn On Camera',
|
||
'voice.camera.off': 'Turn Off Camera',
|
||
'voice.share.start': 'Share Screen',
|
||
'voice.share.stop': 'Stop Sharing',
|
||
'voice.soundboard': 'Soundboard',
|
||
'voice.videoQuality': 'Video Quality',
|
||
'voice.noise.enable': 'Enable AI Noise Suppression',
|
||
'voice.noise.disable': 'Disable AI Noise Suppression',
|
||
'voice.info.title': 'Connection Info',
|
||
'voice.info.notConnected': 'Not connected',
|
||
'voice.info.gathering': 'Gathering stats...',
|
||
'voice.info.network': 'Network',
|
||
'voice.info.ping': 'Ping',
|
||
'voice.info.packetLoss': 'Packet Loss',
|
||
'voice.info.jitter': 'Jitter',
|
||
'voice.info.server': 'Server',
|
||
'voice.info.protocol': 'Protocol',
|
||
'voice.info.audio': 'Audio',
|
||
'voice.info.video': 'Video',
|
||
'voice.info.microphone': 'Microphone',
|
||
'voice.info.camera': 'Camera',
|
||
'voice.info.screen': 'Screen',
|
||
'voice.info.screenAudio': 'Screen Audio',
|
||
'voice.info.unknown': 'Unknown',
|
||
'voice.info.remote': 'Remote',
|
||
'voice.join.empty': 'No one is in this channel yet.',
|
||
'voice.join.beFirst': 'Be the first to join!',
|
||
'voice.join.mute': 'Mute microphone',
|
||
'voice.join.unmute': 'Unmute microphone',
|
||
'voice.join.switch': 'Switch Channel',
|
||
'voice.join.join': 'Join Voice',
|
||
'voice.join.close': 'Close',
|
||
'voice.join.selectedCamera': 'Selected camera',
|
||
'voice.join.enablePreview': 'Enable camera preview',
|
||
'voice.join.checkingCamera': 'Checking camera…',
|
||
'voice.join.tapToPreview': 'Tap to preview camera',
|
||
'voice.join.permissionDenied': 'Camera permission denied.',
|
||
'voice.settings.volume': 'Volume',
|
||
'voice.settings.sfxVolume': 'Sound Effects Volume',
|
||
'voice.settings.messageSound': 'Play sound for every message',
|
||
'voice.settings.messageSound.description': 'Off (default): only DMs and messages that mention you. On: every channel.',
|
||
'voice.settings.processing': 'Voice Processing',
|
||
'voice.settings.noise': 'AI Noise Suppression',
|
||
'voice.settings.noise.description': 'ML-based noise removal (RNNoise) — filters keyboard, fans, and background noise',
|
||
'voice.settings.echo': 'Echo Cancellation',
|
||
'voice.settings.echo.description': 'Cancels echo from your speakers feeding back into the mic. Always on for voice channels and calls.',
|
||
'voice.settings.agc': 'Auto Gain Control',
|
||
'voice.settings.agc.description': 'Auto-adjusts mic volume — can cause voice ducking during streams',
|
||
// Settings navigation (space, user and instance)
|
||
'nav.general': 'General',
|
||
'nav.management': 'Management',
|
||
'nav.overview': 'Overview',
|
||
'nav.discovery': 'Discovery',
|
||
'nav.members': 'Members',
|
||
'nav.roles': 'Roles',
|
||
'nav.bans': 'Bans',
|
||
'nav.userSettings': 'User Settings',
|
||
'nav.appSettings': 'App Settings',
|
||
'nav.administration': 'Administration',
|
||
'nav.instance': 'Instance',
|
||
'nav.registration': 'Registration',
|
||
'nav.federation': 'Federation',
|
||
'nav.streaming': 'Streaming',
|
||
'nav.storage': 'Storage',
|
||
'nav.users': 'Users',
|
||
'nav.back': 'Back to space settings menu',
|
||
'space.discovery.title': 'Discovery',
|
||
'space.discovery.visibility': 'Visibility',
|
||
'space.discovery.visibility.description': 'Control who can discover and join this space.',
|
||
'space.discovery.description': 'Description',
|
||
'space.discovery.description.hint': 'A short summary shown on the Explore page.',
|
||
'space.discovery.description.placeholder': 'A short description for the Explore page...',
|
||
'space.requests.loading': 'Loading...',
|
||
'space.requests.empty': 'No pending join requests',
|
||
'space.requests.accept': 'Accept',
|
||
'space.requests.decline': 'Decline',
|
||
'space.requests.failed': 'Action failed',
|
||
|
||
// Mobile screens
|
||
'mobile.messages': 'Messages',
|
||
'mobile.noConversations': 'No conversations yet.',
|
||
'mobile.loadingChannels': 'Loading channels',
|
||
'mobile.noChannels': 'No channels yet.',
|
||
'mobile.groupInfo': 'Group Info',
|
||
'mobile.info': 'Info',
|
||
'mobile.cancelEdit': 'Cancel edit',
|
||
'mobile.removeGroupIcon': 'Remove group icon',
|
||
'mobile.federatedGroup': 'Federated group',
|
||
'mobile.invitePeople': 'Invite People',
|
||
'mobile.inviteCopied': 'Invite link copied to clipboard',
|
||
'mobile.inviteFailed': 'Failed to generate invite',
|
||
'mobile.createFolder': 'Create Folder',
|
||
'mobile.folderCreated': 'Folder created',
|
||
'mobile.moveToFolder': 'Move to Folder',
|
||
'mobile.movedToFolder': 'Moved to folder',
|
||
'mobile.removeFromFolder': 'Remove from Folder',
|
||
'mobile.removedFromFolder': 'Removed from folder',
|
||
'mobile.folderUngrouped': 'Folder ungrouped',
|
||
'mobile.unnamedFolder': 'Unnamed Folder',
|
||
'mobile.leaveSpace': 'Leave Space',
|
||
'mobile.leave': 'Leave',
|
||
'mobile.leftSpace': 'Left space',
|
||
'mobile.leaveSpaceFailed': 'Failed to leave space',
|
||
'mobile.muteUser': 'Mute User',
|
||
'mobile.channelSettings': 'Channel Settings',
|
||
'mobile.deleteChannel': 'Delete Channel',
|
||
'mobile.channelDeleted': 'Channel deleted',
|
||
'mobile.categorySettings': 'Category Settings',
|
||
'mobile.deleteCategory': 'Delete Category',
|
||
'mobile.categoryDeleted': 'Category deleted',
|
||
'mobile.delete': 'Delete',
|
||
'mobile.createSpace': 'Create Space',
|
||
'mobile.joinSpace': 'Join Space',
|
||
'mobile.exploreSpaces': 'Explore Spaces',
|
||
'mobile.leaveSpace.confirm': "Are you sure you want to leave this space? You'll need a new invite to rejoin.",
|
||
'mobile.deleteChannel.confirm': 'Are you sure? This will permanently delete the channel and all its messages.',
|
||
'mobile.deleteCategory.confirm': 'Are you sure? Channels in this category will be moved to uncategorized.',
|
||
'mobile.deleteChannelFailed': 'Failed to delete channel',
|
||
'mobile.voice.title': 'Voice Call',
|
||
'mobile.voice.collapse': 'Collapse call',
|
||
'mobile.voice.members': 'View members',
|
||
'mobile.voice.unmute': 'Unmute',
|
||
'mobile.voice.deafen': 'Deafen',
|
||
'mobile.voice.undeafen': 'Undeafen',
|
||
'mobile.voice.cameraOn': 'Turn camera on',
|
||
'mobile.voice.cameraOff': 'Turn camera off',
|
||
'mobile.voice.switchCamera': 'Switch camera',
|
||
'mobile.voice.selectCamera': 'Select camera',
|
||
'mobile.voice.shareScreen': 'Share screen',
|
||
'mobile.voice.stopSharing': 'Stop sharing screen',
|
||
'mobile.voice.disconnect': 'Disconnect from call',
|
||
// @everyone mention
|
||
'mention.everyone.label': 'everyone',
|
||
'mention.everyone.description': 'Notify everyone in this channel',
|
||
'mention.everyone.section': 'Special',
|
||
'mention.unknownUser': 'Unknown User',
|
||
'mention.members': 'Members',
|
||
// Pin notice
|
||
'system.pinned': '{actor} pinned {message} to this channel. {seeAll}',
|
||
'system.pinned.message': 'a message',
|
||
'system.pinned.seeAll': 'See all pinned messages.',
|
||
'system.preview.pinned': '{actor} pinned a message',
|
||
'join.page.back': 'Back to Resenhacord',
|
||
// Profile identity — editor, catalogs, badges, widgets
|
||
'settings.tab.profile': 'Profile',
|
||
'profile.tab.board': 'Board',
|
||
'profile.links': 'Links',
|
||
'profile.viewFull': 'View Full Profile',
|
||
'profile.editor.title': 'Profile',
|
||
'profile.editor.subtitle': 'Make it yours. Everything here shows on your profile card, the full profile and the member list.',
|
||
'profile.editor.preview': 'Preview',
|
||
'profile.editor.preview.hint': 'This is exactly what others see when they click your name.',
|
||
'profile.editor.name': 'Display name style',
|
||
'profile.editor.name.hint': 'Font, effect and colours for your name on your profile.',
|
||
'profile.editor.font': 'Font',
|
||
'profile.editor.effect': 'Effect',
|
||
'profile.editor.colors': 'Colours',
|
||
'profile.editor.decoration': 'Avatar decoration',
|
||
'profile.editor.profileEffect': 'Profile effect',
|
||
'profile.editor.nameplate': 'Nameplate',
|
||
'profile.editor.nameplate.hint': 'The background behind your row in the member list.',
|
||
'profile.editor.theme': 'Theme',
|
||
'profile.editor.theme.hint': 'Two colours washed behind your card and used when you have no banner.',
|
||
'profile.editor.theme.none': 'Default',
|
||
'profile.editor.identity': 'Identity',
|
||
'profile.editor.pronouns': 'Pronouns',
|
||
'profile.editor.pronouns.placeholder': 'he/him, she/her, they/them…',
|
||
'profile.editor.tag': 'Tag',
|
||
'profile.editor.tag.hint': '2–4 letters shown in a chip beside your name, with an optional emoji.',
|
||
'profile.editor.links': 'Links',
|
||
'profile.editor.links.hint': 'Up to 5. The icon is picked from the site automatically.',
|
||
'profile.editor.linkLabel': 'Label',
|
||
'profile.editor.addLink': 'Add link',
|
||
'profile.editor.board': 'Board',
|
||
'profile.editor.board.hint': 'Games and a quote, shown on the Board tab of your full profile.',
|
||
'profile.editor.searchGame': 'Search a game on Steam, or type a title and press Enter',
|
||
'profile.editor.searching': 'Searching…',
|
||
'profile.editor.useTyped': 'Use "{name}" as typed',
|
||
'profile.editor.gameNote': 'Why this one? (optional)',
|
||
'profile.editor.gameTags': 'Tags, comma-separated (up to 3)',
|
||
'profile.editor.quote': 'Quote',
|
||
'profile.editor.quote.placeholder': 'A line that says something about you…',
|
||
'profile.editor.quote.author': 'Who said it (optional)',
|
||
'profile.editor.remove': 'Remove',
|
||
'profile.editor.save': 'Save',
|
||
'profile.editor.saving': 'Saving...',
|
||
'profile.editor.saved': 'Profile updated',
|
||
'profile.editor.saveFailed': 'Could not save the profile',
|
||
'profile.editor.discard': 'Discard',
|
||
'profile.editor.resetAll': 'Reset everything',
|
||
'profile.font.default': 'Default',
|
||
'profile.font.display': 'Display',
|
||
'profile.font.comic': 'Comic',
|
||
'profile.font.script': 'Script',
|
||
'profile.font.pixel': 'Pixel',
|
||
'profile.font.future': 'Future',
|
||
'profile.font.serif': 'Serif',
|
||
'profile.font.mono': 'Mono',
|
||
'profile.font.round': 'Round',
|
||
'profile.font.marker': 'Marker',
|
||
'profile.effect.solid': 'Solid',
|
||
'profile.effect.gradient': 'Gradient',
|
||
'profile.effect.neon': 'Neon',
|
||
'profile.effect.toon': 'Toon',
|
||
'profile.effect.pop': 'Pop',
|
||
'profile.effect.gummy': 'Gummy',
|
||
'profile.effect.prism': 'Prism',
|
||
'profile.effect.glitch': 'Glitch',
|
||
'profile.effect.fire': 'Fire',
|
||
'profile.decoration.none': 'None',
|
||
'profile.decoration.ring': 'Ring',
|
||
'profile.decoration.neon': 'Neon',
|
||
'profile.decoration.flames': 'Flames',
|
||
'profile.decoration.stars': 'Stars',
|
||
'profile.decoration.halo': 'Halo',
|
||
'profile.decoration.vines': 'Vines',
|
||
'profile.decoration.pixel': 'Pixel',
|
||
'profile.decoration.hearts': 'Hearts',
|
||
'profile.decoration.frost': 'Frost',
|
||
'profile.decoration.crown': 'Crown',
|
||
'profile.profileEffect.none': 'None',
|
||
'profile.profileEffect.sparkles': 'Sparkles',
|
||
'profile.profileEffect.rain': 'Rain',
|
||
'profile.profileEffect.snow': 'Snow',
|
||
'profile.profileEffect.matrix': 'Matrix',
|
||
'profile.profileEffect.confetti': 'Confetti',
|
||
'profile.profileEffect.aurora': 'Aurora',
|
||
'profile.profileEffect.embers': 'Embers',
|
||
'profile.profileEffect.bubbles': 'Bubbles',
|
||
'profile.profileEffect.hearts': 'Hearts',
|
||
'profile.nameplate.none': 'None',
|
||
'profile.nameplate.sunset': 'Sunset',
|
||
'profile.nameplate.ocean': 'Ocean',
|
||
'profile.nameplate.neon': 'Neon',
|
||
'profile.nameplate.forest': 'Forest',
|
||
'profile.nameplate.royal': 'Royal',
|
||
'profile.nameplate.brasil': 'Brasil',
|
||
'profile.nameplate.cyber': 'Cyber',
|
||
'profile.nameplate.blossom': 'Blossom',
|
||
'profile.nameplate.midnight': 'Midnight',
|
||
'profile.nameplate.custom': 'Custom',
|
||
'profile.badge.admin': 'Instance admin',
|
||
'profile.badge.owner': 'Server owner',
|
||
'profile.badge.founder': 'Founding member — joined in the first month',
|
||
'profile.badge.veteran': 'Veteran — over a year here',
|
||
'profile.badge.og': 'OG — over six months here',
|
||
'profile.badge.stylist': 'Stylist — customised profile',
|
||
'profile.font.techno': 'Techno',
|
||
'profile.font.block': 'Block',
|
||
'profile.font.retro': 'Retro',
|
||
'profile.effect.chrome': 'Chrome',
|
||
'profile.effect.gold': 'Gold',
|
||
'profile.effect.holo': 'Holo',
|
||
'profile.effect.ice': 'Ice',
|
||
'profile.effect.shadow': '3D',
|
||
'profile.effect.outline': 'Outline',
|
||
'profile.effect.rainbow': 'Rainbow',
|
||
'profile.decoration.cd': 'Holo Disc',
|
||
'profile.decoration.chain': 'Chain',
|
||
'profile.decoration.cyber': 'Cyber',
|
||
'profile.decoration.lightning': 'Lightning',
|
||
'profile.decoration.spikes': 'Spikes',
|
||
'profile.decoration.butterfly': 'Butterflies',
|
||
'profile.decoration.sakura': 'Sakura',
|
||
'profile.decoration.bubbles': 'Bubbles',
|
||
'profile.decoration.flower': 'Flower',
|
||
'profile.widgets.collection': 'Game Collection',
|
||
'profile.widgets.favoriteGame': 'Favorite Game',
|
||
'profile.widgets.rotation': 'Games in Rotation',
|
||
'profile.widgets.wishlist': 'Want to Play',
|
||
'profile.widgets.empty': 'Nothing on the board yet.',
|
||
// Profile editor — three-panel layout
|
||
'profile.section.nameplate': 'Nameplate',
|
||
'profile.section.avatar': 'Avatar & Decoration',
|
||
'profile.section.name': 'Display Name Style',
|
||
'profile.section.theme': 'Theme',
|
||
'profile.section.effect': 'Profile Effect',
|
||
'profile.section.identity': 'Pronouns & Tag',
|
||
'profile.section.links': 'Links',
|
||
'profile.section.board': 'Board',
|
||
'profile.editor.back': 'Back',
|
||
'profile.editor.items': 'items',
|
||
'profile.editor.inChat': 'In chat',
|
||
'profile.editor.inChat.sample': 'does anyone read this?',
|
||
'profile.editor.inMembers': 'In the member list',
|
||
'profile.editor.editBoard': 'Edit',
|
||
} as const;
|
||
|
||
export type TranslationKey = keyof typeof en;
|
||
export type Dictionary = Record<TranslationKey, string>;
|