Merge branch 'develop' into germain-gg/notifications-labs

This commit is contained in:
RMidhunSuresh 2023-09-06 19:13:10 +05:30
commit 9ceac6eae1
No known key found for this signature in database
148 changed files with 14554 additions and 11595 deletions

View file

@ -1240,10 +1240,10 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
<span>
{isSpace
? _t("Are you sure you want to leave the space '%(spaceName)s'?", {
spaceName: roomToLeave?.name ?? _t("Unnamed Space"),
spaceName: roomToLeave?.name ?? _t("common|unnamed_space"),
})
: _t("Are you sure you want to leave the room '%(roomName)s'?", {
roomName: roomToLeave?.name ?? _t("Unnamed Room"),
roomName: roomToLeave?.name ?? _t("common|unnamed_room"),
})}
{warnings}
</span>

View file

@ -119,7 +119,7 @@ const Tile: React.FC<ITileProps> = ({
room.name ||
room.canonical_alias ||
room.aliases?.[0] ||
(room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room"));
(room.room_type === RoomType.Space ? _t("common|unnamed_space") : _t("common|unnamed_room"));
const [showChildren, toggleShowChildren] = useStateToggle(true);
const [onFocus, isActive, ref] = useRovingTabIndex();

View file

@ -63,13 +63,13 @@ function tooltipText(variant: Icon): string | undefined {
case Icon.Globe:
return _t("This room is public");
case Icon.PresenceOnline:
return _t("Online");
return _t("presence|online");
case Icon.PresenceAway:
return _t("Away");
return _t("presence|away");
case Icon.PresenceOffline:
return _t("common|offline");
return _t("presence|offline");
case Icon.PresenceBusy:
return _t("Busy");
return _t("presence|busy");
}
}

View file

@ -72,7 +72,7 @@ const RoomCallBannerInner: React.FC<RoomCallBannerProps> = ({ roomId, call }) =>
return (
<div className="mx_RoomCallBanner" onClick={onClick}>
<div className="mx_RoomCallBanner_text">
<span className="mx_RoomCallBanner_label">{_t("Video call")}</span>
<span className="mx_RoomCallBanner_label">{_t("voip|video_call")}</span>
<GroupCallDuration groupCall={call.groupCall} />
</div>

View file

@ -33,6 +33,7 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { privateShouldBeEncrypted } from "../../../utils/rooms";
import SettingsStore from "../../../settings/SettingsStore";
import LabelledCheckbox from "../elements/LabelledCheckbox";
interface IProps {
type?: RoomType;
@ -45,15 +46,46 @@ interface IProps {
}
interface IState {
/**
* The selected room join rule.
*/
joinRule: JoinRule;
isPublic: boolean;
/**
* Indicates whether the created room should have public visibility (ie, it should be
* shown in the public room list). Only applicable if `joinRule` == `JoinRule.Knock`.
*/
isPublicKnockRoom: boolean;
/**
* Indicates whether end-to-end encryption is enabled for the room.
*/
isEncrypted: boolean;
/**
* The room name.
*/
name: string;
/**
* The room topic.
*/
topic: string;
/**
* The room alias.
*/
alias: string;
/**
* Indicates whether the details section is open.
*/
detailsOpen: boolean;
/**
* Indicates whether federation is disabled for the room.
*/
noFederate: boolean;
/**
* Indicates whether the room name is valid.
*/
nameIsValid: boolean;
/**
* Indicates whether the user can change encryption settings for the room.
*/
canChangeEncryption: boolean;
}
@ -78,7 +110,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
const cli = MatrixClientPeg.safeGet();
this.state = {
isPublic: this.props.defaultPublic || false,
isPublicKnockRoom: this.props.defaultPublic || false,
isEncrypted: this.props.defaultEncrypted ?? privateShouldBeEncrypted(cli),
joinRule,
name: this.props.defaultName || "",
@ -129,6 +161,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
if (this.state.joinRule === JoinRule.Knock) {
opts.joinRule = JoinRule.Knock;
createOpts.visibility = this.state.isPublicKnockRoom ? Visibility.Public : Visibility.Private;
}
return opts;
@ -215,6 +248,10 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
return result;
};
private onIsPublicKnockRoomChange = (isPublicKnockRoom: boolean): void => {
this.setState({ isPublicKnockRoom });
};
private static validateRoomName = withValidation({
rules: [
{
@ -251,7 +288,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
"Everyone in <SpaceName/> will be able to find and join this room.",
{},
{
SpaceName: () => <b>{this.props.parentSpace?.name ?? _t("Unnamed Space")}</b>,
SpaceName: () => <b>{this.props.parentSpace?.name ?? _t("common|unnamed_space")}</b>,
},
)}
&nbsp;
@ -265,7 +302,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
"Anyone will be able to find and join this room, not just members of <SpaceName/>.",
{},
{
SpaceName: () => <b>{this.props.parentSpace?.name ?? _t("Unnamed Space")}</b>,
SpaceName: () => <b>{this.props.parentSpace?.name ?? _t("common|unnamed_space")}</b>,
},
)}
&nbsp;
@ -298,6 +335,18 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
);
}
let visibilitySection: JSX.Element | undefined;
if (this.state.joinRule === JoinRule.Knock) {
visibilitySection = (
<LabelledCheckbox
className="mx_CreateRoomDialog_labelledCheckbox"
label={_t("Make this room visible in the public room directory.")}
onChange={this.onIsPublicKnockRoomChange}
value={this.state.isPublicKnockRoom}
/>
);
}
let e2eeSection: JSX.Element | undefined;
if (this.state.joinRule !== JoinRule.Public) {
let microcopy: string;
@ -341,11 +390,14 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
let title: string;
if (isVideoRoom) {
title = _t("Create a video room");
title = _t("create_room|title_video_room");
} else if (this.props.parentSpace || this.state.joinRule === JoinRule.Knock) {
title = _t("action|create_a_room");
} else {
title = this.state.joinRule === JoinRule.Public ? _t("Create a public room") : _t("Create a private room");
title =
this.state.joinRule === JoinRule.Public
? _t("create_room|title_public_room")
: _t("create_room|title_private_room");
}
return (
@ -383,6 +435,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
/>
{publicPrivateLabel}
{visibilitySection}
{e2eeSection}
{aliasField}
<details onToggle={this.onDetailsToggled} className="mx_CreateRoomDialog_details">
@ -401,7 +454,9 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
</div>
</form>
<DialogButtons
primaryButton={isVideoRoom ? _t("Create video room") : _t("Create room")}
primaryButton={
isVideoRoom ? _t("create_room|action_create_video_room") : _t("create_room|action_create_room")
}
onPrimaryButtonClick={this.onOk}
onCancel={this.onCancel}
/>

View file

@ -41,8 +41,8 @@ enum Category {
}
const categoryLabels: Record<Category, TranslationKey> = {
[Category.Room]: _td("common|room"),
[Category.Other]: _td("Other"),
[Category.Room]: _td("devtools|category_room"),
[Category.Other]: _td("devtools|category_other"),
};
export type Tool = React.FC<IDevtoolsProps> | ((props: IDevtoolsProps) => JSX.Element);

View file

@ -1338,10 +1338,10 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
const isSpace = room?.isSpaceRoom();
title = isSpace
? _t("Invite to %(spaceName)s", {
spaceName: room?.name || _t("Unnamed Space"),
spaceName: room?.name || _t("common|unnamed_space"),
})
: _t("Invite to %(roomName)s", {
roomName: room?.name || _t("Unnamed Room"),
roomName: room?.name || _t("common|unnamed_room"),
});
let helpTextUntranslated;

View file

@ -84,7 +84,7 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
return (
<BaseDialog
title={_t("Settings - %(spaceName)s", { spaceName: space.name || _t("Unnamed Space") })}
title={_t("Settings - %(spaceName)s", { spaceName: space.name || _t("common|unnamed_space") })}
className="mx_SpaceSettingsDialog"
contentId="mx_SpaceSettingsDialog"
onFinished={onFinished}

View file

@ -31,7 +31,7 @@ export default class DialPadBackspaceButton extends React.PureComponent<IProps>
<AccessibleButton
className="mx_DialPadBackspaceButton"
onClick={this.props.onBackspacePress}
aria-label={_t("Backspace")}
aria-label={_t("keyboard|backspace")}
/>
</div>
);

View file

@ -27,22 +27,38 @@ interface IProps extends HTMLAttributes<HTMLSpanElement> {
tooltipLabel?: string;
tooltipShortcut?: string;
children?: ReactNode;
viewUserOnClick?: boolean;
}
const FacePile: FC<IProps> = ({ members, size, overflow, tooltipLabel, tooltipShortcut, children, ...props }) => {
const FacePile: FC<IProps> = ({
members,
size,
overflow,
tooltipLabel,
tooltipShortcut,
children,
viewUserOnClick = true,
...props
}) => {
const faces = members.map(
tooltipLabel
? (m) => <MemberAvatar key={m.userId} member={m} size={size} hideTitle />
: (m) => (
<Tooltip key={m.userId} label={m.name} shortcut={tooltipShortcut}>
<MemberAvatar member={m} size={size} viewUserOnClick={!props.onClick} hideTitle />
<MemberAvatar
member={m}
size={size}
viewUserOnClick={!props.onClick && viewUserOnClick}
hideTitle
/>
</Tooltip>
),
);
const pileContents = (
<>
{overflow ? <span className="mx_FacePile_more" /> : null}
{/* XXX: The margin-left is a workaround for Compound's styling excluding this element and being overly specific */}
{overflow ? <span className="mx_FacePile_more" style={{ marginLeft: `calc(${size} * -0.2)` }} /> : null}
{faces}
</>
);

View file

@ -15,6 +15,7 @@ limitations under the License.
*/
import React from "react";
import classnames from "classnames";
import StyledCheckbox from "./StyledCheckbox";
@ -29,11 +30,13 @@ interface IProps {
disabled?: boolean;
// The function to call when the value changes
onChange(checked: boolean): void;
// Optional additional CSS class to apply to the label
className?: string;
}
const LabelledCheckbox: React.FC<IProps> = ({ value, label, byline, disabled, onChange }) => {
const LabelledCheckbox: React.FC<IProps> = ({ value, label, byline, disabled, onChange, className }) => {
return (
<label className="mx_LabelledCheckbox">
<label className={classnames("mx_LabelledCheckbox", className)}>
<StyledCheckbox disabled={disabled} checked={value} onChange={(e) => onChange(e.target.checked)} />
<div className="mx_LabelledCheckbox_labels">
<span className="mx_LabelledCheckbox_label">{label}</span>

View file

@ -71,7 +71,7 @@ const ActiveCallEvent = forwardRef<any, ActiveCallEventProps>(
</span>
<LiveContentSummary
type={LiveContentType.Video}
text={_t("Video call")}
text={_t("voip|video_call")}
active={false}
participantCount={participatingMembers.length}
/>

View file

@ -117,7 +117,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
<AccessibleTooltipButton
className={silenceClass}
onClick={this.props.callEventGrouper.toggleSilenced}
title={this.state.silenced ? _t("Sound on") : _t("Silence call")}
title={this.state.silenced ? _t("voip|unsilence") : _t("voip|silence")}
/>
);
}
@ -185,7 +185,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
// Also the correct hangup code as of VoIP v1 (with underscore)
// Also, if we don't have a reason
const duration = this.props.callEventGrouper.duration!;
let text = _t("Call ended");
let text = _t("timeline|m.call.hangup|dm");
if (duration) {
text += " • " + formatPreciseDuration(duration);
}
@ -268,7 +268,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
const event = this.props.mxEvent;
const sender = event.sender ? event.sender.name : event.getSender();
const isVoice = this.props.callEventGrouper.isVoice;
const callType = isVoice ? _t("Voice call") : _t("Video call");
const callType = isVoice ? _t("voip|voice_call") : _t("voip|video_call");
const callState = this.state.callState;
const hangupReason = this.props.callEventGrouper.hangupReason;
const content = this.renderContent();

View file

@ -25,13 +25,13 @@ import { IBodyProps } from "./IBodyProps";
const RedactedBody = React.forwardRef<any, IBodyProps>(({ mxEvent }, ref) => {
const cli: MatrixClient = useContext(MatrixClientContext);
let text = _t("Message deleted");
let text = _t("timeline|self_redaction");
const unsigned = mxEvent.getUnsigned();
const redactedBecauseUserId = unsigned && unsigned.redacted_because && unsigned.redacted_because.sender;
if (redactedBecauseUserId && redactedBecauseUserId !== mxEvent.getSender()) {
const room = cli.getRoom(mxEvent.getRoomId());
const sender = room && room.getMember(redactedBecauseUserId);
text = _t("Message deleted by %(name)s", { name: sender ? sender.name : redactedBecauseUserId });
text = _t("timeline|redaction", { name: sender ? sender.name : redactedBecauseUserId });
}
const showTwelveHour = SettingsStore.getValue("showTwelveHourTimestamps");

View file

@ -343,14 +343,16 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, on
{_t("common|people")}
<span className="mx_BaseCard_Button_sublabel">{memberCount}</span>
</Button>
<Button
className="mx_RoomSummaryCard_icon_search"
onClick={() => {
onSearchClick?.();
}}
>
{_t("Search")}
</Button>
{SettingsStore.getValue("feature_new_room_decoration_ui") && (
<Button
className="mx_RoomSummaryCard_icon_search"
onClick={() => {
onSearchClick?.();
}}
>
{_t("Search")}
</Button>
)}
{!isVideoRoom && (
<Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
{_t("Files")}

View file

@ -112,8 +112,8 @@ const VoiceCallButton: FC<VoiceCallButtonProps> = ({ room, busy, setBusy, behavi
<AccessibleTooltipButton
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_voiceCallButton"
onClick={onClick}
title={_t("Voice call")}
tooltip={tooltip ?? _t("Voice call")}
title={_t("voip|voice_call")}
tooltip={tooltip ?? _t("voip|voice_call")}
alignment={Alignment.Bottom}
disabled={disabled || busy}
/>
@ -228,8 +228,8 @@ const VideoCallButton: FC<VideoCallButtonProps> = ({ room, busy, setBusy, behavi
inputRef={buttonRef}
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_videoCallButton"
onClick={onClick}
title={_t("Video call")}
tooltip={tooltip ?? _t("Video call")}
title={_t("voip|video_call")}
tooltip={tooltip ?? _t("voip|video_call")}
alignment={Alignment.Bottom}
disabled={disabled || busy}
/>

View file

@ -78,7 +78,7 @@ function SendButton(props: ISendButtonProps): JSX.Element {
<AccessibleTooltipButton
className="mx_MessageComposer_sendMessage"
onClick={props.onClick}
title={props.title ?? _t("Send message")}
title={props.title ?? _t("composer|send_button_title")}
data-testid="sendmessagebtn"
/>
);
@ -303,19 +303,19 @@ export class MessageComposer extends React.Component<IProps, IState> {
if (this.props.replyToEvent) {
const replyingToThread = this.props.relation?.rel_type === THREAD_RELATION_TYPE.name;
if (replyingToThread && this.props.e2eStatus) {
return _t("Reply to encrypted thread…");
return _t("composer|placeholder_thread_encrypted");
} else if (replyingToThread) {
return _t("Reply to thread…");
return _t("composer|placeholder_thread");
} else if (this.props.e2eStatus) {
return _t("Send an encrypted reply…");
return _t("composer|placeholder_reply_encrypted");
} else {
return _t("Send a reply…");
return _t("composer|placeholder_reply");
}
} else {
if (this.props.e2eStatus) {
return _t("Send an encrypted message…");
return _t("composer|placeholder_encrypted");
} else {
return _t("Send a message…");
return _t("composer|placeholder");
}
}
};

View file

@ -18,6 +18,7 @@ import React from "react";
import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
import { _t } from "../../../languageHandler";
import { formatDuration } from "../../../DateUtils";
const BUSY_PRESENCE_NAME = new UnstableValue("busy", "org.matrix.msc3026.busy");
@ -37,47 +38,23 @@ export default class PresenceLabel extends React.Component<IProps> {
activeAgo: -1,
};
// Return duration as a string using appropriate time units
// XXX: This would be better handled using a culture-aware library, but we don't use one yet.
private getDuration(time: number): string | undefined {
if (!time) return;
const t = Math.round(time / 1000);
const s = t % 60;
const m = Math.round(t / 60) % 60;
const h = Math.round(t / (60 * 60)) % 24;
const d = Math.round(t / (60 * 60 * 24));
if (t < 60) {
if (t < 0) {
return _t("%(duration)ss", { duration: 0 });
}
return _t("%(duration)ss", { duration: s });
}
if (t < 60 * 60) {
return _t("%(duration)sm", { duration: m });
}
if (t < 24 * 60 * 60) {
return _t("%(duration)sh", { duration: h });
}
return _t("%(duration)sd", { duration: d });
}
private getPrettyPresence(presence?: string, activeAgo?: number, currentlyActive?: boolean): string {
// for busy presence, we ignore the 'currentlyActive' flag: they're busy whether
// they're active or not. It can be set while the user is active in which case
// the 'active ago' ends up being 0.
if (presence && BUSY_PRESENCE_NAME.matches(presence)) return _t("Busy");
if (presence && BUSY_PRESENCE_NAME.matches(presence)) return _t("presence|busy");
if (!currentlyActive && activeAgo !== undefined && activeAgo > 0) {
const duration = this.getDuration(activeAgo);
if (presence === "online") return _t("Online for %(duration)s", { duration: duration });
if (presence === "unavailable") return _t("Idle for %(duration)s", { duration: duration }); // XXX: is this actually right?
if (presence === "offline") return _t("Offline for %(duration)s", { duration: duration });
return _t("Unknown for %(duration)s", { duration: duration });
const duration = formatDuration(activeAgo);
if (presence === "online") return _t("presence|online_for", { duration: duration });
if (presence === "unavailable") return _t("presence|idle_for", { duration: duration }); // XXX: is this actually right?
if (presence === "offline") return _t("presence|offline_for", { duration: duration });
return _t("presence|unknown_for", { duration: duration });
} else {
if (presence === "online") return _t("Online");
if (presence === "unavailable") return _t("Idle"); // XXX: is this actually right?
if (presence === "offline") return _t("common|offline");
return _t("Unknown");
if (presence === "online") return _t("presence|online");
if (presence === "unavailable") return _t("presence|idle"); // XXX: is this actually right?
if (presence === "offline") return _t("presence|offline");
return _t("presence|unknown");
}
}

View file

@ -177,44 +177,56 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
</Box>
<Flex as="nav" align="center" gap="var(--cpd-space-2x)">
{!useElementCallExclusively && (
<IconButton
disabled={!!voiceCallDisabledReason}
title={!voiceCallDisabledReason ? _t("Voice call") : voiceCallDisabledReason!}
onClick={() => {
placeCall(room, CallType.Voice, voiceCallType);
}}
>
<VoiceCallIcon />
</IconButton>
<Tooltip label={!voiceCallDisabledReason ? _t("voip|voice_call") : voiceCallDisabledReason!}>
<IconButton
disabled={!!voiceCallDisabledReason}
title={!voiceCallDisabledReason ? _t("voip|voice_call") : voiceCallDisabledReason!}
onClick={(evt) => {
evt.stopPropagation();
placeCall(room, CallType.Voice, voiceCallType);
}}
>
<VoiceCallIcon />
</IconButton>
</Tooltip>
)}
<IconButton
disabled={!!videoCallDisabledReason}
title={!videoCallDisabledReason ? _t("Video call") : videoCallDisabledReason!}
onClick={() => {
placeCall(room, CallType.Video, videoCallType);
}}
>
<VideoCallIcon />
</IconButton>
<IconButton
indicator={notificationColorToIndicator(threadNotifications)}
onClick={() => {
showOrHidePanel(RightPanelPhases.ThreadPanel);
}}
title={_t("common|threads")}
>
<ThreadsIcon />
</IconButton>
{notificationsEnabled && (
<Tooltip label={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}>
<IconButton
indicator={notificationColorToIndicator(globalNotificationState.color)}
onClick={() => {
showOrHidePanel(RightPanelPhases.NotificationPanel);
disabled={!!videoCallDisabledReason}
title={!videoCallDisabledReason ? _t("voip|video_call") : videoCallDisabledReason!}
onClick={(evt) => {
evt.stopPropagation();
placeCall(room, CallType.Video, videoCallType);
}}
title={_t("Notifications")}
>
<NotificationsIcon />
<VideoCallIcon />
</IconButton>
</Tooltip>
<Tooltip label={_t("common|threads")}>
<IconButton
indicator={notificationColorToIndicator(threadNotifications)}
onClick={(evt) => {
evt.stopPropagation();
showOrHidePanel(RightPanelPhases.ThreadPanel);
}}
title={_t("common|threads")}
>
<ThreadsIcon />
</IconButton>
</Tooltip>
{notificationsEnabled && (
<Tooltip label={_t("Notifications")}>
<IconButton
indicator={notificationColorToIndicator(globalNotificationState.color)}
onClick={(evt) => {
evt.stopPropagation();
showOrHidePanel(RightPanelPhases.NotificationPanel);
}}
title={_t("Notifications")}
>
<NotificationsIcon />
</IconButton>
</Tooltip>
)}
</Flex>
{!isDirectMessage && (
@ -233,6 +245,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
members={members.slice(0, 3)}
size="20px"
overflow={false}
viewUserOnClick={false}
>
{formatCount(memberCount)}
</FacePile>

View file

@ -33,7 +33,7 @@ export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
const [disabled, setDisabled] = useState(false);
const knockMembers = useTypedEventEmitterState(
room,
RoomStateEvent.Members,
RoomStateEvent.Update,
useCallback(() => room.getMembersWithMembership("knock"), [room]),
);
const knockMembersCount = knockMembers.length;

View file

@ -419,7 +419,7 @@ const TAG_AESTHETICS: TagAestheticsMap = {
defaultHidden: false,
},
[DefaultTagID.ServerNotice]: {
sectionLabel: _td("System Alerts"),
sectionLabel: _td("common|system_alerts"),
isInvite: false,
defaultHidden: false,
},

View file

@ -261,7 +261,7 @@ export default class Stickerpicker extends React.PureComponent<IProps, IState> {
// Load stickerpack content
if (!!stickerpickerWidget?.content?.url) {
// Set default name
stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("Stickerpack");
stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("common|stickerpack");
// FIXME: could this use the same code as other apps?
const stickerApp: IWidget = {

View file

@ -144,7 +144,7 @@ export const PeopleRoomSettingsTab: VFC<{ room: Room }> = ({ room }) => {
const knockMembers = useTypedEventEmitterState(
room,
RoomStateEvent.Members,
RoomStateEvent.Update,
useCallback(() => room.getMembersWithMembership("knock"), [room]),
);

View file

@ -249,68 +249,74 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
const plEventsToLabels: Record<EventType | string, TranslationKey | null> = {
// These will be translated for us later.
[EventType.RoomAvatar]: isSpaceRoom ? _td("Change space avatar") : _td("Change room avatar"),
[EventType.RoomName]: isSpaceRoom ? _td("Change space name") : _td("Change room name"),
[EventType.RoomAvatar]: isSpaceRoom
? _td("room_settings|permissions|m.room.avatar_space")
: _td("room_settings|permissions|m.room.avatar"),
[EventType.RoomName]: isSpaceRoom
? _td("room_settings|permissions|m.room.name_space")
: _td("room_settings|permissions|m.room.name"),
[EventType.RoomCanonicalAlias]: isSpaceRoom
? _td("Change main address for the space")
: _td("Change main address for the room"),
[EventType.SpaceChild]: _td("Manage rooms in this space"),
[EventType.RoomHistoryVisibility]: _td("Change history visibility"),
[EventType.RoomPowerLevels]: _td("Change permissions"),
[EventType.RoomTopic]: isSpaceRoom ? _td("Change description") : _td("Change topic"),
[EventType.RoomTombstone]: _td("Upgrade the room"),
[EventType.RoomEncryption]: _td("Enable room encryption"),
[EventType.RoomServerAcl]: _td("Change server ACLs"),
[EventType.Reaction]: _td("Send reactions"),
[EventType.RoomRedaction]: _td("Remove messages sent by me"),
? _td("room_settings|permissions|m.room.canonical_alias_space")
: _td("room_settings|permissions|m.room.canonical_alias"),
[EventType.SpaceChild]: _td("room_settings|permissions|m.space.child"),
[EventType.RoomHistoryVisibility]: _td("room_settings|permissions|m.room.history_visibility"),
[EventType.RoomPowerLevels]: _td("room_settings|permissions|m.room.power_levels"),
[EventType.RoomTopic]: isSpaceRoom
? _td("room_settings|permissions|m.room.topic_space")
: _td("room_settings|permissions|m.room.topic"),
[EventType.RoomTombstone]: _td("room_settings|permissions|m.room.tombstone"),
[EventType.RoomEncryption]: _td("room_settings|permissions|m.room.encryption"),
[EventType.RoomServerAcl]: _td("room_settings|permissions|m.room.server_acl"),
[EventType.Reaction]: _td("room_settings|permissions|m.reaction"),
[EventType.RoomRedaction]: _td("room_settings|permissions|m.room.redaction"),
// TODO: Enable support for m.widget event type (https://github.com/vector-im/element-web/issues/13111)
"im.vector.modular.widgets": isSpaceRoom ? null : _td("Modify widgets"),
[VoiceBroadcastInfoEventType]: _td("Voice broadcasts"),
"im.vector.modular.widgets": isSpaceRoom ? null : _td("room_settings|permissions|m.widget"),
[VoiceBroadcastInfoEventType]: _td("room_settings|permissions|io.element.voice_broadcast_info"),
};
if (SettingsStore.getValue("feature_pinning")) {
plEventsToLabels[EventType.RoomPinnedEvents] = _td("Manage pinned events");
plEventsToLabels[EventType.RoomPinnedEvents] = _td("room_settings|permissions|m.room.pinned_events");
}
// MSC3401: Native Group VoIP signaling
if (SettingsStore.getValue("feature_group_calls")) {
plEventsToLabels[ElementCall.CALL_EVENT_TYPE.name] = _td("Start %(brand)s calls");
plEventsToLabels[ElementCall.MEMBER_EVENT_TYPE.name] = _td("Join %(brand)s calls");
plEventsToLabels[ElementCall.CALL_EVENT_TYPE.name] = _td("room_settings|permissions|m.call");
plEventsToLabels[ElementCall.MEMBER_EVENT_TYPE.name] = _td("room_settings|permissions|m.call.member");
}
const powerLevelDescriptors: Record<string, IPowerLevelDescriptor> = {
"users_default": {
desc: _t("Default role"),
desc: _t("room_settings|permissions|users_default"),
defaultValue: 0,
},
"events_default": {
desc: _t("Send messages"),
desc: _t("room_settings|permissions|events_default"),
defaultValue: 0,
hideForSpace: true,
},
"invite": {
desc: _t("Invite users"),
desc: _t("room_settings|permissions|invite"),
defaultValue: 0,
},
"state_default": {
desc: _t("Change settings"),
desc: _t("room_settings|permissions|state_default"),
defaultValue: 50,
},
"kick": {
desc: _t("Remove users"),
desc: _t("room_settings|permissions|kick"),
defaultValue: 50,
},
"ban": {
desc: _t("Ban users"),
desc: _t("room_settings|permissions|ban"),
defaultValue: 50,
},
"redact": {
desc: _t("Remove messages sent by others"),
desc: _t("room_settings|permissions|redact"),
defaultValue: 50,
hideForSpace: true,
},
"notifications.room": {
desc: _t("Notify everyone"),
desc: _t("room_settings|permissions|notifications.room"),
defaultValue: 50,
hideForSpace: true,
},

View file

@ -276,7 +276,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
public render(): React.ReactNode {
const secureBackup = (
<SettingsSubsection heading={_t("Secure Backup")}>
<SettingsSubsection heading={_t("common|secure_backup")}>
<SecureBackupPanel />
</SettingsSubsection>
);
@ -292,7 +292,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
// in having advanced details here once all flows are implemented, we
// can remove this.
const crossSigning = (
<SettingsSubsection heading={_t("Cross-signing")}>
<SettingsSubsection heading={_t("common|cross_signing")}>
<CrossSigningPanel />
</SettingsSubsection>
);

View file

@ -259,23 +259,23 @@ export const Lobby: FC<LobbyProps> = ({ room, joinCallButtonDisabledTooltip, con
kind="audio"
devices={audioInputs}
setDevice={setAudioInput}
deviceListLabel={_t("Audio devices")}
deviceListLabel={_t("voip|audio_devices")}
muted={audioMuted}
disabled={connecting}
toggle={toggleAudio}
unmutedTitle={_t("Mute microphone")}
mutedTitle={_t("Unmute microphone")}
unmutedTitle={_t("voip|disable_microphone")}
mutedTitle={_t("voip|enable_microphone")}
/>
<DeviceButton
kind="video"
devices={videoInputs}
setDevice={setVideoInput}
deviceListLabel={_t("Video devices")}
deviceListLabel={_t("voip|video_devices")}
muted={videoMuted}
disabled={connecting}
toggle={toggleVideo}
unmutedTitle={_t("Turn off camera")}
mutedTitle={_t("Turn on camera")}
unmutedTitle={_t("voip|disable_camera")}
mutedTitle={_t("voip|enable_camera")}
/>
</div>
</div>

View file

@ -66,7 +66,7 @@ class DialPadButton extends React.PureComponent<DigitButtonProps | DialButtonPro
<AccessibleButton
className="mx_DialPad_button mx_DialPad_dialButton"
onClick={this.onClick}
aria-label={_t("Dial")}
aria-label={_t("voip|dial")}
/>
);
}

View file

@ -404,11 +404,9 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
const sharerName = primaryFeed?.getMember()?.name;
if (!sharerName) return null;
let text = isScreensharing ? _t("You are presenting") : _t("%(sharerName)s is presenting", { sharerName });
let text = isScreensharing ? _t("voip|you_are_presenting") : _t("voip|user_is_presenting", { sharerName });
if (!sidebarShown) {
text +=
" • " +
(call.isLocalVideoMuted() ? _t("Your camera is turned off") : _t("Your camera is still enabled"));
text += " • " + (call.isLocalVideoMuted() ? _t("voip|camera_disabled") : _t("voip|camera_enabled"));
}
return <div className="mx_LegacyCallView_toast">{text}</div>;
@ -450,7 +448,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
holdTransferContent = (
<div className="mx_LegacyCallView_status">
{_t(
"Consulting with %(transferTarget)s. <a>Transfer to %(transferee)s</a>",
"voip|consulting",
{
transferTarget: transferTargetName,
transferee: transfereeName,
@ -470,8 +468,8 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
if (isRemoteOnHold) {
onHoldText = _t(
LegacyCallHandler.instance.hasAnyUnheldCall()
? _td("You held the call <a>Switch</a>")
: _td("You held the call <a>Resume</a>"),
? _td("voip|call_held_switch")
: _td("voip|call_held_resume"),
{},
{
a: (sub) => (
@ -482,7 +480,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
},
);
} else if (isLocalOnHold) {
onHoldText = _t("%(peerName)s held the call", {
onHoldText = _t("voip|call_held", {
peerName: call.getOpponentMember()?.name,
});
}

View file

@ -269,15 +269,15 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
inputRef={this.dialpadButton}
onClick={this.onDialpadClick}
isExpanded={this.state.showDialpad}
title={_t("Dialpad")}
title={_t("voip|dialpad")}
alignment={Alignment.Top}
/>
)}
<LegacyCallViewDropdownButton
state={!this.props.buttonsState.micMuted}
className="mx_LegacyCallViewButtons_button_mic"
onLabel={_t("Mute the microphone")}
offLabel={_t("Unmute the microphone")}
onLabel={_t("voip|disable_microphone")}
offLabel={_t("voip|enable_microphone")}
onClick={this.props.handlers.onMicMuteClick}
deviceKinds={[MediaDeviceKindEnum.AudioInput, MediaDeviceKindEnum.AudioOutput]}
/>
@ -285,8 +285,8 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
<LegacyCallViewDropdownButton
state={!this.props.buttonsState.vidMuted}
className="mx_LegacyCallViewButtons_button_vid"
onLabel={_t("Stop the camera")}
offLabel={_t("Start the camera")}
onLabel={_t("voip|disable_camera")}
offLabel={_t("voip|enable_camera")}
onClick={this.props.handlers.onVidMuteClick}
deviceKinds={[MediaDeviceKindEnum.VideoInput]}
/>
@ -295,8 +295,8 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
<LegacyCallViewToggleButton
state={this.props.buttonsState.screensharing}
className="mx_LegacyCallViewButtons_button_screensharing"
onLabel={_t("Stop sharing your screen")}
offLabel={_t("Start sharing your screen")}
onLabel={_t("voip|stop_screenshare")}
offLabel={_t("voip|start_screenshare")}
onClick={this.props.handlers.onScreenshareClick}
/>
)}
@ -322,7 +322,7 @@ export default class LegacyCallViewButtons extends React.Component<IProps, IStat
<AccessibleTooltipButton
className="mx_LegacyCallViewButtons_button mx_LegacyCallViewButtons_button_hangup"
onClick={this.props.handlers.onHangupClick}
title={_t("Hangup")}
title={_t("voip|hangup")}
alignment={Alignment.Top}
/>
</div>

View file

@ -34,7 +34,7 @@ const LegacyCallViewHeaderControls: React.FC<LegacyCallControlsProps> = ({ onExp
<AccessibleTooltipButton
className="mx_LegacyCallViewHeader_button mx_LegacyCallViewHeader_button_fullscreen"
onClick={onMaximize}
title={_t("Fill screen")}
title={_t("voip|maximise")}
/>
)}
{onPin && (
@ -48,7 +48,7 @@ const LegacyCallViewHeaderControls: React.FC<LegacyCallControlsProps> = ({ onExp
<AccessibleTooltipButton
className="mx_LegacyCallViewHeader_button mx_LegacyCallViewHeader_button_expand"
onClick={onExpand}
title={_t("Return to call")}
title={_t("voip|expand")}
/>
)}
</div>
@ -64,7 +64,7 @@ const SecondaryCallInfo: React.FC<ISecondaryCallInfoProps> = ({ callRoom }) => {
<span className="mx_LegacyCallViewHeader_secondaryCallInfo">
<RoomAvatar room={callRoom} size="16px" />
<span className="mx_LegacyCallView_secondaryCall_roomName">
{_t("%(name)s on hold", { name: callRoom.name })}
{_t("voip|on_hold", { name: callRoom.name })}
</span>
</span>
);