i18n: localize hardcoded UI strings across 10 components
Wraps the hardcoded strings flagged in LOTUS_BUGS.md (Localization rows) in t() via react-i18next, and adds the keys to public/locales/en.json under the existing Organisms.* namespace. de.json intentionally left to fall back to en for now (fallbackLng: 'en') rather than fabricate translations. Files: CreateRoomTypeSelector, ImageViewer, MsgTypeRenderers (MLocation), Reply (ThreadIndicator), ImageContent, DeviceVerification (5 subcomponents), UrlPreviewCard (DiscordCard), InviteUserPrompt, UploadBoard, PasswordStage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,46 @@
|
|||||||
"Organisms": {
|
"Organisms": {
|
||||||
"RoomCommon": {
|
"RoomCommon": {
|
||||||
"changed_room_name": " changed room name"
|
"changed_room_name": " changed room name"
|
||||||
|
},
|
||||||
|
"CreateRoom": {
|
||||||
|
"chat_room": "Chat Room",
|
||||||
|
"chat_room_desc": "Messages, photos, and videos.",
|
||||||
|
"voice_room": "Voice Room",
|
||||||
|
"voice_room_desc": "Live audio and video conversations."
|
||||||
|
},
|
||||||
|
"ImageViewer": {
|
||||||
|
"download": "Download"
|
||||||
|
},
|
||||||
|
"Message": {
|
||||||
|
"open_location": "Open Location",
|
||||||
|
"thread": "Thread"
|
||||||
|
},
|
||||||
|
"ImageContent": {
|
||||||
|
"view": "View",
|
||||||
|
"spoiler": "Spoiler",
|
||||||
|
"retry": "Retry"
|
||||||
|
},
|
||||||
|
"DeviceVerification": {
|
||||||
|
"close": "Close",
|
||||||
|
"accept": "Accept",
|
||||||
|
"they_match": "They Match",
|
||||||
|
"okay": "Okay"
|
||||||
|
},
|
||||||
|
"UrlPreview": {
|
||||||
|
"join_server": "Join Server"
|
||||||
|
},
|
||||||
|
"InviteUser": {
|
||||||
|
"invite": "Invite"
|
||||||
|
},
|
||||||
|
"UploadBoard": {
|
||||||
|
"files": "Files",
|
||||||
|
"send": "Send",
|
||||||
|
"upload_failed": "Upload Failed"
|
||||||
|
},
|
||||||
|
"PasswordStage": {
|
||||||
|
"account_password": "Account Password",
|
||||||
|
"password": "Password",
|
||||||
|
"invalid_password": "Invalid Password!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
Verifier,
|
Verifier,
|
||||||
} from 'matrix-js-sdk/lib/crypto-api';
|
} from 'matrix-js-sdk/lib/crypto-api';
|
||||||
import React, { CSSProperties, useCallback, useEffect, useState } from 'react';
|
import React, { CSSProperties, useCallback, useEffect, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { VerificationMethod } from 'matrix-js-sdk/lib/types';
|
import { VerificationMethod } from 'matrix-js-sdk/lib/types';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@@ -51,11 +52,12 @@ function WaitingMessage({ message }: WaitingMessageProps) {
|
|||||||
|
|
||||||
type VerificationUnexpectedProps = { message: string; onClose: () => void };
|
type VerificationUnexpectedProps = { message: string; onClose: () => void };
|
||||||
function VerificationUnexpected({ message, onClose }: VerificationUnexpectedProps) {
|
function VerificationUnexpected({ message, onClose }: VerificationUnexpectedProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Box direction="Column" gap="400">
|
<Box direction="Column" gap="400">
|
||||||
<Text>{message}</Text>
|
<Text>{message}</Text>
|
||||||
<Button variant="Secondary" fill="Soft" onClick={onClose}>
|
<Button variant="Secondary" fill="Soft" onClick={onClose}>
|
||||||
<Text size="B400">Close</Text>
|
<Text size="B400">{t('Organisms.DeviceVerification.close')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -74,6 +76,7 @@ type VerificationAcceptProps = {
|
|||||||
onAccept: () => Promise<void>;
|
onAccept: () => Promise<void>;
|
||||||
};
|
};
|
||||||
function VerificationAccept({ onAccept }: VerificationAcceptProps) {
|
function VerificationAccept({ onAccept }: VerificationAcceptProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [acceptState, accept] = useAsyncCallback(onAccept);
|
const [acceptState, accept] = useAsyncCallback(onAccept);
|
||||||
|
|
||||||
const accepting = acceptState.status === AsyncStatus.Loading;
|
const accepting = acceptState.status === AsyncStatus.Loading;
|
||||||
@@ -87,7 +90,7 @@ function VerificationAccept({ onAccept }: VerificationAcceptProps) {
|
|||||||
before={accepting && <Spinner size="100" variant="Primary" fill="Solid" />}
|
before={accepting && <Spinner size="100" variant="Primary" fill="Solid" />}
|
||||||
disabled={accepting}
|
disabled={accepting}
|
||||||
>
|
>
|
||||||
<Text size="B400">Accept</Text>
|
<Text size="B400">{t('Organisms.DeviceVerification.accept')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -118,6 +121,7 @@ function AutoVerificationStart({ onStart }: VerificationStartProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function CompareEmoji({ sasData }: { sasData: ShowSasCallbacks }) {
|
function CompareEmoji({ sasData }: { sasData: ShowSasCallbacks }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [confirmState, confirm] = useAsyncCallback(useCallback(() => sasData.confirm(), [sasData]));
|
const [confirmState, confirm] = useAsyncCallback(useCallback(() => sasData.confirm(), [sasData]));
|
||||||
|
|
||||||
const confirming =
|
const confirming =
|
||||||
@@ -157,7 +161,7 @@ function CompareEmoji({ sasData }: { sasData: ShowSasCallbacks }) {
|
|||||||
disabled={confirming}
|
disabled={confirming}
|
||||||
before={confirming && <Spinner size="100" variant="Primary" />}
|
before={confirming && <Spinner size="100" variant="Primary" />}
|
||||||
>
|
>
|
||||||
<Text size="B400">They Match</Text>
|
<Text size="B400">{t('Organisms.DeviceVerification.they_match')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="Primary"
|
variant="Primary"
|
||||||
@@ -201,13 +205,14 @@ type VerificationDoneProps = {
|
|||||||
onExit: () => void;
|
onExit: () => void;
|
||||||
};
|
};
|
||||||
function VerificationDone({ onExit }: VerificationDoneProps) {
|
function VerificationDone({ onExit }: VerificationDoneProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Box direction="Column" gap="400">
|
<Box direction="Column" gap="400">
|
||||||
<div>
|
<div>
|
||||||
<Text>Your device is verified.</Text>
|
<Text>Your device is verified.</Text>
|
||||||
</div>
|
</div>
|
||||||
<Button variant="Primary" fill="Solid" onClick={onExit}>
|
<Button variant="Primary" fill="Solid" onClick={onExit}>
|
||||||
<Text size="B400">Okay</Text>
|
<Text size="B400">{t('Organisms.DeviceVerification.okay')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -217,11 +222,12 @@ type VerificationCanceledProps = {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
};
|
};
|
||||||
function VerificationCanceled({ onClose }: VerificationCanceledProps) {
|
function VerificationCanceled({ onClose }: VerificationCanceledProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Box direction="Column" gap="400">
|
<Box direction="Column" gap="400">
|
||||||
<Text>Verification has been canceled.</Text>
|
<Text>Verification has been canceled.</Text>
|
||||||
<Button variant="Secondary" fill="Soft" onClick={onClose}>
|
<Button variant="Secondary" fill="Soft" onClick={onClose}>
|
||||||
<Text size="B400">Close</Text>
|
<Text size="B400">{t('Organisms.DeviceVerification.close')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Box, Text, Icon, Icons, config, IconSrc } from 'folds';
|
import { Box, Text, Icon, Icons, config, IconSrc } from 'folds';
|
||||||
import { SequenceCard } from '../sequence-card';
|
import { SequenceCard } from '../sequence-card';
|
||||||
import { SettingTile } from '../setting-tile';
|
import { SettingTile } from '../setting-tile';
|
||||||
@@ -17,6 +18,7 @@ export function CreateRoomTypeSelector({
|
|||||||
disabled,
|
disabled,
|
||||||
getIcon,
|
getIcon,
|
||||||
}: CreateRoomTypeSelectorProps) {
|
}: CreateRoomTypeSelectorProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Box shrink="No" direction="Column" gap="100">
|
<Box shrink="No" direction="Column" gap="100">
|
||||||
<SequenceCard
|
<SequenceCard
|
||||||
@@ -36,10 +38,10 @@ export function CreateRoomTypeSelector({
|
|||||||
>
|
>
|
||||||
<Box gap="200" alignItems="Baseline">
|
<Box gap="200" alignItems="Baseline">
|
||||||
<Text size="H6" style={{ flexShrink: 0 }}>
|
<Text size="H6" style={{ flexShrink: 0 }}>
|
||||||
Chat Room
|
{t('Organisms.CreateRoom.chat_room')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="T300" priority="300" truncate>
|
<Text size="T300" priority="300" truncate>
|
||||||
- Messages, photos, and videos.
|
- {t('Organisms.CreateRoom.chat_room_desc')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
</SettingTile>
|
</SettingTile>
|
||||||
@@ -61,10 +63,10 @@ export function CreateRoomTypeSelector({
|
|||||||
>
|
>
|
||||||
<Box gap="200" alignItems="Baseline">
|
<Box gap="200" alignItems="Baseline">
|
||||||
<Text size="H6" style={{ flexShrink: 0 }}>
|
<Text size="H6" style={{ flexShrink: 0 }}>
|
||||||
Voice Room
|
{t('Organisms.CreateRoom.voice_room')}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="T300" priority="300" truncate>
|
<Text size="T300" priority="300" truncate>
|
||||||
- Live audio and video conversations.
|
- {t('Organisms.CreateRoom.voice_room_desc')}
|
||||||
</Text>
|
</Text>
|
||||||
<BetaNoticeBadge />
|
<BetaNoticeBadge />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import FileSaver from 'file-saver';
|
import FileSaver from 'file-saver';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds';
|
import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds';
|
||||||
@@ -15,6 +16,7 @@ export type ImageViewerProps = {
|
|||||||
|
|
||||||
export const ImageViewer = as<'div', ImageViewerProps>(
|
export const ImageViewer = as<'div', ImageViewerProps>(
|
||||||
({ className, alt, src, requestClose, ...props }, ref) => {
|
({ className, alt, src, requestClose, ...props }, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { zoom, zoomIn, zoomOut, setZoom } = useZoom(0.2);
|
const { zoom, zoomIn, zoomOut, setZoom } = useZoom(0.2);
|
||||||
const { pan, cursor, onMouseDown } = usePan(zoom !== 1);
|
const { pan, cursor, onMouseDown } = usePan(zoom !== 1);
|
||||||
|
|
||||||
@@ -69,7 +71,7 @@ export const ImageViewer = as<'div', ImageViewerProps>(
|
|||||||
radii="300"
|
radii="300"
|
||||||
before={<Icon size="50" src={Icons.Download} />}
|
before={<Icon size="50" src={Icons.Download} />}
|
||||||
>
|
>
|
||||||
<Text size="B300">Download</Text>
|
<Text size="B300">{t('Organisms.ImageViewer.download')}</Text>
|
||||||
</Chip>
|
</Chip>
|
||||||
</Box>
|
</Box>
|
||||||
</Header>
|
</Header>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Overlay,
|
Overlay,
|
||||||
OverlayBackdrop,
|
OverlayBackdrop,
|
||||||
@@ -66,6 +67,7 @@ type InviteUserProps = {
|
|||||||
requestClose: () => void;
|
requestClose: () => void;
|
||||||
};
|
};
|
||||||
export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
|
export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const mx = useMatrixClient();
|
const mx = useMatrixClient();
|
||||||
const modalStyle = useModalStyle(560);
|
const modalStyle = useModalStyle(560);
|
||||||
const alive = useAlive();
|
const alive = useAlive();
|
||||||
@@ -194,7 +196,7 @@ export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
|
|||||||
>
|
>
|
||||||
<Box grow="Yes">
|
<Box grow="Yes">
|
||||||
<Text size="H4" truncate>
|
<Text size="H4" truncate>
|
||||||
Invite
|
{t('Organisms.InviteUser.invite')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box shrink="No" gap="100" alignItems="Center">
|
<Box shrink="No" gap="100" alignItems="Center">
|
||||||
@@ -351,7 +353,7 @@ export function InviteUserPrompt({ room, requestClose }: InviteUserProps) {
|
|||||||
disabled={!validUserId || inviting}
|
disabled={!validUserId || inviting}
|
||||||
before={inviting && <Spinner size="200" variant="Primary" fill="Solid" />}
|
before={inviting && <Spinner size="200" variant="Primary" fill="Solid" />}
|
||||||
>
|
>
|
||||||
<Text size="B400">Invite</Text>
|
<Text size="B400">{t('Organisms.InviteUser.invite')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
|
import React, { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Box, Button, config, Icon, Icons, Text, color, toRem } from 'folds';
|
import { Box, Button, config, Icon, Icons, Text, color, toRem } from 'folds';
|
||||||
import { IContent } from 'matrix-js-sdk';
|
import { IContent } from 'matrix-js-sdk';
|
||||||
import { JUMBO_EMOJI_REG, URL_REG } from '../../utils/regex';
|
import { JUMBO_EMOJI_REG, URL_REG } from '../../utils/regex';
|
||||||
@@ -507,6 +508,7 @@ type MLocationProps = {
|
|||||||
content: IContent;
|
content: IContent;
|
||||||
};
|
};
|
||||||
export function MLocation({ content }: MLocationProps) {
|
export function MLocation({ content }: MLocationProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const geoUri = content.geo_uri;
|
const geoUri = content.geo_uri;
|
||||||
if (typeof geoUri !== 'string') return <BrokenContent />;
|
if (typeof geoUri !== 'string') return <BrokenContent />;
|
||||||
const location = parseGeoUri(geoUri);
|
const location = parseGeoUri(geoUri);
|
||||||
@@ -549,7 +551,7 @@ export function MLocation({ content }: MLocationProps) {
|
|||||||
radii="300"
|
radii="300"
|
||||||
before={<Icon src={Icons.External} size="50" />}
|
before={<Icon src={Icons.External} size="50" />}
|
||||||
>
|
>
|
||||||
<Text size="B300">Open Location</Text>
|
<Text size="B300">{t('Organisms.Message.open_location')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Box, Icon, Icons, Text, as, color, toRem } from 'folds';
|
import { Box, Icon, Icons, Text, as, color, toRem } from 'folds';
|
||||||
import { EventTimelineSet, Room } from 'matrix-js-sdk';
|
import { EventTimelineSet, Room } from 'matrix-js-sdk';
|
||||||
import React, { MouseEventHandler, ReactNode, useCallback, useMemo } from 'react';
|
import React, { MouseEventHandler, ReactNode, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { getMemberDisplayName, trimReplyFromBody } from '../../utils/room';
|
import { getMemberDisplayName, trimReplyFromBody } from '../../utils/room';
|
||||||
import { getMxIdLocalPart } from '../../utils/matrix';
|
import { getMxIdLocalPart } from '../../utils/matrix';
|
||||||
@@ -37,7 +38,9 @@ export const ReplyLayout = as<'div', ReplyLayoutProps>(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const ThreadIndicator = as<'div'>(({ ...props }, ref) => (
|
export const ThreadIndicator = as<'div'>(({ ...props }, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
<Box
|
<Box
|
||||||
shrink="No"
|
shrink="No"
|
||||||
className={css.ThreadIndicator}
|
className={css.ThreadIndicator}
|
||||||
@@ -47,9 +50,10 @@ export const ThreadIndicator = as<'div'>(({ ...props }, ref) => (
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
<Icon size="50" src={Icons.Thread} />
|
<Icon size="50" src={Icons.Thread} />
|
||||||
<Text size="L400">Thread</Text>
|
<Text size="L400">{t('Organisms.Message.thread')}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
));
|
);
|
||||||
|
});
|
||||||
|
|
||||||
type ReplyProps = {
|
type ReplyProps = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
Box,
|
Box,
|
||||||
@@ -81,6 +82,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
|||||||
const useAuthentication = useMediaAuthentication();
|
const useAuthentication = useMediaAuthentication();
|
||||||
const blurHash = validBlurHash(info?.[MATRIX_BLUR_HASH_PROPERTY_NAME]);
|
const blurHash = validBlurHash(info?.[MATRIX_BLUR_HASH_PROPERTY_NAME]);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
const [load, setLoad] = useState(false);
|
const [load, setLoad] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [viewer, setViewer] = useState(false);
|
const [viewer, setViewer] = useState(false);
|
||||||
@@ -168,7 +170,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
|||||||
onClick={loadSrc}
|
onClick={loadSrc}
|
||||||
before={<Icon size="Inherit" src={Icons.Photo} filled />}
|
before={<Icon size="Inherit" src={Icons.Photo} filled />}
|
||||||
>
|
>
|
||||||
<Text size="B300">View</Text>
|
<Text size="B300">{t('Organisms.ImageContent.view')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -212,7 +214,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text size="B300">Spoiler</Text>
|
<Text size="B300">{t('Organisms.ImageContent.spoiler')}</Text>
|
||||||
</Chip>
|
</Chip>
|
||||||
)}
|
)}
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
@@ -247,7 +249,7 @@ export const ImageContent = as<'div', ImageContentProps>(
|
|||||||
onClick={handleRetry}
|
onClick={handleRetry}
|
||||||
before={<Icon size="Inherit" src={Icons.Warning} filled />}
|
before={<Icon size="Inherit" src={Icons.Warning} filled />}
|
||||||
>
|
>
|
||||||
<Text size="B300">Retry</Text>
|
<Text size="B300">{t('Organisms.ImageContent.retry')}</Text>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Box, Button, color, config, Dialog, Header, Icon, IconButton, Icons, Text } from 'folds';
|
import { Box, Button, color, config, Dialog, Header, Icon, IconButton, Icons, Text } from 'folds';
|
||||||
import React, { FormEventHandler } from 'react';
|
import React, { FormEventHandler } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { AuthType } from 'matrix-js-sdk';
|
import { AuthType } from 'matrix-js-sdk';
|
||||||
import { StageComponentProps } from './types';
|
import { StageComponentProps } from './types';
|
||||||
import { ErrorCode } from '../../cs-errorcode';
|
import { ErrorCode } from '../../cs-errorcode';
|
||||||
@@ -13,6 +14,7 @@ export function PasswordStage({
|
|||||||
}: StageComponentProps & {
|
}: StageComponentProps & {
|
||||||
userId: string;
|
userId: string;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { errorCode, error, session } = stageData;
|
const { errorCode, error, session } = stageData;
|
||||||
|
|
||||||
const handleFormSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
|
const handleFormSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
|
||||||
@@ -44,7 +46,7 @@ export function PasswordStage({
|
|||||||
>
|
>
|
||||||
<Box grow="Yes">
|
<Box grow="Yes">
|
||||||
<Text as="h2" size="H4">
|
<Text as="h2" size="H4">
|
||||||
Account Password
|
{t('Organisms.PasswordStage.account_password')}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
|
<IconButton size="300" onClick={onCancel} radii="300" aria-label="Cancel">
|
||||||
@@ -64,7 +66,7 @@ export function PasswordStage({
|
|||||||
password.
|
password.
|
||||||
</Text>
|
</Text>
|
||||||
<Box direction="Column" gap="100">
|
<Box direction="Column" gap="100">
|
||||||
<Text size="L400">Password</Text>
|
<Text size="L400">{t('Organisms.PasswordStage.password')}</Text>
|
||||||
<PasswordInput size="400" name="passwordInput" outlined autoFocus required />
|
<PasswordInput size="400" name="passwordInput" outlined autoFocus required />
|
||||||
{errorCode && (
|
{errorCode && (
|
||||||
<Box alignItems="Center" gap="100" style={{ color: color.Critical.Main }}>
|
<Box alignItems="Center" gap="100" style={{ color: color.Critical.Main }}>
|
||||||
@@ -72,7 +74,7 @@ export function PasswordStage({
|
|||||||
<Text size="T200">
|
<Text size="T200">
|
||||||
<b>
|
<b>
|
||||||
{errorCode === ErrorCode.M_FORBIDDEN
|
{errorCode === ErrorCode.M_FORBIDDEN
|
||||||
? 'Invalid Password!'
|
? t('Organisms.PasswordStage.invalid_password')
|
||||||
: `${errorCode}: ${error}`}
|
: `${errorCode}: ${error}`}
|
||||||
</b>
|
</b>
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { MutableRefObject, ReactNode, useImperativeHandle, useRef } from 'react';
|
import React, { MutableRefObject, ReactNode, useImperativeHandle, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Badge, Box, Chip, Header, Icon, Icons, Spinner, Text, as, percent } from 'folds';
|
import { Badge, Box, Chip, Header, Icon, Icons, Spinner, Text, as, percent } from 'folds';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
@@ -43,6 +44,7 @@ export function UploadBoardHeader({
|
|||||||
onSend,
|
onSend,
|
||||||
imperativeHandlerRef,
|
imperativeHandlerRef,
|
||||||
}: UploadBoardHeaderProps) {
|
}: UploadBoardHeaderProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const sendingRef = useRef(false);
|
const sendingRef = useRef(false);
|
||||||
const uploads = useAtomValue(uploadFamilyObserverAtom);
|
const uploads = useAtomValue(uploadFamilyObserverAtom);
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ export function UploadBoardHeader({
|
|||||||
gap="100"
|
gap="100"
|
||||||
>
|
>
|
||||||
<Icon src={open ? Icons.ChevronTop : Icons.ChevronRight} size="50" />
|
<Icon src={open ? Icons.ChevronTop : Icons.ChevronRight} size="50" />
|
||||||
<Text size="H6">Files</Text>
|
<Text size="H6">{t('Organisms.UploadBoard.files')}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box className={css.UploadBoardHeaderContent} alignItems="Center" gap="100">
|
<Box className={css.UploadBoardHeaderContent} alignItems="Center" gap="100">
|
||||||
{isSuccess && (
|
{isSuccess && (
|
||||||
@@ -100,12 +102,12 @@ export function UploadBoardHeader({
|
|||||||
outlined
|
outlined
|
||||||
after={<Icon src={Icons.Send} size="50" filled />}
|
after={<Icon src={Icons.Send} size="50" filled />}
|
||||||
>
|
>
|
||||||
<Text size="B300">Send</Text>
|
<Text size="B300">{t('Organisms.UploadBoard.send')}</Text>
|
||||||
</Chip>
|
</Chip>
|
||||||
)}
|
)}
|
||||||
{isError && !open && (
|
{isError && !open && (
|
||||||
<Badge variant="Critical" fill="Solid" radii="300">
|
<Badge variant="Critical" fill="Solid" radii="300">
|
||||||
<Text size="L400">Upload Failed</Text>
|
<Text size="L400">{t('Organisms.UploadBoard.upload_failed')}</Text>
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
{!isSuccess && !isError && !open && (
|
{!isSuccess && !isError && !open && (
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { IPreviewUrlResponse } from 'matrix-js-sdk';
|
import { IPreviewUrlResponse } from 'matrix-js-sdk';
|
||||||
import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, as, color, config } from 'folds';
|
import { Box, Icon, IconButton, Icons, Scroll, Spinner, Text, as, color, config } from 'folds';
|
||||||
import { ImageOverlay } from '../ImageOverlay';
|
import { ImageOverlay } from '../ImageOverlay';
|
||||||
@@ -1343,6 +1344,7 @@ function WikipediaCard({ url, prev }: { url: string; prev: IPreviewUrlResponse }
|
|||||||
}
|
}
|
||||||
|
|
||||||
function DiscordCard({ url, prev }: { url: string; prev: IPreviewUrlResponse }) {
|
function DiscordCard({ url, prev }: { url: string; prev: IPreviewUrlResponse }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const title = prev['og:title'] ?? '';
|
const title = prev['og:title'] ?? '';
|
||||||
const description = prev['og:description'] ?? '';
|
const description = prev['og:description'] ?? '';
|
||||||
const iconUrl = (prev['og:image'] as string | undefined) ?? '';
|
const iconUrl = (prev['og:image'] as string | undefined) ?? '';
|
||||||
@@ -1383,7 +1385,9 @@ function DiscordCard({ url, prev }: { url: string; prev: IPreviewUrlResponse })
|
|||||||
priority="300"
|
priority="300"
|
||||||
>
|
>
|
||||||
<SiteBadge label="Discord" colorClass={previewCss.BadgeDiscord} />
|
<SiteBadge label="Discord" colorClass={previewCss.BadgeDiscord} />
|
||||||
<span style={{ marginLeft: '6px', opacity: 0.7, fontSize: '0.85em' }}>Join Server</span>
|
<span style={{ marginLeft: '6px', opacity: 0.7, fontSize: '0.85em' }}>
|
||||||
|
{t('Organisms.UrlPreview.join_server')}
|
||||||
|
</span>
|
||||||
</Text>
|
</Text>
|
||||||
{title && (
|
{title && (
|
||||||
<Text truncate priority="400">
|
<Text truncate priority="400">
|
||||||
|
|||||||
Reference in New Issue
Block a user