Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
847 changes: 847 additions & 0 deletions public/locales/en.json

Large diffs are not rendered by default.

31 changes: 19 additions & 12 deletions src/app/components/BackupRestore.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { MouseEventHandler, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useAtom } from 'jotai';
import { CryptoApi, KeyBackupInfo } from 'matrix-js-sdk/lib/crypto-api';
import {
Expand Down Expand Up @@ -35,6 +36,7 @@ type BackupStatusProps = {
enabled: boolean;
};
function BackupStatus({ enabled }: BackupStatusProps) {
const { t } = useTranslation();
return (
<Box as="span" gap="100" alignItems="Center">
<Badge variant={enabled ? 'Success' : 'Critical'} fill="Solid" size="200" radii="Pill" />
Expand All @@ -43,7 +45,7 @@ function BackupStatus({ enabled }: BackupStatusProps) {
size="L400"
style={{ color: enabled ? color.Success.Main : color.Critical.Main }}
>
{enabled ? 'Connected' : 'Disconnected'}
{enabled ? t('backup.connected') : t('backup.disconnected')}
</Text>
</Box>
);
Expand All @@ -52,21 +54,23 @@ type BackupSyncingProps = {
count: number;
};
function BackupSyncing({ count }: BackupSyncingProps) {
const { t } = useTranslation();
return (
<Box as="span" gap="100" alignItems="Center">
<Spinner size="50" variant="Primary" fill="Soft" />
<Text as="span" size="L400" style={{ color: color.Primary.Main }}>
Syncing ({count})
{t('backup.syncing', { count })}
</Text>
</Box>
);
}

function BackupProgressFetching() {
const { t } = useTranslation();
return (
<Box grow="Yes" gap="200" alignItems="Center">
<Badge variant="Secondary" fill="Solid" radii="300">
<Text size="L400">Restoring: 0%</Text>
<Text size="L400">{t('backup.restoring', { percentage: 0 })}</Text>
</Badge>
<Box grow="Yes" direction="Column">
<ProgressBar variant="Secondary" size="300" min={0} max={1} value={0} />
Expand All @@ -81,10 +85,11 @@ type BackupProgressProps = {
downloaded: number;
};
function BackupProgress({ total, downloaded }: BackupProgressProps) {
const { t } = useTranslation();
return (
<Box grow="Yes" gap="200" alignItems="Center">
<Badge variant="Secondary" fill="Solid" radii="300">
<Text size="L400">Restoring: {`${Math.round(percent(0, total, downloaded))}%`}</Text>
<Text size="L400">{t('backup.restoring', { percentage: Math.round(percent(0, total, downloaded)) })}</Text>
</Badge>
<Box grow="Yes" direction="Column">
<ProgressBar variant="Secondary" size="300" min={0} max={total} value={downloaded} />
Expand All @@ -103,6 +108,7 @@ type BackupTrustInfoProps = {
backupInfo: KeyBackupInfo;
};
function BackupTrustInfo({ crypto, backupInfo }: BackupTrustInfoProps) {
const { t } = useTranslation();
const trust = useKeyBackupTrust(crypto, backupInfo);

if (!trust) return null;
Expand All @@ -111,20 +117,20 @@ function BackupTrustInfo({ crypto, backupInfo }: BackupTrustInfoProps) {
<Box direction="Column">
{trust.matchesDecryptionKey ? (
<Text size="T200" style={{ color: color.Success.Main }}>
<b>Backup has trusted decryption key.</b>
<b>{t('backup.trustedKey')}</b>
</Text>
) : (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>Backup does not have trusted decryption key!</b>
<b>{t('backup.untrustedKey')}</b>
</Text>
)}
{trust.trusted ? (
<Text size="T200" style={{ color: color.Success.Main }}>
<b>Backup has trusted by signature.</b>
<b>{t('backup.trustedSignature')}</b>
</Text>
) : (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>Backup does not have trusted signature!</b>
<b>{t('backup.untrustedSignature')}</b>
</Text>
)}
</Box>
Expand All @@ -135,6 +141,7 @@ type BackupRestoreTileProps = {
crypto: CryptoApi;
};
export function BackupRestoreTile({ crypto }: BackupRestoreTileProps) {
const { t } = useTranslation();
const [restoreProgress, setRestoreProgress] = useAtom(backupRestoreProgressAtom);
const restoring =
restoreProgress.status === BackupProgressStatus.Fetching ||
Expand Down Expand Up @@ -168,7 +175,7 @@ export function BackupRestoreTile({ crypto }: BackupRestoreTileProps) {
return (
<InfoCard
variant="Surface"
title="Encryption Backup"
title={t('backup.encryptionBackup')}
after={
<Box alignItems="Center" gap="200">
{remainingSession === 0 ? (
Expand Down Expand Up @@ -212,7 +219,7 @@ export function BackupRestoreTile({ crypto }: BackupRestoreTileProps) {
<Box direction="Column" gap="200">
<InfoCard
variant="SurfaceVariant"
title="Backup Details"
title={t('backup.backupDetails')}
description={
<>
<span>Version: {backupInfo?.version ?? 'NIL'}</span>
Expand All @@ -234,7 +241,7 @@ export function BackupRestoreTile({ crypto }: BackupRestoreTileProps) {
}
before={<Icon size="100" src={Icons.Download} />}
>
<Text size="B300">Restore Backup</Text>
<Text size="B300">{t('backup.restoreBackup')}</Text>
</Button>
</Box>
</Menu>
Expand All @@ -251,7 +258,7 @@ export function BackupRestoreTile({ crypto }: BackupRestoreTileProps) {
)}
{!backupEnabled && backupInfo === null && (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>No backup present on server!</b>
<b>{t('backup.noBackup')}</b>
</Text>
)}
{!syncFailure && !backupEnabled && backupInfo && (
Expand Down
8 changes: 5 additions & 3 deletions src/app/components/BetaNoticeBadge.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { TooltipProvider, Tooltip, Box, Text, Badge, toRem } from 'folds';

export function BetaNoticeBadge() {
const { t } = useTranslation();
return (
<TooltipProvider
position="Right"
align="Center"
tooltip={
<Tooltip style={{ maxWidth: toRem(200) }}>
<Box direction="Column">
<Text size="L400">Notice</Text>
<Text size="T200">This feature is under testing and may change over time.</Text>
<Text size="L400">{t('common.notice')}</Text>
<Text size="T200">{t('common.betaDesc')}</Text>
</Box>
</Tooltip>
}
>
{(triggerRef) => (
<Badge size="500" tabIndex={0} ref={triggerRef} variant="Primary" fill="Solid">
<Text size="L400">Beta</Text>
<Text size="L400">{t('common.beta')}</Text>
</Badge>
)}
</TooltipProvider>
Expand Down
40 changes: 20 additions & 20 deletions src/app/components/DeviceVerificationSetup.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { FormEventHandler, forwardRef, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Dialog,
Header,
Expand Down Expand Up @@ -71,6 +72,7 @@ type SetupVerificationProps = {
onComplete: (recoveryKey: string) => void;
};
function SetupVerification({ onComplete }: SetupVerificationProps) {
const { t } = useTranslation();
const mx = useMatrixClient();
const alive = useAlive();

Expand Down Expand Up @@ -182,31 +184,30 @@ function SetupVerification({ onComplete }: SetupVerificationProps) {
return (
<Box as="form" onSubmit={handleSubmit} direction="Column" gap="400">
<Text size="T300">
Generate a <b>Recovery Key</b> for verifying identity if you do not have access to other
devices. Additionally, setup a passphrase as a memorable alternative.
{t('verification.generateRecoveryKeyDesc')}
</Text>
<Box direction="Column" gap="100">
<Text size="L400">Passphrase (Optional)</Text>
<Text size="L400">{t('verification.passphraseOptional')}</Text>
<PasswordInput name="passphraseInput" size="400" readOnly={loading} />
</Box>
<Button
type="submit"
disabled={loading}
before={loading && <Spinner size="200" variant="Primary" fill="Solid" />}
>
<Text size="B400">Continue</Text>
<Text size="B400">{t('common.continue')}</Text>
</Button>
{setupState.status === AsyncStatus.Error && (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>{setupState.error ? setupState.error.message : 'Unexpected Error!'}</b>
<b>{setupState.error ? setupState.error.message : t('verification.unexpectedError')}</b>
</Text>
)}
{nextAuthData !== null && uiaAction && (
<ActionUIAFlowsLoader
authData={nextAuthData ?? uiaAction.authData}
unsupported={() => (
<Text size="T200">
Authentication steps to perform this action are not supported by client.
{t('verification.authNotSupported')}
</Text>
)}
>
Expand All @@ -228,6 +229,7 @@ type RecoveryKeyDisplayProps = {
recoveryKey: string;
};
function RecoveryKeyDisplay({ recoveryKey }: RecoveryKeyDisplayProps) {
const { t } = useTranslation();
const [show, setShow] = useState(false);

const handleCopy = () => {
Expand All @@ -246,11 +248,10 @@ function RecoveryKeyDisplay({ recoveryKey }: RecoveryKeyDisplayProps) {
return (
<Box direction="Column" gap="400">
<Text size="T300">
Store the Recovery Key in a safe place for future use, as you will need it to verify your
identity if you do not have access to other devices.
{t('verification.storeRecoveryKey')}
</Text>
<Box direction="Column" gap="100">
<Text size="L400">Recovery Key</Text>
<Text size="L400">{t('verification.recoveryKey')}</Text>
<Box
className={ContainerColor({ variant: 'SurfaceVariant' })}
style={{
Expand All @@ -265,16 +266,16 @@ function RecoveryKeyDisplay({ recoveryKey }: RecoveryKeyDisplayProps) {
{safeToDisplayKey}
</Text>
<Chip onClick={() => setShow(!show)} variant="Secondary" radii="Pill">
<Text size="B300">{show ? 'Hide' : 'Show'}</Text>
<Text size="B300">{show ? t('verification.hide') : t('verification.show')}</Text>
</Chip>
</Box>
</Box>
<Box direction="Column" gap="200">
<Button onClick={handleCopy}>
<Text size="B400">Copy</Text>
<Text size="B400">{t('verification.copy')}</Text>
</Button>
<Button onClick={handleDownload} fill="Soft">
<Text size="B400">Download</Text>
<Text size="B400">{t('verification.download')}</Text>
</Button>
</Box>
</Box>
Expand All @@ -286,6 +287,7 @@ type DeviceVerificationSetupProps = {
};
export const DeviceVerificationSetup = forwardRef<HTMLDivElement, DeviceVerificationSetupProps>(
({ onCancel }, ref) => {
const { t } = useTranslation();
const [recoveryKey, setRecoveryKey] = useState<string>();

return (
Expand All @@ -299,7 +301,7 @@ export const DeviceVerificationSetup = forwardRef<HTMLDivElement, DeviceVerifica
size="500"
>
<Box grow="Yes">
<Text size="H4">Setup Device Verification</Text>
<Text size="H4">{t('verification.setupTitle')}</Text>
</Box>
<IconButton size="300" radii="300" onClick={onCancel}>
<Icon src={Icons.Cross} />
Expand All @@ -321,6 +323,7 @@ type DeviceVerificationResetProps = {
};
export const DeviceVerificationReset = forwardRef<HTMLDivElement, DeviceVerificationResetProps>(
({ onCancel }, ref) => {
const { t } = useTranslation();
const [reset, setReset] = useState(false);

return (
Expand All @@ -334,7 +337,7 @@ export const DeviceVerificationReset = forwardRef<HTMLDivElement, DeviceVerifica
size="500"
>
<Box grow="Yes">
<Text size="H4">Reset Device Verification</Text>
<Text size="H4">{t('verification.resetTitle')}</Text>
</Box>
<IconButton size="300" radii="300" onClick={onCancel}>
<Icon src={Icons.Cross} />
Expand All @@ -356,16 +359,13 @@ export const DeviceVerificationReset = forwardRef<HTMLDivElement, DeviceVerifica
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Box direction="Column" gap="200">
<Text size="H1">✋🧑‍🚒🤚</Text>
<Text size="T300">Resetting device verification is permanent.</Text>
<Text size="T300">{t('verification.resetPermanent')}</Text>
<Text size="T300">
Anyone you have verified with will see security alerts and your encryption backup
will be lost. You almost certainly do not want to do this, unless you have lost{' '}
<b>Recovery Key</b> or <b>Recovery Passphrase</b> and every device you can verify
from.
{t('verification.resetWarning')}
</Text>
</Box>
<Button variant="Critical" onClick={() => setReset(true)}>
<Text size="B400">Reset</Text>
<Text size="B400">{t('verification.reset')}</Text>
</Button>
</Box>
)}
Expand Down
4 changes: 3 additions & 1 deletion src/app/components/HexColorPickerPopOut.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import FocusTrap from 'focus-trap-react';
import { Box, Button, config, Menu, PopOut, RectCords, Text } from 'folds';
import React, { MouseEventHandler, ReactNode, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { stopPropagation } from '../utils/keyboard';

type HexColorPickerPopOutProps = {
Expand All @@ -9,6 +10,7 @@ type HexColorPickerPopOutProps = {
onRemove?: () => void;
};
export function HexColorPickerPopOut({ picker, onRemove, children }: HexColorPickerPopOutProps) {
const { t } = useTranslation();
const [cords, setCords] = useState<RectCords>();

const handleOpen: MouseEventHandler<HTMLElement> = (evt) => {
Expand Down Expand Up @@ -45,7 +47,7 @@ export function HexColorPickerPopOut({ picker, onRemove, children }: HexColorPic
radii="400"
onClick={() => onRemove()}
>
<Text size="B300">Remove</Text>
<Text size="B300">{t('roomSettings.remove')}</Text>
</Button>
)}
</Box>
Expand Down
24 changes: 14 additions & 10 deletions src/app/components/JoinRulesSwitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
Spinner,
} from 'folds';
import { JoinRule } from 'matrix-js-sdk';
import { useTranslation } from 'react-i18next';
import FocusTrap from 'focus-trap-react';
import { stopPropagation } from '../utils/keyboard';
import { getRoomIconSrc } from '../utils/room';
Expand All @@ -37,18 +38,20 @@ export const useJoinRuleIcons = (roomType?: string): JoinRuleIcons =>
);

type JoinRuleLabels = Record<ExtendedJoinRules, string>;
export const useRoomJoinRuleLabel = (): JoinRuleLabels =>
useMemo(
export const useRoomJoinRuleLabel = (): JoinRuleLabels => {
const { t } = useTranslation();
return useMemo(
() => ({
[JoinRule.Invite]: 'Invite Only',
[JoinRule.Knock]: 'Knock & Invite',
knock_restricted: 'Space Members or Knock',
[JoinRule.Restricted]: 'Space Members',
[JoinRule.Public]: 'Public',
[JoinRule.Private]: 'Invite Only',
[JoinRule.Invite]: t('roomSettings.inviteOnly'),
[JoinRule.Knock]: t('roomSettings.knockInvite'),
knock_restricted: t('roomSettings.spaceMembersOrKnock'),
[JoinRule.Restricted]: t('roomSettings.spaceMembers'),
[JoinRule.Public]: t('roomSettings.public'),
[JoinRule.Private]: t('roomSettings.inviteOnly'),
}),
[]
[t]
);
};

type JoinRulesSwitcherProps<T extends ExtendedJoinRules[]> = {
icons: JoinRuleIcons;
Expand All @@ -68,6 +71,7 @@ export function JoinRulesSwitcher<T extends ExtendedJoinRules[]>({
disabled,
changing,
}: JoinRulesSwitcherProps<T>) {
const { t } = useTranslation();
const [cords, setCords] = useState<RectCords>();

const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
Expand Down Expand Up @@ -138,7 +142,7 @@ export function JoinRulesSwitcher<T extends ExtendedJoinRules[]>({
onClick={handleOpenMenu}
disabled={disabled}
>
<Text size="B300">{labels[value] ?? 'Unsupported'}</Text>
<Text size="B300">{labels[value] ?? t('roomSettings.unsupported')}</Text>
</Button>
</PopOut>
);
Expand Down
Loading
Loading