i18n: translate the channel and member sidebars
OpenSSF Scorecard / Scorecard analysis (push) Waiting to run
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) Waiting to run
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
Clears the two remaining rounds owed under the fork's per-update translation rule. Covers channels, direct messages, the context menus, the inline voice settings and the member list's loading and grouping labels. The member grouping needed care: one function returns either a fixed label or a role's own name, and role names are user data that must never be translated. It now marks only the fixed cases and the component resolves them at render, leaving role names untouched. The function is not a component, so it cannot call the hook itself.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useT } from '../../i18n';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import type { Channel } from '@backspace/shared';
|
||||
import { useSpaceStore, getChannelOrigin, getMyUserIdForOrigin } from '../../stores/spaceStore';
|
||||
@@ -26,6 +27,7 @@ import { useAudioDevices } from '../../hooks/useAudioDevices';
|
||||
import { DropdownItem } from '../modals/settingsPanels/_shared/SettingsPickerPrimitives';
|
||||
|
||||
export function ChannelSidebar() {
|
||||
const tr = useT();
|
||||
const spaces = useSpaceStore((s) => s.spaces);
|
||||
const currentSpaceId = useSpaceStore((s) => s.currentSpaceId);
|
||||
const loadingSpaceId = useSpaceStore((s) => s.loadingSpaceId);
|
||||
@@ -318,7 +320,7 @@ export function ChannelSidebar() {
|
||||
items.push({
|
||||
key: 'create-channel',
|
||||
type: 'action',
|
||||
label: 'Create Channel',
|
||||
label: tr('sidebar.createChannel'),
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
<path d="M2.5 12.5v-9l5-2v9l-5 2zm6-9v9l5-2v-9l-5 2z" opacity="0.5" />
|
||||
@@ -330,7 +332,7 @@ export function ChannelSidebar() {
|
||||
items.push({
|
||||
key: 'create-category',
|
||||
type: 'action',
|
||||
label: 'Create Category',
|
||||
label: tr('sidebar.createCategory'),
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z" />
|
||||
@@ -343,7 +345,7 @@ export function ChannelSidebar() {
|
||||
items.push({
|
||||
key: 'invite',
|
||||
type: 'action',
|
||||
label: 'Invite People',
|
||||
label: tr('sidebar.invitePeople'),
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M21 3H24V5H21V8H19V5H16V3H19V0H21V3ZM10 12C12.21 12 14 10.21 14 8C14 5.79 12.21 4 10 4C7.79 4 6 5.79 6 8C6 10.21 7.79 12 10 12ZM10 13C6.69 13 1 14.66 1 18V20H19V18C19 14.66 13.31 13 10 13Z" />
|
||||
@@ -355,7 +357,7 @@ export function ChannelSidebar() {
|
||||
items.push({
|
||||
key: 'settings',
|
||||
type: 'action',
|
||||
label: 'Space Settings',
|
||||
label: tr('sidebar.spaceSettings'),
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1112 8.4a3.6 3.6 0 010 7.2z" />
|
||||
@@ -373,7 +375,7 @@ export function ChannelSidebar() {
|
||||
{
|
||||
key: 'leave-group',
|
||||
type: 'action',
|
||||
label: 'Leave Group',
|
||||
label: tr('sidebar.leaveGroup'),
|
||||
danger: true,
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||||
@@ -453,7 +455,7 @@ export function ChannelSidebar() {
|
||||
<path d="M3 18a1 1 0 0 0 1 1h18a1 1 0 0 0 1-1v-1c0-2.76-5.37-4-8-4s-8 1.24-8 4v1Z" />
|
||||
<path d="M3.5 13.5a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7Z" opacity=".5" />
|
||||
</svg>
|
||||
<span className="font-medium text-[16px]">Friends</span>
|
||||
<span className="font-medium text-[16px]">{tr('sidebar.friends')}</span>
|
||||
</div>
|
||||
|
||||
{/* Placeholder nav items */}
|
||||
@@ -463,7 +465,7 @@ export function ChannelSidebar() {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0">
|
||||
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
|
||||
</svg>
|
||||
<span className="font-medium text-[16px]">Coming Soon</span>
|
||||
<span className="font-medium text-[16px]">{tr('sidebar.comingSoon')}</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-3 px-2 h-[42px] rounded-[6px] mb-[2px] text-txt-tertiary cursor-default opacity-50"
|
||||
@@ -471,11 +473,11 @@ export function ChannelSidebar() {
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor" className="flex-shrink-0">
|
||||
<path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-7 14c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6z" />
|
||||
</svg>
|
||||
<span className="font-medium text-[16px]">Coming Soon</span>
|
||||
<span className="font-medium text-[16px]">{tr('sidebar.comingSoon')}</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-[18px] px-2 mb-1 flex items-center justify-between group">
|
||||
<span className="text-[12px] font-bold text-txt-tertiary tracking-wider">Direct Messages</span>
|
||||
<span className="text-[12px] font-bold text-txt-tertiary tracking-wider">{tr('sidebar.directMessages')}</span>
|
||||
<button
|
||||
onClick={() => openModal('newDm')}
|
||||
className="text-txt-tertiary hover:text-txt-primary transition-colors"
|
||||
@@ -584,7 +586,7 @@ export function ChannelSidebar() {
|
||||
{/* Channels — dynamic category layout */}
|
||||
<div ref={scrollContainerRef} className="flex-1 overflow-y-auto pt-3 px-2 no-scrollbar" style={{ paddingBottom: floatingPanelHeight + 24 }} onDrop={containerHandlers.onDrop} onDragOver={containerHandlers.onDragOver} onContextMenu={handleSidebarContextMenu}>
|
||||
{showChannelSkeleton ? (
|
||||
<div className="px-2 pt-3" role="status" aria-label="Loading channels">
|
||||
<div className="px-2 pt-3" role="status" aria-label={tr('sidebar.loadingChannels')}>
|
||||
{/* Category group 1 */}
|
||||
<div className="skeleton skeleton-bar h-2 w-[45%] ml-2 mb-3" />
|
||||
{Array.from({ length: 3 }, (_, i) => (
|
||||
@@ -706,7 +708,7 @@ export function ChannelSidebar() {
|
||||
{
|
||||
key: 'category-settings',
|
||||
type: 'action',
|
||||
label: 'Category Settings',
|
||||
label: tr('sidebar.categorySettings'),
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1115.6 12 3.611 3.611 0 0112 15.6z" />
|
||||
@@ -717,7 +719,7 @@ export function ChannelSidebar() {
|
||||
{
|
||||
key: 'delete-category',
|
||||
type: 'action',
|
||||
label: 'Delete Category',
|
||||
label: tr('sidebar.deleteCategory'),
|
||||
danger: true,
|
||||
icon: (
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
|
||||
@@ -758,7 +760,7 @@ export function ChannelSidebar() {
|
||||
/>
|
||||
))}
|
||||
{catChannels.length === 0 && (
|
||||
<div className="px-2 py-2 text-[12px] text-txt-tertiary italic opacity-40">No channels</div>
|
||||
<div className="px-2 py-2 text-[12px] text-txt-tertiary italic opacity-40">{tr('sidebar.noChannels')}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -777,7 +779,7 @@ export function ChannelSidebar() {
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" className="flex-shrink-0 opacity-70">
|
||||
<path d="M8 2a.5.5 0 01.5.5v5h5a.5.5 0 010 1h-5v5a.5.5 0 01-1 0v-5h-5a.5.5 0 010-1h5v-5A.5.5 0 018 2z" />
|
||||
</svg>
|
||||
<span className="text-[12px]">Create Channel</span>
|
||||
<span className="text-[12px]">{tr('sidebar.createChannel')}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
@@ -787,7 +789,7 @@ export function ChannelSidebar() {
|
||||
<svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" className="flex-shrink-0 opacity-70">
|
||||
<path d="M8 2a.5.5 0 01.5.5v5h5a.5.5 0 010 1h-5v5a.5.5 0 01-1 0v-5h-5a.5.5 0 010-1h5v-5A.5.5 0 018 2z" />
|
||||
</svg>
|
||||
<span className="text-[12px]">Create Category</span>
|
||||
<span className="text-[12px]">{tr('sidebar.createCategory')}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -845,6 +847,7 @@ function UserAreaPanel({
|
||||
onDeafenToggle: () => void;
|
||||
onSettingsClick: (tab?: string) => void;
|
||||
}) {
|
||||
const tr = useT();
|
||||
const [accountMenuOpen, setAccountMenuOpen] = useState(false);
|
||||
const [openPanel, setOpenPanel] = useState<'input' | 'output' | null>(null);
|
||||
const inputDeviceId = useVoiceStore((s) => s.inputDeviceId);
|
||||
@@ -959,7 +962,7 @@ function UserAreaPanel({
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-interactive-hover transition-colors"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[15px] font-semibold text-txt-primary text-left">Input Device</div>
|
||||
<div className="text-[15px] font-semibold text-txt-primary text-left">{tr('sidebar.inputDevice')}</div>
|
||||
<div className="text-[13px] text-txt-tertiary truncate text-left">{selectedInputLabel}</div>
|
||||
</div>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary flex-shrink-0 ml-2">
|
||||
@@ -1004,7 +1007,7 @@ function UserAreaPanel({
|
||||
|
||||
{/* Input Volume */}
|
||||
<div className="px-4 py-3">
|
||||
<div className="text-[15px] font-semibold text-txt-primary mb-2">Input Volume</div>
|
||||
<div className="text-[15px] font-semibold text-txt-primary mb-2">{tr('sidebar.inputVolume')}</div>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
@@ -1039,7 +1042,7 @@ function UserAreaPanel({
|
||||
onClick={() => onSettingsClick('voice')}
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-interactive-hover transition-colors"
|
||||
>
|
||||
<span className="text-[15px] font-semibold text-txt-primary">Voice Settings</span>
|
||||
<span className="text-[15px] font-semibold text-txt-primary">{tr('sidebar.voiceSettings')}</span>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary">
|
||||
<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" />
|
||||
</svg>
|
||||
@@ -1057,7 +1060,7 @@ function UserAreaPanel({
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-interactive-hover transition-colors"
|
||||
>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[15px] font-semibold text-txt-primary text-left">Output Device</div>
|
||||
<div className="text-[15px] font-semibold text-txt-primary text-left">{tr('sidebar.outputDevice')}</div>
|
||||
<div className="text-[13px] text-txt-tertiary truncate text-left">{selectedOutputLabel}</div>
|
||||
</div>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary flex-shrink-0 ml-2">
|
||||
@@ -1102,7 +1105,7 @@ function UserAreaPanel({
|
||||
|
||||
{/* Output Volume */}
|
||||
<div className="px-4 py-3">
|
||||
<div className="text-[15px] font-semibold text-txt-primary mb-2">Output Volume</div>
|
||||
<div className="text-[15px] font-semibold text-txt-primary mb-2">{tr('sidebar.outputVolume')}</div>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
@@ -1125,7 +1128,7 @@ function UserAreaPanel({
|
||||
onClick={() => onSettingsClick('voice')}
|
||||
className="w-full px-4 py-3 flex items-center justify-between hover:bg-interactive-hover transition-colors"
|
||||
>
|
||||
<span className="text-[15px] font-semibold text-txt-primary">Voice Settings</span>
|
||||
<span className="text-[15px] font-semibold text-txt-primary">{tr('sidebar.voiceSettings')}</span>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" className="text-txt-tertiary">
|
||||
<path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z" />
|
||||
</svg>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { useT } from '../../i18n';
|
||||
import type { MemberWithUser, Activity } from '@backspace/shared';
|
||||
import { useSpaceStore } from '../../stores/spaceStore';
|
||||
import { useUIStore } from '../../stores/uiStore';
|
||||
@@ -21,7 +22,7 @@ function getMemberGroup(member: MemberWithUser, ownerId: string | undefined) {
|
||||
const ownerRole = member.roles?.find(r => r.position > 0);
|
||||
return {
|
||||
key: '__owner__',
|
||||
label: 'OWNER',
|
||||
label: '__OWNER__',
|
||||
color: ownerRole?.color ?? 'rgb(var(--accent-rose))',
|
||||
position: Infinity,
|
||||
};
|
||||
@@ -40,7 +41,10 @@ function getMemberGroup(member: MemberWithUser, ownerId: string | undefined) {
|
||||
// No explicit roles — just @everyone
|
||||
return {
|
||||
key: '__online__',
|
||||
label: 'ONLINE',
|
||||
// Marcado em vez de traduzido aqui: esta funcao nao e um componente e nao
|
||||
// pode usar o hook. Nomes de cargo passam adiante sem traducao — sao dados
|
||||
// do usuario, nao interface.
|
||||
label: '__ONLINE__',
|
||||
color: undefined,
|
||||
position: -1,
|
||||
};
|
||||
@@ -111,6 +115,7 @@ export function MemberSidebar() {
|
||||
const currentSpaceId = useSpaceStore((s) => s.currentSpaceId);
|
||||
const loadingSpaceId = useSpaceStore((s) => s.loadingSpaceId);
|
||||
const memberListOpen = useUIStore((s) => s.memberListOpen);
|
||||
const tr = useT();
|
||||
const openUserProfile = useUIStore((s) => s.openUserProfile);
|
||||
const userActivities = useActivityStore((s) => s.userActivities);
|
||||
|
||||
@@ -183,7 +188,7 @@ export function MemberSidebar() {
|
||||
return (
|
||||
<div className="w-60 bg-surface-members flex-shrink-0 overflow-y-auto select-none no-scrollbar hidden md:block border-l border-border-hard">
|
||||
{showMemberSkeleton ? (
|
||||
<div className="px-3 pt-4" role="status" aria-label="Loading members">
|
||||
<div className="px-3 pt-4" role="status" aria-label={tr('sidebar.loadingMembers')}>
|
||||
{/* Role group 1 */}
|
||||
<div className="skeleton skeleton-bar h-2 w-[40%] mb-3" style={{ animationDelay: '0s' }} />
|
||||
{Array.from({ length: 2 }, (_, i) => (
|
||||
@@ -207,7 +212,11 @@ export function MemberSidebar() {
|
||||
{roleGroups.map(([key, group]) => (
|
||||
<div key={key} className="mb-4">
|
||||
<h3 className="text-[10.5px] font-bold text-txt-tertiary uppercase tracking-[0.06em] px-2 mb-1">
|
||||
{group.label} — {group.members.length}
|
||||
{group.label === '__ONLINE__'
|
||||
? tr('sidebar.groupOnline')
|
||||
: group.label === '__OWNER__'
|
||||
? tr('sidebar.groupOwner')
|
||||
: group.label} — {group.members.length}
|
||||
</h3>
|
||||
{group.members.map((m) => renderMember(m))}
|
||||
</div>
|
||||
|
||||
@@ -56,6 +56,28 @@ export const en = {
|
||||
'accountMenu.copyId': 'Copy User ID',
|
||||
'accountMenu.copied': 'Copied',
|
||||
|
||||
// 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',
|
||||
|
||||
@@ -55,6 +55,28 @@ export const ptBR: Partial<Dictionary> = {
|
||||
'accountMenu.copyId': 'Copiar ID do usuário',
|
||||
'accountMenu.copied': 'Copiado',
|
||||
|
||||
// Barra lateral — servidores, canais e membros
|
||||
'sidebar.friends': 'Amigos',
|
||||
'sidebar.directMessages': 'Mensagens diretas',
|
||||
'sidebar.noChannels': 'Nenhum canal',
|
||||
'sidebar.comingSoon': 'Em breve',
|
||||
'sidebar.loadingChannels': 'Carregando canais',
|
||||
'sidebar.loadingMembers': 'Carregando membros',
|
||||
'sidebar.groupOnline': 'DISPONÍVEIS',
|
||||
'sidebar.groupOwner': 'DONO',
|
||||
'sidebar.createChannel': 'Criar canal',
|
||||
'sidebar.createCategory': 'Criar categoria',
|
||||
'sidebar.categorySettings': 'Configurações da categoria',
|
||||
'sidebar.deleteCategory': 'Excluir categoria',
|
||||
'sidebar.invitePeople': 'Convidar pessoas',
|
||||
'sidebar.spaceSettings': 'Configurações do servidor',
|
||||
'sidebar.leaveGroup': 'Sair do grupo',
|
||||
'sidebar.voiceSettings': 'Configurações de voz',
|
||||
'sidebar.inputDevice': 'Dispositivo de entrada',
|
||||
'sidebar.inputVolume': 'Volume de entrada',
|
||||
'sidebar.outputDevice': 'Dispositivo de saída',
|
||||
'sidebar.outputVolume': 'Volume de saída',
|
||||
|
||||
// Chat — composer e mensagens
|
||||
'chat.composer.placeholder': 'Conversar em {channel}',
|
||||
'chat.composer.attach': 'Anexar arquivo',
|
||||
|
||||
Reference in New Issue
Block a user