Merge branch 'develop' of github.com:matrix-org/matrix-react-sdk into t3chguy/cr/72

This commit is contained in:
Michael Telatynski 2023-09-11 17:50:26 +01:00
commit 0a36af7cc0
No known key found for this signature in database
GPG key ID: A2B008A5F49F5D0D
118 changed files with 11130 additions and 9263 deletions

View file

@ -2205,6 +2205,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
knocked={myMembership === "knock" || this.state.knocked}
onSubmitAskToJoin={this.onSubmitAskToJoin}
onCancelAskToJoin={this.onCancelAskToJoin}
onForgetClick={this.onForgetClick}
/>
</ErrorBoundary>
</div>

View file

@ -66,7 +66,7 @@ const LiveTimeRemaining: React.FC<{ beacon: Beacon }> = ({ beacon }) => {
const msRemaining = useMsRemaining(beacon);
const timeRemaining = formatDuration(msRemaining);
const liveTimeRemaining = _t(`%(timeRemaining)s left`, { timeRemaining });
const liveTimeRemaining = _t("time|left", { timeRemaining });
return (
<span data-testid="room-live-share-expiry" className="mx_LiveTimeRemaining">

View file

@ -51,7 +51,7 @@ export const AnalyticsLearnMoreDialog: React.FC<IProps> = ({
const privacyPolicyLink = privacyPolicyUrl ? (
<span>
{_t(
"You can read all our terms <PrivacyPolicyUrl>here</PrivacyPolicyUrl>",
"analytics|privacy_policy",
{},
{
PrivacyPolicyUrl: (sub) => {
@ -71,33 +71,18 @@ export const AnalyticsLearnMoreDialog: React.FC<IProps> = ({
<BaseDialog
className="mx_AnalyticsLearnMoreDialog"
contentId="mx_AnalyticsLearnMore"
title={_t("Help improve %(analyticsOwner)s", { analyticsOwner })}
title={_t("analytics|enable_prompt", { analyticsOwner })}
onFinished={onFinished}
>
<div className="mx_Dialog_content">
<div className="mx_AnalyticsLearnMore_image_holder" />
<div className="mx_AnalyticsLearnMore_copy">
{_t(
"Help us identify issues and improve %(analyticsOwner)s by sharing anonymous usage data. To understand how people use multiple devices, we'll generate a random identifier, shared by your devices.",
{ analyticsOwner },
)}
{_t("analytics|pseudonymous_usage_data", { analyticsOwner })}
</div>
<ul className="mx_AnalyticsLearnMore_bullets">
<li>
{_t(
"We <Bold>don't</Bold> record or profile any account data",
{},
{ Bold: (sub) => <b>{sub}</b> },
)}
</li>
<li>
{_t(
"We <Bold>don't</Bold> share information with third parties",
{},
{ Bold: (sub) => <b>{sub}</b> },
)}
</li>
<li>{_t("You can turn this off anytime in settings")}</li>
<li>{_t("analytics|bullet_1", {}, { Bold: (sub) => <b>{sub}</b> })}</li>
<li>{_t("analytics|bullet_2", {}, { Bold: (sub) => <b>{sub}</b> })}</li>
<li>{_t("analytics|disable_prompt")}</li>
</ul>
{privacyPolicyLink}
</div>

View file

@ -67,7 +67,7 @@ export default class SessionRestoreErrorDialog extends React.Component<IProps> {
if (SdkConfig.get().bug_report_endpoint_url) {
dialogButtons = (
<DialogButtons
primaryButton={_t("Send Logs")}
primaryButton={_t("bug_reporting|send_logs")}
onPrimaryButtonClick={this.sendBugReport}
focus={true}
hasCancel={false}

View file

@ -92,14 +92,14 @@ export default class TermsDialog extends React.PureComponent<ITermsDialogProps,
case SERVICE_TYPES.IS:
return (
<div>
{_t("Identity server")}
{_t("common|identity_server")}
<br />({host})
</div>
);
case SERVICE_TYPES.IM:
return (
<div>
{_t("Integration manager")}
{_t("common|integration_manager")}
<br />({host})
</div>
);

View file

@ -51,7 +51,7 @@ export default class UploadFailureDialog extends React.Component<IProps> {
message = _t(
"This file is <b>too large</b> to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.",
{
limit: fileSize(this.props.contentMessages.getUploadLimit()),
limit: fileSize(this.props.contentMessages.getUploadLimit()!),
sizeOfThisFile: fileSize(this.props.badFiles[0].size),
},
{
@ -70,7 +70,7 @@ export default class UploadFailureDialog extends React.Component<IProps> {
message = _t(
"These files are <b>too large</b> to upload. The file size limit is %(limit)s.",
{
limit: fileSize(this.props.contentMessages.getUploadLimit()),
limit: fileSize(this.props.contentMessages.getUploadLimit()!),
},
{
b: (sub) => <b>{sub}</b>,
@ -88,7 +88,7 @@ export default class UploadFailureDialog extends React.Component<IProps> {
message = _t(
"Some files are <b>too large</b> to be uploaded. The file size limit is %(limit)s.",
{
limit: fileSize(this.props.contentMessages.getUploadLimit()),
limit: fileSize(this.props.contentMessages.getUploadLimit()!),
},
{
b: (sub) => <b>{sub}</b>,

View file

@ -563,7 +563,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
};
let otherSearchesSection: JSX.Element | undefined;
if (trimmedQuery || filter !== Filter.PublicRooms) {
if (trimmedQuery || (filter !== Filter.PublicRooms && filter !== Filter.PublicSpaces)) {
otherSearchesSection = (
<div
className="mx_SpotlightDialog_section mx_SpotlightDialog_otherSearches"

View file

@ -85,7 +85,7 @@ export default class ErrorBoundary extends React.PureComponent<Props, IState> {
<React.Fragment>
<p>
{_t(
"Please <newIssueLink>create a new issue</newIssueLink> on GitHub so that we can investigate this bug.",
"bug_reporting|create_new_issue",
{},
{
newIssueLink: (sub) => {

View file

@ -133,7 +133,7 @@ export default class EventListSummary extends React.Component<
const desc = formatCommaSeparatedList(descs);
return _t("%(nameList)s %(transitionList)s", { nameList, transitionList: desc });
return _t("timeline|summary|format", { nameList, transitionList: desc });
});
if (!summaries) {
@ -250,101 +250,101 @@ export default class EventListSummary extends React.Component<
case TransitionType.Joined:
res =
userCount > 1
? _t("%(severalUsers)sjoined %(count)s times", { severalUsers: "", count })
: _t("%(oneUser)sjoined %(count)s times", { oneUser: "", count });
? _t("timeline|summary|joined_multiple", { severalUsers: "", count })
: _t("timeline|summary|joined", { oneUser: "", count });
break;
case TransitionType.Left:
res =
userCount > 1
? _t("%(severalUsers)sleft %(count)s times", { severalUsers: "", count })
: _t("%(oneUser)sleft %(count)s times", { oneUser: "", count });
? _t("timeline|summary|left_multiple", { severalUsers: "", count })
: _t("timeline|summary|left", { oneUser: "", count });
break;
case TransitionType.JoinedAndLeft:
res =
userCount > 1
? _t("%(severalUsers)sjoined and left %(count)s times", { severalUsers: "", count })
: _t("%(oneUser)sjoined and left %(count)s times", { oneUser: "", count });
? _t("timeline|summary|joined_and_left_multiple", { severalUsers: "", count })
: _t("timeline|summary|joined_and_left", { oneUser: "", count });
break;
case TransitionType.LeftAndJoined:
res =
userCount > 1
? _t("%(severalUsers)sleft and rejoined %(count)s times", { severalUsers: "", count })
: _t("%(oneUser)sleft and rejoined %(count)s times", { oneUser: "", count });
? _t("timeline|summary|rejoined_multiple", { severalUsers: "", count })
: _t("timeline|summary|rejoined", { oneUser: "", count });
break;
case TransitionType.InviteReject:
res =
userCount > 1
? _t("%(severalUsers)srejected their invitations %(count)s times", {
? _t("timeline|summary|rejected_invite_multiple", {
severalUsers: "",
count,
})
: _t("%(oneUser)srejected their invitation %(count)s times", { oneUser: "", count });
: _t("timeline|summary|rejected_invite", { oneUser: "", count });
break;
case TransitionType.InviteWithdrawal:
res =
userCount > 1
? _t("%(severalUsers)shad their invitations withdrawn %(count)s times", {
? _t("timeline|summary|invite_withdrawn_multiple", {
severalUsers: "",
count,
})
: _t("%(oneUser)shad their invitation withdrawn %(count)s times", { oneUser: "", count });
: _t("timeline|summary|invite_withdrawn", { oneUser: "", count });
break;
case TransitionType.Invited:
res =
userCount > 1
? _t("were invited %(count)s times", { count })
: _t("was invited %(count)s times", { count });
? _t("timeline|summary|invited_multiple", { count })
: _t("timeline|summary|invited", { count });
break;
case TransitionType.Banned:
res =
userCount > 1
? _t("were banned %(count)s times", { count })
: _t("was banned %(count)s times", { count });
? _t("timeline|summary|banned_multiple", { count })
: _t("timeline|summary|banned", { count });
break;
case TransitionType.Unbanned:
res =
userCount > 1
? _t("were unbanned %(count)s times", { count })
: _t("was unbanned %(count)s times", { count });
? _t("timeline|summary|unbanned_multiple", { count })
: _t("timeline|summary|unbanned", { count });
break;
case TransitionType.Kicked:
res =
userCount > 1
? _t("were removed %(count)s times", { count })
: _t("was removed %(count)s times", { count });
? _t("timeline|summary|kicked_multiple", { count })
: _t("timeline|summary|kicked", { count });
break;
case TransitionType.ChangedName:
res =
userCount > 1
? _t("%(severalUsers)schanged their name %(count)s times", { severalUsers: "", count })
: _t("%(oneUser)schanged their name %(count)s times", { oneUser: "", count });
? _t("timeline|summary|changed_name_multiple", { severalUsers: "", count })
: _t("timeline|summary|changed_name", { oneUser: "", count });
break;
case TransitionType.ChangedAvatar:
res =
userCount > 1
? _t("%(severalUsers)schanged their profile picture %(count)s times", {
? _t("timeline|summary|changed_avatar_multiple", {
severalUsers: "",
count,
})
: _t("%(oneUser)schanged their profile picture %(count)s times", { oneUser: "", count });
: _t("timeline|summary|changed_avatar", { oneUser: "", count });
break;
case TransitionType.NoChange:
res =
userCount > 1
? _t("%(severalUsers)smade no changes %(count)s times", { severalUsers: "", count })
: _t("%(oneUser)smade no changes %(count)s times", { oneUser: "", count });
? _t("timeline|summary|no_change_multiple", { severalUsers: "", count })
: _t("timeline|summary|no_change", { oneUser: "", count });
break;
case TransitionType.ServerAcl:
res =
userCount > 1
? _t("%(severalUsers)schanged the server ACLs %(count)s times", { severalUsers: "", count })
: _t("%(oneUser)schanged the server ACLs %(count)s times", { oneUser: "", count });
? _t("timeline|summary|server_acls_multiple", { severalUsers: "", count })
: _t("timeline|summary|server_acls", { oneUser: "", count });
break;
case TransitionType.ChangedPins:
res =
userCount > 1
? _t(
"%(severalUsers)schanged the <a>pinned messages</a> for the room %(count)s times",
"timeline|summary|pinned_events_multiple",
{ severalUsers: "", count },
{
a: (sub) => (
@ -355,7 +355,7 @@ export default class EventListSummary extends React.Component<
},
)
: _t(
"%(oneUser)schanged the <a>pinned messages</a> for the room %(count)s times",
"timeline|summary|pinned_events",
{ oneUser: "", count },
{
a: (sub) => (
@ -369,14 +369,14 @@ export default class EventListSummary extends React.Component<
case TransitionType.MessageRemoved:
res =
userCount > 1
? _t("%(severalUsers)sremoved a message %(count)s times", { severalUsers: "", count })
: _t("%(oneUser)sremoved a message %(count)s times", { oneUser: "", count });
? _t("timeline|summary|redacted_multiple", { severalUsers: "", count })
: _t("timeline|summary|redacted", { oneUser: "", count });
break;
case TransitionType.HiddenEvent:
res =
userCount > 1
? _t("%(severalUsers)ssent %(count)s hidden messages", { severalUsers: "", count })
: _t("%(oneUser)ssent %(count)s hidden messages", { oneUser: "", count });
? _t("timeline|summary|hidden_event_multiple", { severalUsers: "", count })
: _t("timeline|summary|hidden_event", { oneUser: "", count });
break;
}

View file

@ -87,63 +87,63 @@ class EmojiPicker extends React.Component<IProps, IState> {
this.categories = [
{
id: "recent",
name: _t("Frequently Used"),
name: _t("emoji|category_frequently_used"),
enabled: this.recentlyUsed.length > 0,
visible: this.recentlyUsed.length > 0,
ref: React.createRef(),
},
{
id: "people",
name: _t("Smileys & People"),
name: _t("emoji|category_smileys_people"),
enabled: true,
visible: true,
ref: React.createRef(),
},
{
id: "nature",
name: _t("Animals & Nature"),
name: _t("emoji|category_animals_nature"),
enabled: true,
visible: false,
ref: React.createRef(),
},
{
id: "foods",
name: _t("Food & Drink"),
name: _t("emoji|category_food_drink"),
enabled: true,
visible: false,
ref: React.createRef(),
},
{
id: "activity",
name: _t("Activities"),
name: _t("emoji|category_activities"),
enabled: true,
visible: false,
ref: React.createRef(),
},
{
id: "places",
name: _t("Travel & Places"),
name: _t("emoji|category_travel_places"),
enabled: true,
visible: false,
ref: React.createRef(),
},
{
id: "objects",
name: _t("Objects"),
name: _t("emoji|category_objects"),
enabled: true,
visible: false,
ref: React.createRef(),
},
{
id: "symbols",
name: _t("Symbols"),
name: _t("emoji|category_symbols"),
enabled: true,
visible: false,
ref: React.createRef(),
},
{
id: "flags",
name: _t("Flags"),
name: _t("emoji|category_flags"),
enabled: true,
visible: false,
ref: React.createRef(),

View file

@ -95,7 +95,7 @@ class Header extends React.PureComponent<IProps> {
<nav
className="mx_EmojiPicker_header"
role="tablist"
aria-label={_t("Categories")}
aria-label={_t("emoji|categories")}
onKeyDown={this.onKeyDown}
>
{this.props.categories.map((category) => {

View file

@ -64,7 +64,7 @@ class QuickReactions extends React.Component<IProps, IState> {
<section className="mx_EmojiPicker_footer mx_EmojiPicker_quick mx_EmojiPicker_category">
<h2 className="mx_EmojiPicker_quick_header mx_EmojiPicker_category_label">
{!this.state.hover ? (
_t("Quick Reactions")
_t("emoji|quick_reactions")
) : (
<React.Fragment>
<span className="mx_EmojiPicker_name">{this.state.hover.label}</span>
@ -72,7 +72,7 @@ class QuickReactions extends React.Component<IProps, IState> {
</React.Fragment>
)}
</h2>
<Toolbar className="mx_EmojiPicker_list" aria-label={_t("Quick Reactions")}>
<Toolbar className="mx_EmojiPicker_list" aria-label={_t("emoji|quick_reactions")}>
{QUICK_REACTIONS.map((emoji) => (
<Emoji
key={emoji.hexcode}

View file

@ -117,7 +117,7 @@ const EncryptionInfo: React.FC<IProps> = ({
"For extra security, verify this user by checking a one-time code on both of your devices.",
)}
</p>
<p>{_t("To be secure, do this in person or use a trusted way to communicate.")}</p>
<p>{_t("encryption|verification|in_person")}</p>
{content}
</div>
</div>

View file

@ -90,14 +90,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const brand = SdkConfig.get().brand;
const noCommonMethodError: JSX.Element | null =
!showSAS && !showQR ? (
<p>
{_t(
"The device you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.",
{ brand },
)}
</p>
) : null;
!showSAS && !showQR ? <p>{_t("encryption|verification|no_support_qr_emoji", { brand })}</p> : null;
if (this.props.layout === "dialog") {
// HACK: This is a terrible idea.
@ -106,7 +99,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
if (showQR) {
qrBlockDialog = (
<div className="mx_VerificationPanel_QRPhase_startOption">
<p>{_t("Scan this unique code")}</p>
<p>{_t("encryption|verification|qr_prompt")}</p>
<VerificationQRCode qrCodeBytes={this.state.qrCodeBytes} />
</div>
);
@ -114,9 +107,9 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
if (showSAS) {
sasBlockDialog = (
<div className="mx_VerificationPanel_QRPhase_startOption">
<p>{_t("Compare unique emoji")}</p>
<p>{_t("encryption|verification|sas_prompt")}</p>
<span className="mx_VerificationPanel_QRPhase_helpText">
{_t("Compare a unique set of emoji if you don't have a camera on either device")}
{_t("encryption|verification|sas_description")}
</span>
<AccessibleButton
disabled={this.state.emojiButtonClicked}
@ -131,7 +124,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
const or =
qrBlockDialog && sasBlockDialog ? (
<div className="mx_VerificationPanel_QRPhase_betweenText">
{_t("%(qrCode)s or %(emojiCompare)s", {
{_t("encryption|verification|qr_or_sas", {
emojiCompare: "",
qrCode: "",
})}
@ -139,7 +132,7 @@ export default class VerificationPanel extends React.PureComponent<IProps, IStat
) : null;
return (
<div>
{_t("Verify this device by completing one of the following:")}
{_t("encryption|verification|qr_or_sas_header")}
<div className="mx_VerificationPanel_QRPhase_startOptions">
{qrBlockDialog}
{or}

View file

@ -1003,7 +1003,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
let avatar: JSX.Element | null = null;
let sender: JSX.Element | null = null;
let avatarSize: string;
let avatarSize: string | null;
let needsSenderProfile: boolean;
if (isRenderingNotification) {
@ -1021,7 +1021,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
avatarSize = "32px";
needsSenderProfile = true;
} else if (eventType === EventType.RoomCreate || isBubbleMessage) {
avatarSize = "0";
avatarSize = null;
needsSenderProfile = false;
} else if (this.props.layout == Layout.IRC) {
avatarSize = "14px";
@ -1032,14 +1032,14 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
ElementCall.CALL_EVENT_TYPE.matches(eventType)
) {
// no avatar or sender profile for continuation messages and call tiles
avatarSize = "0";
avatarSize = null;
needsSenderProfile = false;
} else {
avatarSize = "30px";
needsSenderProfile = true;
}
if (this.props.mxEvent.sender && avatarSize) {
if (this.props.mxEvent.sender && avatarSize !== null) {
let member: RoomMember | null = null;
// set member to receiver (target) if it is a 3PID invite
// so that the correct avatar is shown as the text is

View file

@ -112,7 +112,7 @@ export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
</>
);
names = `${knockMembers[0].name} (${knockMembers[0].userId})`;
link = (
link = knockMembers[0].events.member?.getContent().reason && (
<AccessibleButton
className="mx_RoomKnocksBar_link"
element="a"

View file

@ -62,6 +62,7 @@ enum MessageCase {
OtherError = "OtherError",
PromptAskToJoin = "PromptAskToJoin",
Knocked = "Knocked",
RequestDenied = "requestDenied",
}
interface IProps {
@ -188,7 +189,11 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
const myMember = this.getMyMember();
if (myMember) {
const previousMembership = myMember.events.member?.getPrevContent().membership;
if (myMember.isKicked()) {
if (previousMembership === "knock") {
return MessageCase.RequestDenied;
}
return MessageCase.Kicked;
} else if (myMember.membership === "ban") {
return MessageCase.Banned;
@ -397,6 +402,21 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
}
break;
}
case MessageCase.RequestDenied: {
title = _t("You have been denied access");
subTitle = _t(
"As you have been denied access, you cannot rejoin unless you are invited by the admin or moderator of the group.",
);
if (isSpace) {
primaryActionLabel = _t("Forget this space");
} else {
primaryActionLabel = _t("Forget this room");
}
primaryActionHandler = this.props.onForgetClick;
break;
}
case MessageCase.Banned: {
const { memberName, reason } = this.getKickOrBanInfo();
if (roomName) {

View file

@ -106,7 +106,11 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
public render(): React.ReactNode {
return (
<SettingsSubsection heading={_t("Font size")} stretchContent data-testid="mx_FontScalingPanel">
<SettingsSubsection
heading={_t("settings|appearance|font_size")}
stretchContent
data-testid="mx_FontScalingPanel"
>
<EventTilePreview
className="mx_FontScalingPanel_preview"
message={this.MESSAGE_PREVIEW_TEXT}
@ -125,7 +129,7 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
onChange={this.onFontSizeChanged}
displayFunc={(_) => ""}
disabled={this.state.useCustomFontSize}
label={_t("Font size")}
label={_t("settings|appearance|font_size")}
/>
<div className="mx_FontScalingPanel_fontSlider_largeText">Aa</div>
</div>
@ -148,7 +152,7 @@ export default class FontScalingPanel extends React.Component<IProps, IState> {
<Field
type="number"
label={_t("Font size")}
label={_t("settings|appearance|font_size")}
autoComplete="off"
placeholder={this.state.fontSize.toString()}
value={this.state.fontSize.toString()}

View file

@ -50,7 +50,7 @@ export default class ImageSizePanel extends React.Component<IProps, IState> {
public render(): React.ReactNode {
return (
<SettingsSubsection heading={_t("Image size in the timeline")}>
<SettingsSubsection heading={_t("settings|appearance|timeline_image_size")}>
<div className="mx_ImageSizePanel_radios">
<label>
<div className="mx_ImageSizePanel_size mx_ImageSizePanel_sizeDefault" />

View file

@ -104,6 +104,6 @@ export default class IntegrationManager extends React.Component<IProps, IState>
);
}
return <iframe title={_t("Integration manager")} src={this.props.url} onError={this.onError} />;
return <iframe title={_t("common|integration_manager")} src={this.props.url} onError={this.onError} />;
}
}

View file

@ -14,8 +14,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { ReactNode } from "react";
import { IJoinRuleEventContent, JoinRule, RestrictedAllowType, Room, EventType } from "matrix-js-sdk/src/matrix";
import React, { ReactNode, useEffect, useState } from "react";
import {
IJoinRuleEventContent,
JoinRule,
RestrictedAllowType,
Room,
EventType,
Visibility,
} from "matrix-js-sdk/src/matrix";
import StyledRadioGroup, { IDefinition } from "../elements/StyledRadioGroup";
import { _t } from "../../../languageHandler";
@ -34,6 +41,7 @@ import { Action } from "../../../dispatcher/actions";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { doesRoomVersionSupport, PreferredRoomVersions } from "../../../utils/PreferredRoomVersions";
import SettingsStore from "../../../settings/SettingsStore";
import LabelledCheckbox from "../elements/LabelledCheckbox";
export interface JoinRuleSettingsProps {
room: Room;
@ -76,6 +84,22 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
? content?.allow?.filter((o) => o.type === RestrictedAllowType.RoomMembership).map((o) => o.room_id)
: undefined;
const [isPublicKnockRoom, setIsPublicKnockRoom] = useState(false);
useEffect(() => {
if (joinRule === JoinRule.Knock) {
cli.getRoomDirectoryVisibility(room.roomId)
.then(({ visibility }) => setIsPublicKnockRoom(visibility === Visibility.Public))
.catch(onError);
}
}, [cli, joinRule, onError, room.roomId]);
const onIsPublicKnockRoomChange = (checked: boolean): void => {
cli.setRoomDirectoryVisibility(room.roomId, checked ? Visibility.Public : Visibility.Private)
.then(() => setIsPublicKnockRoom(checked))
.catch(onError);
};
const editRestrictedRoomIds = async (): Promise<string[] | undefined> => {
let selected = restrictedAllowRoomIds;
if (!selected?.length && SpaceStore.instance.activeSpaceRoom) {
@ -297,7 +321,22 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
{preferredKnockVersion && upgradeRequiredPill}
</>
),
description: _t("People cannot join unless access is granted."),
description: (
<>
{_t("People cannot join unless access is granted.")}
<LabelledCheckbox
className="mx_JoinRuleSettings_labelledCheckbox"
disabled={joinRule !== JoinRule.Knock}
label={
room.isSpaceRoom()
? _t("Make this space visible in the public room directory.")
: _t("Make this room visible in the public room directory.")
}
onChange={onIsPublicKnockRoomChange}
value={isPublicKnockRoom}
/>
</>
),
});
}

View file

@ -85,7 +85,7 @@ export default class LayoutSwitcher extends React.Component<IProps, IState> {
checked={this.state.layout === Layout.IRC}
onChange={this.onLayoutChange}
>
{_t("IRC (Experimental)")}
{_t("settings|appearance|layout_irc")}
</StyledRadioButton>
</label>
<label className={groupClasses}>
@ -121,7 +121,7 @@ export default class LayoutSwitcher extends React.Component<IProps, IState> {
checked={this.state.layout == Layout.Bubble}
onChange={this.onLayoutChange}
>
{_t("Message bubbles")}
{_t("settings|appearance|layout_bubbles")}
</StyledRadioButton>
</label>
</div>

View file

@ -381,7 +381,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
if (category === KEYWORD_RULE_CATEGORY) {
preparedNewState.vectorPushRules[category]!.push({
ruleId: KEYWORD_RULE_ID,
description: _t("Messages containing keywords"),
description: _t("settings|notifications|messages_containing_keywords"),
vectorState: preparedNewState.vectorKeywordRuleInfo.vectorState,
});
}
@ -400,8 +400,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
private showSaveError(): void {
Modal.createDialog(ErrorDialog, {
title: _t("Error saving notification preferences"),
description: _t("An error occurred whilst saving your notification preferences."),
title: _t("settings|notifications|error_saving"),
description: _t("settings|notifications|error_saving_detail"),
});
}
@ -661,8 +661,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
<LabelledToggleSwitch
data-testid="notif-master-switch"
value={!this.isInhibited}
label={_t("Enable notifications for this account")}
caption={_t("Turn off to disable notifications on all your devices and sessions")}
label={_t("settings|notifications|enable_notifications_account")}
caption={_t("settings|notifications|enable_notifications_account_detail")}
onChange={this.onMasterRuleChanged}
disabled={this.state.phase === Phase.Persisting}
/>
@ -680,7 +680,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
data-testid="notif-email-switch"
key={e.address}
value={!!this.state.pushers?.some((p) => p.kind === "email" && p.pushkey === e.address)}
label={_t("Enable email notifications for %(email)s", { email: e.address })}
label={_t("settings|notifications|enable_email_notifications", { email: e.address })}
onChange={this.onEmailNotificationsChanged.bind(this, e.address)}
disabled={this.state.phase === Phase.Persisting}
/>
@ -693,7 +693,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
<LabelledToggleSwitch
data-testid="notif-device-switch"
value={this.state.deviceNotificationsEnabled}
label={_t("Enable notifications for this device")}
label={_t("settings|notifications|enable_notifications_device")}
onChange={(checked) => this.updateDeviceNotifications(checked)}
disabled={this.state.phase === Phase.Persisting}
/>
@ -704,21 +704,21 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
data-testid="notif-setting-notificationsEnabled"
value={this.state.desktopNotifications}
onChange={this.onDesktopNotificationsChanged}
label={_t("Enable desktop notifications for this session")}
label={_t("settings|notifications|enable_desktop_notifications_session")}
disabled={this.state.phase === Phase.Persisting}
/>
<LabelledToggleSwitch
data-testid="notif-setting-notificationBodyEnabled"
value={this.state.desktopShowBody}
onChange={this.onDesktopShowBodyChanged}
label={_t("Show message in desktop notification")}
label={_t("settings|notifications|show_message_desktop_notification")}
disabled={this.state.phase === Phase.Persisting}
/>
<LabelledToggleSwitch
data-testid="notif-setting-audioNotificationsEnabled"
value={this.state.audioNotifications}
onChange={this.onAudioNotificationsChanged}
label={_t("Enable audible notifications for this session")}
label={_t("settings|notifications|enable_audible_notifications_session")}
disabled={this.state.phase === Phase.Persisting}
/>
</>

View file

@ -396,7 +396,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
);
}
} else {
sectionTitle = _t("Identity server");
sectionTitle = _t("common|identity_server");
bodyText = _t(
"You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.",
);

View file

@ -141,18 +141,25 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
// XXX: need some schema for this
const themeInfo = await r.json();
if (!themeInfo || typeof themeInfo["name"] !== "string" || typeof themeInfo["colors"] !== "object") {
this.setState({ customThemeMessage: { text: _t("Invalid theme schema."), isError: true } });
this.setState({
customThemeMessage: { text: _t("settings|appearance|custom_theme_invalid"), isError: true },
});
return;
}
currentThemes.push(themeInfo);
} catch (e) {
logger.error(e);
this.setState({ customThemeMessage: { text: _t("Error downloading theme information."), isError: true } });
this.setState({
customThemeMessage: { text: _t("settings|appearance|custom_theme_error_downloading"), isError: true },
});
return; // Don't continue on error
}
await SettingsStore.setValue("custom_themes", null, SettingLevel.ACCOUNT, currentThemes);
this.setState({ customThemeUrl: "", customThemeMessage: { text: _t("Theme added!"), isError: false } });
this.setState({
customThemeUrl: "",
customThemeMessage: { text: _t("settings|appearance|custom_theme_success"), isError: false },
});
this.themeTimer = window.setTimeout(() => {
this.setState({ customThemeMessage: { text: "", isError: false } });
@ -174,7 +181,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
checked={isHighContrastTheme(this.state.theme)}
onChange={(e) => this.highContrastThemeChanged(e.target.checked)}
>
{_t("Use high contrast")}
{_t("settings|appearance|use_high_contrast")}
</StyledCheckbox>
</div>
);
@ -223,7 +230,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
<div className="mx_SettingsTab_section">
<form onSubmit={this.onAddCustomTheme}>
<Field
label={_t("Custom theme URL")}
label={_t("settings|appearance|custom_theme_url")}
type="text"
id="mx_GeneralUserSettingsTab_customThemeInput"
autoComplete="off"
@ -236,7 +243,7 @@ export default class ThemeChoicePanel extends React.Component<IProps, IState> {
kind="primary_sm"
disabled={!this.state.customThemeUrl.trim()}
>
{_t("Add theme")}
{_t("settings|appearance|custom_theme_add_button")}
</AccessibleButton>
{messageElement}
</form>

View file

@ -122,7 +122,7 @@ export default function NotificationSettings2(): JSX.Element {
<SettingsSection heading={_t("Notifications")}>
<div className="mx_SettingsSubsection_content mx_NotificationSettings2_flags">
<LabelledToggleSwitch
label={_t("Enable notifications for this account")}
label={_t("settings|notifications|enable_notifications_account")}
value={!settings.globalMute}
disabled={disabled}
onChange={(value) => {
@ -133,7 +133,7 @@ export default function NotificationSettings2(): JSX.Element {
}}
/>
<LabelledToggleSwitch
label={_t("Enable desktop notifications for this session")}
label={_t("settings|notifications|enable_desktop_notifications_session")}
value={desktopNotifications}
onChange={(value) =>
SettingsStore.setValue("notificationsEnabled", null, SettingLevel.DEVICE, value)
@ -147,7 +147,7 @@ export default function NotificationSettings2(): JSX.Element {
}
/>
<LabelledToggleSwitch
label={_t("Enable audible notifications for this session")}
label={_t("settings|notifications|enable_audible_notifications_session")}
value={audioNotifications}
onChange={(value) =>
SettingsStore.setValue("audioNotificationsEnabled", null, SettingLevel.DEVICE, value)

View file

@ -106,10 +106,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
let advanced: React.ReactNode;
if (this.state.showAdvanced) {
const tooltipContent = _t(
"Set the name of a font installed on your system & %(brand)s will attempt to use it.",
{ brand },
);
const tooltipContent = _t("settings|appearance|custom_font_description", { brand });
advanced = (
<>
<SettingsFlag name="useCompactLayout" level={SettingLevel.DEVICE} useCheckbox={true} />
@ -151,10 +148,8 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
return (
<SettingsTab data-testid="mx_AppearanceUserSettingsTab">
<SettingsSection heading={_t("Customise your appearance")}>
<SettingsSubsectionText>
{_t("Appearance Settings only affect this %(brand)s session.", { brand })}
</SettingsSubsectionText>
<SettingsSection heading={_t("settings|appearance|heading")}>
<SettingsSubsectionText>{_t("settings|appearance|subheading", { brand })}</SettingsSubsectionText>
<ThemeChoicePanel />
<LayoutSwitcher
userId={this.state.userId}

View file

@ -27,7 +27,7 @@ export default class VerificationCancelled extends React.Component<IProps> {
public render(): React.ReactNode {
return (
<div>
<p>{_t("The other party cancelled the verification.")}</p>
<p>{_t("encryption|verification|other_party_cancelled")}</p>
<DialogButtons
primaryButton={_t("action|ok")}
hasCancel={false}

View file

@ -27,8 +27,8 @@ export default class VerificationComplete extends React.Component<IProps> {
public render(): React.ReactNode {
return (
<div>
<h2>{_t("Verified!")}</h2>
<p>{_t("You've successfully verified this user.")}</p>
<h2>{_t("encryption|verification|complete_title")}</h2>
<p>{_t("encryption|verification|complete_description")}</p>
<p>
{_t(
"Secure messages with this user are end-to-end encrypted and not able to be read by third parties.",

View file

@ -180,10 +180,10 @@ export default class VerificationShowSas extends React.Component<IProps, IState>
confirm = (
<div className="mx_VerificationShowSas_buttonRow">
<AccessibleButton onClick={this.onDontMatchClick} kind="danger">
{_t("They don't match")}
{_t("encryption|verification|sas_no_match")}
</AccessibleButton>
<AccessibleButton onClick={this.onMatchClick} kind="primary">
{_t("They match")}
{_t("encryption|verification|sas_match")}
</AccessibleButton>
</div>
);
@ -193,11 +193,7 @@ export default class VerificationShowSas extends React.Component<IProps, IState>
<div className="mx_VerificationShowSas">
<p>{sasCaption}</p>
{sasDisplay}
<p>
{this.props.isSelf
? ""
: _t("To be secure, do this in person or use a trusted way to communicate.")}
</p>
<p>{this.props.isSelf ? "" : _t("encryption|verification|in_person")}</p>
{confirm}
</div>
);