diff --git a/docs/roadmap-resenha.md b/docs/roadmap-resenha.md
index 916e0421..9c579ac1 100644
--- a/docs/roadmap-resenha.md
+++ b/docs/roadmap-resenha.md
@@ -110,6 +110,12 @@ fixas por `t('chave')`. O que faltar cai para o inglês sozinho.
- Configurações do servidor → Cargos (incluindo a tabela de permissões)
- Configurações da categoria (renomear, privacidade, exceções, excluir)
- Configurações da instância → Geral (nome, descoberta, busca de GIFs)
+- Mensagens de sistema do chat e a prévia delas na barra lateral
+- Login, registro e a página de convite
+- Voz: controles, informações de conexão, folha de entrada no celular, aba Voz
+- Telas do celular (`Mobile*.tsx`)
+- Navegação das configurações (servidor, usuário e instância) + Descoberta e
+ pedidos de entrada
Os rótulos de permissão viraram um conjunto único (`perm.*`), usado pelo painel
de cargos e pelos editores de exceção de canal e de categoria — antes havia
@@ -118,8 +124,9 @@ duas cópias da mesma lista.
### Fila sugerida de tradução
Configurações → Instância: registro, federação, transmissão, armazenamento e
-usuários · chrome do `SpaceSettings` e do `UserSettings` (abas) ·
-`ConnectedInstances` · `UserProfileModal` · telas móveis (`Mobile*.tsx`)
+usuários (os cinco painéis grandes, só o dono vê) · `ConnectedInstances` ·
+`UserProfileModal` · `MainContent` e `ChannelSidebar` · mensagens de erro
+vindas de stores e hooks (`instanceStore`, `useLiveKit`, `transferStore`)
## Correções pendentes
diff --git a/packages/web/src/components/JoinPage.tsx b/packages/web/src/components/JoinPage.tsx
index d8ad1b34..5faa0cdf 100644
--- a/packages/web/src/components/JoinPage.tsx
+++ b/packages/web/src/components/JoinPage.tsx
@@ -7,10 +7,12 @@ import { api, createApiClient } from '../api/client';
import { parseInviteInput } from '../utils/inviteParser';
import { Avatar } from './ui/Avatar';
import type { InvitePreview } from '@backspace/shared';
+import { useT } from '../i18n';
type JoinPhase = 'preview' | 'connect' | 'fallback' | 'other-instance' | 'already-member';
export function JoinPage() {
+ const t = useT();
const { inviteCode: rawInviteCode } = useParams<{ inviteCode: string }>();
const navigate = useNavigate();
const token = useAuthStore((s) => s.token);
@@ -59,13 +61,13 @@ export function JoinPage() {
// Fetch preview on mount
useEffect(() => {
if (!rawInviteCode) {
- setPreviewError('No invite code provided');
+ setPreviewError(t('join.page.noCode'));
setIsLoadingPreview(false);
return;
}
if (!parsed) {
- setPreviewError('Invalid invite code');
+ setPreviewError(t('join.page.invalidCode'));
setIsLoadingPreview(false);
return;
}
@@ -84,7 +86,7 @@ export function JoinPage() {
const data = await client.spaces.invitePreview(parsed.code);
setPreview(data);
} catch (err) {
- setPreviewError(err instanceof Error ? err.message : 'Failed to load invite');
+ setPreviewError(err instanceof Error ? err.message : t('join.page.loadFailed'));
} finally {
setIsLoadingPreview(false);
}
@@ -106,7 +108,7 @@ export function JoinPage() {
setPhase('connect');
setError('');
} else {
- const msg = err instanceof Error ? err.message : 'Failed to join space';
+ const msg = err instanceof Error ? err.message : t('join.page.joinFailed');
if (msg.toLowerCase().includes('already a member')) {
setPhase('already-member');
setError('');
@@ -136,7 +138,7 @@ export function JoinPage() {
setFallbackPassword('');
setError('');
} else {
- const msg = err instanceof Error ? err.message : 'Failed to connect';
+ const msg = err instanceof Error ? err.message : t('join.page.connectFailed');
if (msg.toLowerCase().includes('already a member')) {
setPhase('already-member');
setError('');
@@ -160,7 +162,7 @@ export function JoinPage() {
const space = await joinByCode(parsed.code, parsed.origin);
navigate(`/channels/${space.id}`);
} catch (err) {
- const msg = err instanceof Error ? err.message : 'Failed to log in';
+ const msg = err instanceof Error ? err.message : t('join.page.loginFailed');
if (msg.toLowerCase().includes('already a member')) {
setPhase('already-member');
setError('');
@@ -204,7 +206,7 @@ export function JoinPage() {
Loading invite...
+{t('join.page.loading')}
); @@ -221,9 +223,9 @@ export function JoinPage() {- {previewError || 'This invite link is invalid or has expired.'} + {previewError || t('join.page.invalid.body')}
{token ? ( -You've been invited to join
+{t('join.page.invitedTo')}
{preview.description}
@@ -303,7 +305,7 @@ export function JoinPage() { disabled={isJoining} className="w-full py-2.5 bg-accent-primary hover:bg-accent-primary/80 text-white font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > - {isJoining ? 'Joining...' : `Join as ${user.displayName || user.username}`} + {isJoining ? t('join.joining') : `Join as ${user.displayName || user.username}`}Not you?{' '} @@ -378,7 +380,7 @@ export function JoinPage() { type="text" value={otherDomain} onChange={(e) => setOtherDomain(e.target.value)} - placeholder="e.g. my-instance.com" + placeholder={t('join.page.instance.placeholder')} className="input-standard flex-1 py-2.5" autoFocus /> @@ -431,7 +433,7 @@ export function JoinPage() { type="password" value={password} onChange={(e) => setPassword(e.target.value)} - placeholder="Your account password" + placeholder={t('join.connect.password.placeholder')} className="input-standard w-full py-2.5" disabled={isJoining} autoFocus @@ -454,7 +456,7 @@ export function JoinPage() { disabled={isJoining || !password} className="flex-1 py-2.5 bg-accent-primary hover:bg-accent-primary/80 text-white font-medium rounded transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > - {isJoining ? 'Connecting...' : 'Connect & Join'} + {isJoining ? t('join.connect.connecting') : t('join.connect.submit')} @@ -473,24 +475,24 @@ export function JoinPage() {
You're already in {spaceName}!
-Redirecting you now...
+{t('join.page.alreadyMember', { space: spaceName })}
+{t('join.page.redirecting')}
); diff --git a/packages/web/src/components/auth/LoginPage.tsx b/packages/web/src/components/auth/LoginPage.tsx index 00574413..0d8a9e3c 100644 --- a/packages/web/src/components/auth/LoginPage.tsx +++ b/packages/web/src/components/auth/LoginPage.tsx @@ -4,8 +4,10 @@ import { useAuthStore } from '../../stores/authStore'; import { api, RateLimitError } from '../../api/client'; import type { InstanceInfoResponse } from '@backspace/shared'; import { SourceCodeLink } from '../ui/SourceCodeLink'; +import { useT } from '../../i18n'; export function LoginPage() { + const t = useT(); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); @@ -46,11 +48,11 @@ export function LoginPage() { setError(''); if (!username.trim()) { - setError('Username is required'); + setError(t('auth.username.required')); return; } if (!password) { - setError('Password is required'); + setError(t('auth.password.required')); return; } @@ -66,7 +68,7 @@ export function LoginPage() { setRetryAfter(err.retryAfter); setError(''); } else { - setError(err instanceof Error ? err.message : 'Login failed'); + setError(err instanceof Error ? err.message : t('auth.login.failed')); } } }; @@ -78,15 +80,15 @@ export function LoginPage() {We're so excited to see you again!
+{t('auth.login.subtitle')}