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

# Conflicts:
#	src/i18n/strings/en_EN.json
#	test/components/views/rooms/__snapshots__/RoomHeader-test.tsx.snap
This commit is contained in:
Johannes Marbach 2023-09-04 14:08:50 +02:00
commit 44d4de14c9
143 changed files with 7371 additions and 6803 deletions

View file

@ -57,6 +57,7 @@ interface RoomlessProps extends BaseProps {
interface RoomProps extends BaseProps {
room: Room;
permalinkCreator: RoomPermalinkCreator;
onSearchClick?: () => void;
}
type Props = XOR<RoomlessProps, RoomProps>;
@ -293,6 +294,7 @@ export default class RightPanel extends React.Component<Props, IState> {
onClose={this.onClose}
// whenever RightPanel is passed a room it is passed a permalinkcreator
permalinkCreator={this.props.permalinkCreator!}
onSearchClick={this.props.onSearchClick}
/>
);
}

View file

@ -2443,6 +2443,7 @@ export class RoomView extends React.Component<IRoomProps, IRoomState> {
resizeNotifier={this.props.resizeNotifier}
permalinkCreator={this.permalinkCreator}
e2eStatus={this.state.e2eStatus}
onSearchClick={this.onSearchClick}
/>
) : undefined;

View file

@ -160,14 +160,14 @@ export default class ViewSource extends React.Component<IProps, IState> {
<BaseDialog className="mx_ViewSource" onFinished={this.props.onFinished} title={_t("action|view_source")}>
<div className="mx_ViewSource_header">
<CopyableText getTextToCopy={() => roomId} border={false}>
{_t("Room ID: %(roomId)s", { roomId })}
{_t("devtools|room_id", { roomId })}
</CopyableText>
<CopyableText getTextToCopy={() => eventId} border={false}>
{_t("Event ID: %(eventId)s", { eventId })}
{_t("devtools|event_id", { eventId })}
</CopyableText>
{mxEvent.threadRootId && (
<CopyableText getTextToCopy={() => mxEvent.threadRootId!} border={false}>
{_t("Thread root ID: %(threadRootId)s", {
{_t("devtools|thread_root_id", {
threadRootId: mxEvent.threadRootId,
})}
</CopyableText>

View file

@ -684,7 +684,7 @@ export class MsisdnAuthEntry extends React.Component<IMsisdnAuthEntryProps, IMsi
<br />
<input
type="submit"
value={_t("Submit")}
value={_t("action|submit")}
className={submitClasses}
disabled={!enableSubmit}
/>

View file

@ -99,7 +99,7 @@ const BaseAvatar: React.FC<IProps> = (props) => {
const {
name,
idName,
title = "",
title,
url,
urls,
size = "40px",

View file

@ -68,7 +68,7 @@ export const AppDownloadDialog: FC<Props> = ({ onFinished }) => {
)}
<div className="mx_AppDownloadDialog_mobile">
<div className="mx_AppDownloadDialog_app">
<Heading size="3">{_t("iOS")}</Heading>
<Heading size="3">{_t("common|ios")}</Heading>
<QRCode data={urlAppStore} margin={0} width={172} />
<div className="mx_AppDownloadDialog_info">
{_t("%(qrCode)s or %(appLinks)s", {
@ -89,7 +89,7 @@ export const AppDownloadDialog: FC<Props> = ({ onFinished }) => {
</div>
</div>
<div className="mx_AppDownloadDialog_app">
<Heading size="3">{_t("Android")}</Heading>
<Heading size="3">{_t("common|android")}</Heading>
<QRCode data={urlAndroid} margin={0} width={172} />
<div className="mx_AppDownloadDialog_info">
{_t("%(qrCode)s or %(appLinks)s", {

View file

@ -48,18 +48,18 @@ const categoryLabels: Record<Category, TranslationKey> = {
export type Tool = React.FC<IDevtoolsProps> | ((props: IDevtoolsProps) => JSX.Element);
const Tools: Record<Category, [label: TranslationKey, tool: Tool][]> = {
[Category.Room]: [
[_td("Send custom timeline event"), TimelineEventEditor],
[_td("Explore room state"), RoomStateExplorer],
[_td("Explore room account data"), RoomAccountDataExplorer],
[_td("View servers in room"), ServersInRoom],
[_td("Notifications debug"), RoomNotifications],
[_td("Verification explorer"), VerificationExplorer],
[_td("Active Widgets"), WidgetExplorer],
[_td("devtools|send_custom_timeline_event"), TimelineEventEditor],
[_td("devtools|explore_room_state"), RoomStateExplorer],
[_td("devtools|explore_room_account_data"), RoomAccountDataExplorer],
[_td("devtools|view_servers_in_room"), ServersInRoom],
[_td("devtools|notifications_debug"), RoomNotifications],
[_td("devtools|verification_explorer"), VerificationExplorer],
[_td("devtools|active_widgets"), WidgetExplorer],
],
[Category.Other]: [
[_td("Explore account data"), AccountDataExplorer],
[_td("Settings explorer"), SettingExplorer],
[_td("Server info"), ServerInfo],
[_td("devtools|explore_account_data"), AccountDataExplorer],
[_td("devtools|settings_explorer"), SettingExplorer],
[_td("devtools|server_info"), ServerInfo],
],
};
@ -116,15 +116,15 @@ const DevtoolsDialog: React.FC<IProps> = ({ roomId, threadRootId, onFinished })
);
}
const label = tool ? tool[0] : _t("Toolbox");
const label = tool ? tool[0] : _t("devtools|toolbox");
return (
<BaseDialog className="mx_QuestionDialog" onFinished={onFinished} title={_t("Developer Tools")}>
<BaseDialog className="mx_QuestionDialog" onFinished={onFinished} title={_t("devtools|developer_tools")}>
<MatrixClientContext.Consumer>
{(cli) => (
<>
<div className="mx_DevTools_label_left">{label}</div>
<CopyableText className="mx_DevTools_label_right" getTextToCopy={() => roomId} border={false}>
{_t("Room ID: %(roomId)s", { roomId })}
{_t("devtools|room_id", { roomId })}
</CopyableText>
{!threadRootId ? null : (
<CopyableText
@ -132,7 +132,7 @@ const DevtoolsDialog: React.FC<IProps> = ({ roomId, threadRootId, onFinished })
getTextToCopy={() => threadRootId}
border={false}
>
{_t("Thread Root ID: %(threadRootId)s", { threadRootId })}
{_t("devtools|thread_root_id", { threadRootId })}
</CopyableText>
)}
<div className="mx_DevTools_label_bottom" />

View file

@ -410,7 +410,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
</div>
) : (
<DialogButtons
primaryButton={_t("Export")}
primaryButton={_t("action|export")}
onPrimaryButtonClick={onExportClick}
onCancel={() => onFinished(false)}
/>

View file

@ -1313,7 +1313,7 @@ export default class InviteDialog extends React.PureComponent<Props, IInviteDial
);
}
buttonText = _t("Go");
buttonText = _t("action|go");
goButtonFn = this.checkProfileAndStartDm;
extraSection = (
<div className="mx_InviteDialog_section_hidden_suggestions_disclaimer">

View file

@ -428,7 +428,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
{ignoreUserCheckbox}
</div>
<DialogButtons
primaryButton={_t("Send report")}
primaryButton={_t("action|send_report")}
onPrimaryButtonClick={this.onSubmit}
focus={true}
onCancel={this.onCancel}
@ -467,7 +467,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
{ignoreUserCheckbox}
</div>
<DialogButtons
primaryButton={_t("Send report")}
primaryButton={_t("action|send_report")}
onPrimaryButtonClick={this.onSubmit}
focus={true}
onCancel={this.onCancel}

View file

@ -78,7 +78,7 @@ export default class SessionRestoreErrorDialog extends React.Component<IProps> {
} else {
dialogButtons = (
<DialogButtons
primaryButton={_t("Refresh")}
primaryButton={_t("action|refresh")}
onPrimaryButtonClick={this.onRefreshClick}
focus={true}
hasCancel={false}

View file

@ -98,7 +98,7 @@ export const AccountDataExplorer: React.FC<IDevtoolsProps> = ({ onBack, setTool
<BaseAccountDataExplorer
events={cli.store.accountData}
Editor={AccountDataEventEditor}
actionLabel={_t("Send custom account data event")}
actionLabel={_t("devtools|send_custom_account_data_event")}
onBack={onBack}
setTool={setTool}
/>
@ -112,7 +112,7 @@ export const RoomAccountDataExplorer: React.FC<IDevtoolsProps> = ({ onBack, setT
<BaseAccountDataExplorer
events={context.room.accountData}
Editor={RoomAccountDataEventEditor}
actionLabel={_t("Send custom room account data event")}
actionLabel={_t("devtools|send_custom_room_account_data_event")}
onBack={onBack}
setTool={setTool}
/>

View file

@ -43,13 +43,13 @@ interface IFieldDef {
export const eventTypeField = (defaultValue?: string): IFieldDef => ({
id: "eventType",
label: _td("Event Type"),
label: _td("devtools|event_type"),
default: defaultValue,
});
export const stateKeyField = (defaultValue?: string): IFieldDef => ({
id: "stateKey",
label: _td("State Key"),
label: _td("devtools|state_key"),
default: defaultValue,
});
@ -69,7 +69,7 @@ const validateEventContent = withValidation<any, Error | undefined>({
if (!value) return true;
return !error;
},
invalid: (error) => _t("Doesn't look like valid JSON.") + " " + error,
invalid: (error) => _t("devtools|invalid_json") + " " + error,
},
],
});
@ -111,9 +111,9 @@ export const EventEditor: React.FC<IEventEditorProps> = ({ fieldDefs, defaultCon
const json = JSON.parse(content);
await onSend(fieldData, json);
} catch (e) {
return _t("Failed to send event!") + (e instanceof Error ? ` (${e.message})` : "");
return _t("devtools|failed_to_send") + (e instanceof Error ? ` (${e.message})` : "");
}
return _t("Event sent!");
return _t("devtools|event_sent");
};
return (
@ -122,7 +122,7 @@ export const EventEditor: React.FC<IEventEditorProps> = ({ fieldDefs, defaultCon
<Field
id="evContent"
label={_t("Event Content")}
label={_t("devtools|event_content")}
type="text"
className="mx_DevTools_textarea"
autoComplete="off"

View file

@ -33,27 +33,29 @@ function UserReadUpTo({ target }: { target: ReadReceipt<any, any> }): JSX.Elemen
return (
<>
<li>
{_t("User read up to: ")}
<strong>{target.getReadReceiptForUserId(userId)?.eventId ?? _t("No receipt found")}</strong>
{_t("devtools|user_read_up_to")}
<strong>{target.getReadReceiptForUserId(userId)?.eventId ?? _t("devtools|no_receipt_found")}</strong>
</li>
<li>
{_t("User read up to (ignoreSynthetic): ")}
<strong>{target.getReadReceiptForUserId(userId, true)?.eventId ?? _t("No receipt found")}</strong>
{_t("devtools|user_read_up_to_ignore_synthetic")}
<strong>
{target.getReadReceiptForUserId(userId, true)?.eventId ?? _t("devtools|no_receipt_found")}
</strong>
</li>
{hasPrivate && (
<>
<li>
{_t("User read up to (m.read.private): ")}
{_t("devtools|user_read_up_to_private")}
<strong>
{target.getReadReceiptForUserId(userId, false, ReceiptType.ReadPrivate)?.eventId ??
_t("No receipt found")}
_t("devtools|no_receipt_found")}
</strong>
</li>
<li>
{_t("User read up to (m.read.private;ignoreSynthetic): ")}
{_t("devtools|user_read_up_to_private_ignore_synthetic")}
<strong>
{target.getReadReceiptForUserId(userId, true, ReceiptType.ReadPrivate)?.eventId ??
_t("No receipt found")}
_t("devtools|no_receipt_found")}
</strong>
</li>
</>
@ -72,12 +74,12 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
return (
<BaseTool onBack={onBack}>
<section>
<h2>{_t("Room status")}</h2>
<h2>{_t("devtools|room_status")}</h2>
<ul>
<li>
{count > 0
? _t(
"Room unread status: <strong>%(status)s</strong>, count: <strong>%(count)s</strong>",
"devtools|room_unread_status_count",
{
status: humanReadableNotificationColor(color),
count,
@ -87,7 +89,7 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
},
)
: _t(
"Room unread status: <strong>%(status)s</strong>",
"devtools|room_unread_status",
{
status: humanReadableNotificationColor(color),
},
@ -98,7 +100,7 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
</li>
<li>
{_t(
"Notification state is <strong>%(notificationState)s</strong>",
"devtools|notification_state",
{
notificationState,
},
@ -110,8 +112,8 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
<li>
{_t(
cli.isRoomEncrypted(room.roomId!)
? _td("Room is <strong>encrypted ✅</strong>")
: _td("Room is <strong>not encrypted 🚨</strong>"),
? _td("devtools|room_encrypted")
: _td("devtools|room_not_encrypted"),
{},
{
strong: (sub) => <strong>{sub}</strong>,
@ -122,33 +124,36 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
</section>
<section>
<h2>{_t("Main timeline")}</h2>
<h2>{_t("devtools|main_timeline")}</h2>
<ul>
<li>
{_t("Total: ")} {room.getRoomUnreadNotificationCount(NotificationCountType.Total)}
{_t("devtools|room_notifications_total")}{" "}
{room.getRoomUnreadNotificationCount(NotificationCountType.Total)}
</li>
<li>
{_t("Highlight: ")} {room.getRoomUnreadNotificationCount(NotificationCountType.Highlight)}
{_t("devtools|room_notifications_highlight")}{" "}
{room.getRoomUnreadNotificationCount(NotificationCountType.Highlight)}
</li>
<li>
{_t("Dot: ")} {doesRoomOrThreadHaveUnreadMessages(room) + ""}
{_t("devtools|room_notifications_dot")} {doesRoomOrThreadHaveUnreadMessages(room) + ""}
</li>
{roomHasUnread(room) && (
<>
<UserReadUpTo target={room} />
<li>
{_t("Last event:")}
{_t("devtools|room_notifications_last_event")}
<ul>
<li>
{_t("ID: ")} <strong>{room.timeline[room.timeline.length - 1].getId()}</strong>
{_t("devtools|id")}{" "}
<strong>{room.timeline[room.timeline.length - 1].getId()}</strong>
</li>
<li>
{_t("Type: ")}{" "}
{_t("devtools|room_notifications_type")}{" "}
<strong>{room.timeline[room.timeline.length - 1].getType()}</strong>
</li>
<li>
{_t("Sender: ")}{" "}
{_t("devtools|room_notifications_sender")}{" "}
<strong>{room.timeline[room.timeline.length - 1].getSender()}</strong>
</li>
</ul>
@ -159,17 +164,17 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
</section>
<section>
<h2>{_t("Threads timeline")}</h2>
<h2>{_t("devtools|threads_timeline")}</h2>
<ul>
{room
.getThreads()
.filter((thread) => threadHasUnread(thread))
.map((thread) => (
<li key={thread.id}>
{_t("Thread Id: ")} {thread.id}
{_t("devtools|room_notifications_thread_id")} {thread.id}
<ul>
<li>
{_t("Total: ")}
{_t("devtools|room_notifications_total")}
<strong>
{room.getThreadUnreadNotificationCount(
thread.id,
@ -178,7 +183,7 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
</strong>
</li>
<li>
{_t("Highlight: ")}
{_t("devtools|room_notifications_highlight")}
<strong>
{room.getThreadUnreadNotificationCount(
thread.id,
@ -187,20 +192,23 @@ export default function RoomNotifications({ onBack }: IDevtoolsProps): JSX.Eleme
</strong>
</li>
<li>
{_t("Dot: ")} <strong>{doesRoomOrThreadHaveUnreadMessages(thread) + ""}</strong>
{_t("devtools|room_notifications_dot")}{" "}
<strong>{doesRoomOrThreadHaveUnreadMessages(thread) + ""}</strong>
</li>
<UserReadUpTo target={thread} />
<li>
{_t("Last event:")}
{_t("devtools|room_notifications_last_event")}
<ul>
<li>
{_t("ID: ")} <strong>{thread.lastReply()?.getId()}</strong>
{_t("devtools|id")} <strong>{thread.lastReply()?.getId()}</strong>
</li>
<li>
{_t("Type: ")} <strong>{thread.lastReply()?.getType()}</strong>
{_t("devtools|room_notifications_type")}{" "}
<strong>{thread.lastReply()?.getType()}</strong>
</li>
<li>
{_t("Sender: ")} <strong>{thread.lastReply()?.getSender()}</strong>
{_t("devtools|room_notifications_sender")}{" "}
<strong>{thread.lastReply()?.getSender()}</strong>
</li>
</ul>
</li>

View file

@ -97,7 +97,7 @@ const StateEventButton: React.FC<StateEventButtonProps> = ({ label, onClick }) =
let content = label;
if (!trimmed) {
content = label.length > 0 ? _t("<%(count)s spaces>", { count: label.length }) : _t("<empty string>");
content = label.length > 0 ? _t("devtools|spaces", { count: label.length }) : _t("devtools|empty_string");
}
return (
@ -150,7 +150,7 @@ const RoomStateExplorerEventType: React.FC<IEventTypeProps> = ({ eventType, onBa
const onHistoryClick = (): void => {
setHistory(true);
};
const extraButton = <button onClick={onHistoryClick}>{_t("See history")}</button>;
const extraButton = <button onClick={onHistoryClick}>{_t("devtools|see_history")}</button>;
return <EventViewer mxEvent={event} onBack={_onBack} Editor={StateEventEditor} extraButton={extraButton} />;
}
@ -180,11 +180,11 @@ export const RoomStateExplorer: React.FC<IDevtoolsProps> = ({ onBack, setTool })
}
const onAction = async (): Promise<void> => {
setTool(_t("Send custom state event"), StateEventEditor);
setTool(_t("devtools|send_custom_state_event"), StateEventEditor);
};
return (
<BaseTool onBack={onBack} actionLabel={_t("Send custom state event")} onAction={onAction}>
<BaseTool onBack={onBack} actionLabel={_t("devtools|send_custom_state_event")} onAction={onAction}>
<FilteredList query={query} onChange={setQuery}>
{Array.from(events.keys()).map((eventType) => (
<StateEventButton key={eventType} label={eventType} onClick={() => setEventType(eventType)} />

View file

@ -73,25 +73,25 @@ const ServerInfo: React.FC<IDevtoolsProps> = ({ onBack }) => {
} else {
body = (
<>
<h4>{_t("Capabilities")}</h4>
<h4>{_t("common|capabilities")}</h4>
{capabilities !== FAILED_TO_LOAD ? (
<SyntaxHighlight language="json" children={JSON.stringify(capabilities, null, 4)} />
) : (
<div>{_t("Failed to load.")}</div>
<div>{_t("devtools|failed_to_load")}</div>
)}
<h4>{_t("Client Versions")}</h4>
<h4>{_t("devtools|client_versions")}</h4>
{clientVersions !== FAILED_TO_LOAD ? (
<SyntaxHighlight language="json" children={JSON.stringify(clientVersions, null, 4)} />
) : (
<div>{_t("Failed to load.")}</div>
<div>{_t("devtools|failed_to_load")}</div>
)}
<h4>{_t("Server Versions")}</h4>
<h4>{_t("devtools|server_versions")}</h4>
{serverVersions !== FAILED_TO_LOAD ? (
<SyntaxHighlight language="json" children={JSON.stringify(serverVersions, null, 4)} />
) : (
<div>{_t("Failed to load.")}</div>
<div>{_t("devtools|failed_to_load")}</div>
)}
</>
);

View file

@ -39,8 +39,8 @@ const ServersInRoom: React.FC<IDevtoolsProps> = ({ onBack }) => {
<table>
<thead>
<tr>
<th>{_t("Server")}</th>
<th>{_t("Number of users")}</th>
<th>{_t("common|server")}</th>
<th>{_t("devtools|number_of_users")}</th>
</tr>
</thead>
<tbody>

View file

@ -125,22 +125,22 @@ const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
}
onBack();
} catch (e) {
return _t("Failed to save settings.") + (e instanceof Error ? ` (${e.message})` : "");
return _t("devtools|failed_to_save") + (e instanceof Error ? ` (${e.message})` : "");
}
};
return (
<BaseTool onBack={onBack} actionLabel={_t("Save setting values")} onAction={onSave}>
<BaseTool onBack={onBack} actionLabel={_t("devtools|save_setting_values")} onAction={onSave}>
<h3>
{_t("Setting:")} <code>{setting}</code>
{_t("devtools|setting_colon")} <code>{setting}</code>
</h3>
<div className="mx_DevTools_SettingsExplorer_warning">
<b>{_t("Caution:")}</b> {_t("This UI does NOT check the types of the values. Use at your own risk.")}
<b>{_t("devtools|caution_colon")}</b> {_t("devtools|use_at_own_risk")}
</div>
<div>
{_t("Setting definition:")}
{_t("devtools|setting_definition")}
<pre>
<code>{JSON.stringify(SETTINGS[setting], null, 4)}</code>
</pre>
@ -150,9 +150,9 @@ const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
<table>
<thead>
<tr>
<th>{_t("Level")}</th>
<th>{_t("Settable at global")}</th>
<th>{_t("Settable at room")}</th>
<th>{_t("devtools|level")}</th>
<th>{_t("devtools|settable_global")}</th>
<th>{_t("devtools|settable_room")}</th>
</tr>
</thead>
<tbody>
@ -172,7 +172,7 @@ const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
<div>
<Field
id="valExpl"
label={_t("Values at explicit levels")}
label={_t("devtools|values_explicit")}
type="text"
className="mx_DevTools_textarea"
element="textarea"
@ -185,7 +185,7 @@ const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
<div>
<Field
id="valExpl"
label={_t("Values at explicit levels in this room")}
label={_t("devtools|values_explicit_room")}
type="text"
className="mx_DevTools_textarea"
element="textarea"
@ -207,37 +207,37 @@ const ViewSetting: React.FC<IViewSettingProps> = ({ setting, onEdit, onBack }) =
const context = useContext(DevtoolsContext);
return (
<BaseTool onBack={onBack} actionLabel={_t("Edit values")} onAction={onEdit}>
<BaseTool onBack={onBack} actionLabel={_t("devtools|edit_values")} onAction={onEdit}>
<h3>
{_t("Setting:")} <code>{setting}</code>
{_t("devtools|setting_colon")} <code>{setting}</code>
</h3>
<div>
{_t("Setting definition:")}
{_t("devtools|setting_definition")}
<pre>
<code>{JSON.stringify(SETTINGS[setting], null, 4)}</code>
</pre>
</div>
<div>
{_t("Value:")}&nbsp;
{_t("devtools|value_colon")}&nbsp;
<code>{renderSettingValue(SettingsStore.getValue(setting))}</code>
</div>
<div>
{_t("Value in this room:")}&nbsp;
{_t("devtools|value_this_room_colon")}&nbsp;
<code>{renderSettingValue(SettingsStore.getValue(setting, context.room.roomId))}</code>
</div>
<div>
{_t("Values at explicit levels:")}
{_t("devtools|values_explicit_colon")}
<pre>
<code>{renderExplicitSettingValues(setting)}</code>
</pre>
</div>
<div>
{_t("Values at explicit levels in this room:")}
{_t("devtools|values_explicit_this_room_colon")}
<pre>
<code>{renderExplicitSettingValues(setting, context.room.roomId)}</code>
</pre>
@ -289,9 +289,9 @@ const SettingsList: React.FC<ISettingsListProps> = ({ onBack, onView, onEdit })
<table>
<thead>
<tr>
<th>{_t("Setting ID")}</th>
<th>{_t("Value")}</th>
<th>{_t("Value in this room")}</th>
<th>{_t("devtools|setting_id")}</th>
<th>{_t("devtools|value")}</th>
<th>{_t("devtools|value_in_this_room")}</th>
</tr>
</thead>
<tbody>
@ -306,7 +306,7 @@ const SettingsList: React.FC<ISettingsListProps> = ({ onBack, onView, onEdit })
<code>{i}</code>
</AccessibleButton>
<AccessibleButton
alt={_t("Edit setting")}
alt={_t("devtools|edit_setting")}
onClick={() => onEdit(i)}
className="mx_DevTools_SettingsExplorer_edit"
>

View file

@ -28,11 +28,11 @@ import { Tool } from "../DevtoolsDialog";
const PHASE_MAP: Record<Phase, TranslationKey> = {
[Phase.Unsent]: _td("Unsent"),
[Phase.Requested]: _td("Requested"),
[Phase.Ready]: _td("Ready"),
[Phase.Requested]: _td("devtools|phase_requested"),
[Phase.Ready]: _td("devtools|phase_ready"),
[Phase.Done]: _td("action|done"),
[Phase.Started]: _td("Started"),
[Phase.Cancelled]: _td("Cancelled"),
[Phase.Started]: _td("devtools|phase_started"),
[Phase.Cancelled]: _td("devtools|phase_cancelled"),
};
const VerificationRequestExplorer: React.FC<{
@ -62,17 +62,17 @@ const VerificationRequestExplorer: React.FC<{
return (
<div className="mx_DevTools_VerificationRequest">
<dl>
<dt>{_t("Transaction")}</dt>
<dt>{_t("devtools|phase_transaction")}</dt>
<dd>{txnId}</dd>
<dt>{_t("Phase")}</dt>
<dt>{_t("devtools|phase")}</dt>
<dd>{PHASE_MAP[request.phase] ? _t(PHASE_MAP[request.phase]) : request.phase}</dd>
<dt>{_t("Timeout")}</dt>
<dt>{_t("devtools|timeout")}</dt>
<dd>{Math.floor(timeout / 1000)}</dd>
<dt>{_t("Methods")}</dt>
<dt>{_t("devtools|methods")}</dt>
<dd>{request.methods && request.methods.join(", ")}</dd>
<dt>{_t("Requester")}</dt>
<dt>{_t("devtools|requester")}</dt>
<dd>{request.requestingUserId}</dd>
<dt>{_t("Observe only")}</dt>
<dt>{_t("devtools|observe_only")}</dt>
<dd>{JSON.stringify(request.observeOnly)}</dd>
</dl>
</div>
@ -97,7 +97,7 @@ const VerificationExplorer: Tool = ({ onBack }: IDevtoolsProps) => {
.map(([txnId, request]) => (
<VerificationRequestExplorer txnId={txnId} request={request} key={txnId} />
))}
{requests.size < 1 && _t("No verification requests found")}
{requests.size < 1 && _t("devtools|no_verification_requests_found")}
</BaseTool>
);
};

View file

@ -51,7 +51,7 @@ const WidgetExplorer: React.FC<IDevtoolsProps> = ({ onBack }) => {
const event = allState.find((ev) => ev.getId() === widget.eventId);
if (!event) {
// "should never happen"
return <BaseTool onBack={onBack}>{_t("There was an error finding this widget.")}</BaseTool>;
return <BaseTool onBack={onBack}>{_t("devtools|failed_to_find_widget")}</BaseTool>;
}
return <StateEventEditor mxEvent={event} onBack={onBack} />;

View file

@ -1038,7 +1038,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
<h4>
<span id="mx_SpotlightDialog_section_recentSearches">{_t("Recent searches")}</span>
<AccessibleButton kind="link" onClick={clearRecentSearches}>
{_t("Clear")}
{_t("action|clear")}
</AccessibleButton>
</h4>
<div>

View file

@ -155,7 +155,7 @@ export const NetworkDropdown: React.FC<IProps> = ({ protocols, config, setConfig
options: [
{
key: { roomServer, instanceId: undefined },
label: _t("Matrix"),
label: _t("common|matrix"),
},
...(roomServer === homeServer && protocols
? Object.values(protocols)

View file

@ -737,7 +737,7 @@ export default class AppTile extends React.Component<IProps, IState> {
<AccessibleButton
key="toggleMaximised"
className="mx_AppTileMenuBar_widgets_button"
title={isMaximised ? _t("Un-maximise") : _t("Maximise")}
title={isMaximised ? _t("Un-maximise") : _t("action|maximise")}
onClick={this.onToggleMaximisedClick}
>
{isMaximised ? (
@ -752,7 +752,7 @@ export default class AppTile extends React.Component<IProps, IState> {
<AccessibleButton
key="minimise"
className="mx_AppTileMenuBar_widgets_button"
title={_t("Minimise")}
title={_t("action|minimise")}
onClick={this.onMinimiseClicked}
>
<MinimiseIcon className="mx_Icon mx_Icon_12" />

View file

@ -18,6 +18,7 @@ limitations under the License.
import React from "react";
import { Direction, ConnectionError, MatrixError, HTTPError } from "matrix-js-sdk/src/matrix";
import { logger } from "matrix-js-sdk/src/logger";
import { capitalize } from "lodash";
import { _t, getUserLanguage } from "../../../languageHandler";
import { formatFullDateNoDay, formatFullDateNoTime, getDaysArray } from "../../../DateUtils";
@ -92,6 +93,10 @@ export default class DateSeparator extends React.Component<IProps, IState> {
});
};
private get relativeTimeFormat(): Intl.RelativeTimeFormat {
return new Intl.RelativeTimeFormat(getUserLanguage(), { style: "long", numeric: "auto" });
}
private getLabel(): string {
const date = new Date(this.props.ts);
const disableRelativeTimestamps = !SettingsStore.getValue(UIFeature.TimelineEnableRelativeDates);
@ -104,11 +109,10 @@ export default class DateSeparator extends React.Component<IProps, IState> {
const days = getDaysArray("long");
yesterday.setDate(today.getDate() - 1);
const relativeTimeFormat = new Intl.RelativeTimeFormat(getUserLanguage(), { style: "long", numeric: "auto" });
if (date.toDateString() === today.toDateString()) {
return relativeTimeFormat.format(0, "day"); // Today
return this.relativeTimeFormat.format(0, "day"); // Today
} else if (date.toDateString() === yesterday.toDateString()) {
return relativeTimeFormat.format(-1, "day"); // Yesterday
return this.relativeTimeFormat.format(-1, "day"); // Yesterday
} else if (today.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) {
return days[date.getDay()]; // Sunday-Saturday
} else {
@ -263,6 +267,7 @@ export default class DateSeparator extends React.Component<IProps, IState> {
private renderJumpToDateMenu(): React.ReactElement {
let contextMenu: JSX.Element | undefined;
if (this.state.contextMenuPosition) {
const relativeTimeFormat = this.relativeTimeFormat;
contextMenu = (
<IconizedContextMenu
{...contextMenuBelow(this.state.contextMenuPosition)}
@ -270,12 +275,12 @@ export default class DateSeparator extends React.Component<IProps, IState> {
>
<IconizedContextMenuOptionList first>
<IconizedContextMenuOption
label={_t("Last week")}
label={capitalize(relativeTimeFormat.format(-1, "week"))}
onClick={this.onLastWeekClicked}
data-testid="jump-to-date-last-week"
/>
<IconizedContextMenuOption
label={_t("Last month")}
label={capitalize(relativeTimeFormat.format(-1, "month"))}
onClick={this.onLastMonthClicked}
data-testid="jump-to-date-last-month"
/>

View file

@ -63,7 +63,7 @@ const JumpToDatePicker: React.FC<IProps> = ({ ts, onDatePicked }: IProps) => {
className="mx_JumpToDatePicker_submitButton"
onClick={onJumpToDateSubmit}
>
{_t("Go")}
{_t("action|go")}
</RovingAccessibleButton>
</form>
);

View file

@ -18,6 +18,7 @@ import React, { SyntheticEvent } from "react";
import classNames from "classnames";
import { MatrixEvent, MatrixEventEvent, Relations, RelationsEvent } from "matrix-js-sdk/src/matrix";
import { uniqBy } from "lodash";
import { UnstableValue } from "matrix-js-sdk/src/NamespacedValue";
import { _t } from "../../../languageHandler";
import { isContentActionable } from "../../../utils/EventUtils";
@ -27,10 +28,13 @@ import ReactionPicker from "../emojipicker/ReactionPicker";
import ReactionsRowButton from "./ReactionsRowButton";
import RoomContext from "../../../contexts/RoomContext";
import AccessibleButton from "../elements/AccessibleButton";
import SettingsStore from "../../../settings/SettingsStore";
// The maximum number of reactions to initially show on a message.
const MAX_ITEMS_WHEN_LIMITED = 8;
export const REACTION_SHORTCODE_KEY = new UnstableValue("shortcode", "com.beeper.reaction.shortcode");
const ReactButton: React.FC<IProps> = ({ mxEvent, reactions }) => {
const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
@ -169,6 +173,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
if (!reactions || !isContentActionable(mxEvent)) {
return null;
}
const customReactionImagesEnabled = SettingsStore.getValue("feature_render_reaction_images");
let items = reactions
.getSortedAnnotationsByKey()
@ -195,6 +200,7 @@ export default class ReactionsRow extends React.PureComponent<IProps, IState> {
mxEvent={mxEvent}
reactionEvents={deduplicatedEvents}
myReactionEvent={myReactionEvent}
customReactionImagesEnabled={customReactionImagesEnabled}
disabled={
!this.context.canReact ||
(myReactionEvent && !myReactionEvent.isRedacted() && !this.context.canSelfRedact)

View file

@ -18,13 +18,15 @@ import React from "react";
import classNames from "classnames";
import { MatrixEvent } from "matrix-js-sdk/src/matrix";
import { mediaFromMxc } from "../../../customisations/Media";
import { _t } from "../../../languageHandler";
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils";
import dis from "../../../dispatcher/dispatcher";
import ReactionsRowButtonTooltip from "./ReactionsRowButtonTooltip";
import AccessibleButton from "../elements/AccessibleButton";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
interface IProps {
import { REACTION_SHORTCODE_KEY } from "./ReactionsRow";
export interface IProps {
// The event we're displaying reactions for
mxEvent: MatrixEvent;
// The reaction content / key / emoji
@ -37,6 +39,8 @@ interface IProps {
myReactionEvent?: MatrixEvent;
// Whether to prevent quick-reactions by clicking on this reaction
disabled?: boolean;
// Whether to render custom image reactions
customReactionImagesEnabled?: boolean;
}
interface IState {
@ -100,27 +104,56 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
content={content}
reactionEvents={reactionEvents}
visible={this.state.tooltipVisible}
customReactionImagesEnabled={this.props.customReactionImagesEnabled}
/>
);
}
const room = this.context.getRoom(mxEvent.getRoomId());
let label: string | undefined;
let customReactionName: string | undefined;
if (room) {
const senders: string[] = [];
for (const reactionEvent of reactionEvents) {
const member = room.getMember(reactionEvent.getSender()!);
senders.push(member?.name || reactionEvent.getSender()!);
customReactionName =
(this.props.customReactionImagesEnabled &&
REACTION_SHORTCODE_KEY.findIn(reactionEvent.getContent())) ||
undefined;
}
const reactors = formatCommaSeparatedList(senders, 6);
if (content) {
label = _t("%(reactors)s reacted with %(content)s", { reactors, content });
label = _t("%(reactors)s reacted with %(content)s", {
reactors,
content: customReactionName || content,
});
} else {
label = reactors;
}
}
let reactionContent = (
<span className="mx_ReactionsRowButton_content" aria-hidden="true">
{content}
</span>
);
if (this.props.customReactionImagesEnabled && content.startsWith("mxc://")) {
const imageSrc = mediaFromMxc(content).srcHttp;
if (imageSrc) {
reactionContent = (
<img
className="mx_ReactionsRowButton_content"
alt={customReactionName || _t("Custom reaction")}
src={imageSrc}
width="16"
height="16"
/>
);
}
}
return (
<AccessibleButton
className={classes}
@ -130,9 +163,7 @@ export default class ReactionsRowButton extends React.PureComponent<IProps, ISta
onMouseOver={this.onMouseOver}
onMouseLeave={this.onMouseLeave}
>
<span className="mx_ReactionsRowButton_content" aria-hidden="true">
{content}
</span>
{reactionContent}
<span className="mx_ReactionsRowButton_count" aria-hidden="true">
{count}
</span>

View file

@ -22,6 +22,7 @@ import { _t } from "../../../languageHandler";
import { formatCommaSeparatedList } from "../../../utils/FormattingUtils";
import Tooltip from "../elements/Tooltip";
import MatrixClientContext from "../../../contexts/MatrixClientContext";
import { REACTION_SHORTCODE_KEY } from "./ReactionsRow";
interface IProps {
// The event we're displaying reactions for
mxEvent: MatrixEvent;
@ -30,6 +31,8 @@ interface IProps {
// A list of Matrix reaction events for this key
reactionEvents: MatrixEvent[];
visible: boolean;
// Whether to render custom image reactions
customReactionImagesEnabled?: boolean;
}
export default class ReactionsRowButtonTooltip extends React.PureComponent<IProps> {
@ -43,12 +46,17 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent<IProp
let tooltipLabel: JSX.Element | undefined;
if (room) {
const senders: string[] = [];
let customReactionName: string | undefined;
for (const reactionEvent of reactionEvents) {
const member = room.getMember(reactionEvent.getSender()!);
const name = member?.name ?? reactionEvent.getSender()!;
senders.push(name);
customReactionName =
(this.props.customReactionImagesEnabled &&
REACTION_SHORTCODE_KEY.findIn(reactionEvent.getContent())) ||
undefined;
}
const shortName = unicodeToShortcode(content);
const shortName = unicodeToShortcode(content) || customReactionName;
tooltipLabel = (
<div>
{_t(

View file

@ -58,6 +58,7 @@ interface IProps {
room: Room;
permalinkCreator: RoomPermalinkCreator;
onClose(): void;
onSearchClick?: () => void;
}
interface IAppsSectionProps {
@ -162,7 +163,7 @@ const AppRow: React.FC<IAppRowProps> = ({ app, room }) => {
WidgetLayoutStore.instance.moveToContainer(room, app, Container.Center);
};
const maximiseTitle = isMaximised ? _t("action|close") : _t("Maximise");
const maximiseTitle = isMaximised ? _t("action|close") : _t("action|maximise");
let openTitle = "";
if (isPinned) {
@ -272,7 +273,7 @@ const onRoomSettingsClick = (ev: ButtonEvent): void => {
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev);
};
const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose }) => {
const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose, onSearchClick }) => {
const cli = useContext(MatrixClientContext);
const onShareRoomClick = (): void => {
@ -309,7 +310,7 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose })
<div className="mx_RoomSummaryCard_avatar" role="presentation">
<RoomAvatar room={room} size="54px" viewAvatarOnClick />
<TextWithTooltip
tooltip={isRoomEncrypted ? _t("Encrypted") : _t("Not encrypted")}
tooltip={isRoomEncrypted ? _t("common|encrypted") : _t("Not encrypted")}
class={classNames("mx_RoomSummaryCard_e2ee", {
mx_RoomSummaryCard_e2ee_normal: isRoomEncrypted,
mx_RoomSummaryCard_e2ee_warning: isRoomEncrypted && e2eStatus === E2EStatus.Warning,
@ -342,6 +343,14 @@ const RoomSummaryCard: React.FC<IProps> = ({ room, permalinkCreator, onClose })
{_t("common|people")}
<span className="mx_BaseCard_Button_sublabel">{memberCount}</span>
</Button>
<Button
className="mx_RoomSummaryCard_icon_search"
onClick={() => {
onSearchClick?.();
}}
>
{_t("Search")}
</Button>
{!isVideoRoom && (
<Button className="mx_RoomSummaryCard_icon_files" onClick={onRoomFilesClick}>
{_t("Files")}

View file

@ -206,7 +206,7 @@ export function DeviceItem({ userId, device }: { userId: string; device: IDevice
}
let trustedLabel: string | undefined;
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("Trusted") : _t("Not trusted");
if (userTrust.isVerified()) trustedLabel = isVerified ? _t("common|trusted") : _t("common|not_trusted");
if (isVerified === undefined) {
// we're still deciding if the device is verified
@ -443,7 +443,7 @@ export const UserOptionsSection: React.FC<{
insertPillButton = (
<AccessibleButton kind="link" onClick={onInsertPillButton} className="mx_UserInfo_field">
{_t("Mention")}
{_t("action|mention")}
</AccessibleButton>
);
}

View file

@ -981,9 +981,6 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
mx_EventTile_lastInSection: this.props.lastInSection,
mx_EventTile_contextual: this.props.contextual,
mx_EventTile_actionBarFocused: this.state.actionBarFocused,
mx_EventTile_verified: !isBubbleMessage && this.state.verified === E2EState.Verified,
mx_EventTile_unverified: !isBubbleMessage && this.state.verified === E2EState.Warning,
mx_EventTile_unknown: !isBubbleMessage && this.state.verified === E2EState.Unknown,
mx_EventTile_bad: isEncryptionFailure,
mx_EventTile_emote: msgtype === MsgType.Emote,
mx_EventTile_noSender: this.props.hideSender,

View file

@ -480,6 +480,7 @@ export interface IProps {
interface IState {
contextMenuPosition?: DOMRect;
rightPanelOpen: boolean;
featureAskToJoin: boolean;
}
/**
@ -496,6 +497,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
public static contextType = RoomContext;
public context!: React.ContextType<typeof RoomContext>;
private readonly client = this.props.room.client;
private readonly featureAskToJoinWatcher: string;
public constructor(props: IProps, context: IState) {
super(props, context);
@ -503,7 +505,15 @@ export default class RoomHeader extends React.Component<IProps, IState> {
notiStore.on(NotificationStateEvents.Update, this.onNotificationUpdate);
this.state = {
rightPanelOpen: RightPanelStore.instance.isOpen,
featureAskToJoin: SettingsStore.getValue("feature_ask_to_join"),
};
this.featureAskToJoinWatcher = SettingsStore.watchSetting(
"feature_ask_to_join",
null,
(_settingName, _roomId, _atLevel, _newValAtLevel, featureAskToJoin) => {
this.setState({ featureAskToJoin });
},
);
}
public componentDidMount(): void {
@ -516,6 +526,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
const notiStore = RoomNotificationStateStore.instance.getRoomState(this.props.room);
notiStore.removeListener(NotificationStateEvents.Update, this.onNotificationUpdate);
RightPanelStore.instance.off(UPDATE_EVENT, this.onRightPanelStoreUpdate);
SettingsStore.unwatchSetting(this.featureAskToJoinWatcher);
}
private onRightPanelStoreUpdate = (): void => {
@ -821,7 +832,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
</div>
{!isVideoRoom && <RoomCallBanner roomId={this.props.room.roomId} />}
<RoomLiveShareWarning roomId={this.props.room.roomId} />
<RoomKnocksBar room={this.props.room} />
{this.state.featureAskToJoin && <RoomKnocksBar room={this.props.room} />}
</header>
);
}

View file

@ -22,18 +22,18 @@ import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/thr
import { Icon as NotificationsIcon } from "@vector-im/compound-design-tokens/icons/notifications-solid.svg";
import { Icon as VerifiedIcon } from "@vector-im/compound-design-tokens/icons/verified.svg";
import { Icon as ErrorIcon } from "@vector-im/compound-design-tokens/icons/error.svg";
import { Icon as PublicIcon } from "@vector-im/compound-design-tokens/icons/public.svg";
import { CallType } from "matrix-js-sdk/src/webrtc/call";
import { EventType, type Room } from "matrix-js-sdk/src/matrix";
import { EventType, JoinRule, type Room } from "matrix-js-sdk/src/matrix";
import { useRoomName } from "../../../hooks/useRoomName";
import DecoratedRoomAvatar from "../avatars/DecoratedRoomAvatar";
import { RightPanelPhases } from "../../../stores/right-panel/RightPanelStorePhases";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import { useTopic } from "../../../hooks/room/useTopic";
import { useAccountData } from "../../../hooks/useAccountData";
import { useMatrixClientContext } from "../../../contexts/MatrixClientContext";
import { useRoomMemberCount, useRoomMembers } from "../../../hooks/useRoomMembers";
import { _t, getCurrentLanguage } from "../../../languageHandler";
import { _t } from "../../../languageHandler";
import { Flex } from "../../utils/Flex";
import { Box } from "../../utils/Box";
import { useRoomCallStatus } from "../../../hooks/room/useRoomCallStatus";
@ -46,6 +46,9 @@ import { placeCall } from "../../../utils/room/placeCall";
import { useEncryptionStatus } from "../../../hooks/useEncryptionStatus";
import { E2EStatus } from "../../../utils/ShieldUtils";
import FacePile from "../elements/FacePile";
import { useRoomState } from "../../../hooks/useRoomState";
import RoomAvatar from "../avatars/RoomAvatar";
import { formatCount } from "../../../utils/FormattingUtils";
/**
* A helper to transform a notification color to the what the Compound Icon Button
@ -76,9 +79,10 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
const roomName = useRoomName(room);
const roomTopic = useTopic(room);
const roomState = useRoomState(room);
const members = useRoomMembers(room);
const memberCount = useRoomMemberCount(room);
const members = useRoomMembers(room, 2500);
const memberCount = useRoomMemberCount(room, { throttleWait: 2500 });
const { voiceCallDisabledReason, voiceCallType, videoCallDisabledReason, videoCallType } = useRoomCallStatus(room);
@ -118,7 +122,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
showOrHidePanel(RightPanelPhases.RoomSummary);
}}
>
<DecoratedRoomAvatar room={room} size="40px" displayBadge={false} />
<RoomAvatar room={room} size="40px" />
<Box flex="1" className="mx_RoomHeader_info">
<BodyText
as="div"
@ -128,16 +132,28 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
title={roomName}
role="heading"
aria-level={1}
className="mx_RoomHeader_heading"
>
{roomName}
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
<Tooltip label={_t("Public room")}>
<PublicIcon
width="16px"
height="16px"
className="text-secondary"
aria-label={_t("Public room")}
/>
</Tooltip>
)}
{isDirectMessage && e2eStatus === E2EStatus.Verified && (
<Tooltip label={_t("Verified")}>
<Tooltip label={_t("common|verified")}>
<VerifiedIcon
width="16px"
height="16px"
className="mx_Verified"
aria-label={_t("Verified")}
aria-label={_t("common|verified")}
/>
</Tooltip>
)}
@ -218,7 +234,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
size="20px"
overflow={false}
>
{memberCount.toLocaleString(getCurrentLanguage())}
{formatCount(memberCount)}
</FacePile>
</BodyText>
)}

View file

@ -160,7 +160,7 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
)}
</SettingsSubsectionText>
<AccessibleButton kind="primary" onClick={this.onManage}>
{_t("Manage")}
{_t("action|manage")}
</AccessibleButton>
</>
);

View file

@ -73,18 +73,18 @@ const DeviceDetails: React.FC<Props> = ({
},
{
id: "application",
heading: _t("Application"),
heading: _t("common|application"),
values: [
{ label: _t("common|name"), value: device.appName },
{ label: _t("Version"), value: device.appVersion },
{ label: _t("common|version"), value: device.appVersion },
{ label: _t("URL"), value: device.url },
],
},
{
id: "device",
heading: _t("Device"),
heading: _t("common|device"),
values: [
{ label: _t("Model"), value: device.deviceModel },
{ label: _t("common|model"), value: device.deviceModel },
{ label: _t("Operating system"), value: device.deviceOperatingSystem },
{ label: _t("Browser"), value: device.client },
{ label: _t("IP address"), value: device.last_seen_ip },

View file

@ -63,7 +63,7 @@ const DeviceMetaDatum: React.FC<{ value: string | React.ReactNode; id: string }>
export const DeviceMetaData: React.FC<Props> = ({ device }) => {
const inactive = getInactiveMetadata(device);
const lastActivity = device.last_seen_ts && `${_t("Last activity")} ${formatLastActivity(device.last_seen_ts)}`;
const verificationStatus = device.isVerified ? _t("Verified") : _t("Unverified");
const verificationStatus = device.isVerified ? _t("common|verified") : _t("common|unverified");
// if device is inactive, don't display last activity or verificationStatus
const metadata = inactive
? [inactive, { id: "lastSeenIp", value: device.last_seen_ip }]

View file

@ -62,13 +62,13 @@ export const DeviceTypeIcon: React.FC<Props> = ({ isVerified, isSelected, device
<VerifiedIcon
className={classNames("mx_DeviceTypeIcon_verificationIcon", "verified")}
role="img"
aria-label={_t("Verified")}
aria-label={_t("common|verified")}
/>
) : (
<UnverifiedIcon
className={classNames("mx_DeviceTypeIcon_verificationIcon", "unverified")}
role="img"
aria-label={_t("Unverified")}
aria-label={_t("common|unverified")}
/>
)}
</div>

View file

@ -284,12 +284,12 @@ export const FilteredDeviceList = forwardRef(
{ id: ALL_FILTER_ID, label: _t("All") },
{
id: DeviceSecurityVariation.Verified,
label: _t("Verified"),
label: _t("common|verified"),
description: _t("Ready for secure messaging"),
},
{
id: DeviceSecurityVariation.Unverified,
label: _t("Unverified"),
label: _t("common|unverified"),
description: _t("Not ready for secure messaging"),
},
{

View file

@ -82,7 +82,7 @@ const Knock: VFC<{
return (
<div className="mx_PeopleRoomSettingsTab_knock">
<MemberAvatar member={roomMember} size="42px" />
<MemberAvatar className="mx_PeopleRoomSettingsTab_avatar" member={roomMember} size="42px" />
<div className="mx_PeopleRoomSettingsTab_content">
<span className="mx_PeopleRoomSettingsTab_name">{roomMember.name}</span>
<Timestamp roomMember={roomMember} />

View file

@ -466,7 +466,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
<LabelledToggleSwitch
value={isEncrypted}
onChange={this.onEncryptionChange}
label={_t("Encrypted")}
label={_t("common|encrypted")}
disabled={!canEnableEncryption}
/>
{isEncryptionForceDisabled && !isEncrypted && (

View file

@ -74,7 +74,7 @@ function UserOnboardingButtonInternal({ selected, minimized }: Props): JSX.Eleme
<>
<div className="mx_UserOnboardingButton_content">
<Heading size="4" className="mx_Heading_h4">
{_t("Welcome")}
{_t("common|welcome")}
</Heading>
<AccessibleButton className="mx_UserOnboardingButton_close" onClick={onDismiss} />
</div>

View file

@ -35,53 +35,38 @@ interface Props {
export function UserOnboardingHeader({ useCase }: Props): JSX.Element {
let title: string;
let description: string;
let image;
let description = _t("onboarding|free_e2ee_messaging_unlimited_voip", {
brand: SdkConfig.get("brand"),
});
let image: string;
let actionLabel: string;
switch (useCase) {
case UseCase.PersonalMessaging:
title = _t("Secure messaging for friends and family");
description = _t(
"With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.",
{
brand: SdkConfig.get("brand"),
},
);
title = _t("onboarding|personal_messaging_title");
image = require("../../../../res/img/user-onboarding/PersonalMessaging.png");
actionLabel = _t("Start your first chat");
actionLabel = _t("onboarding|personal_messaging_action");
break;
case UseCase.WorkMessaging:
title = _t("Secure messaging for work");
description = _t(
"With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.",
{
brand: SdkConfig.get("brand"),
},
);
image = require("../../../../res/img/user-onboarding/WorkMessaging.png");
actionLabel = _t("Find your co-workers");
break;
case UseCase.CommunityMessaging:
title = _t("Community ownership");
description = _t(
"Keep ownership and control of community discussion.\nScale to support millions, with powerful moderation and interoperability.",
);
image = require("../../../../res/img/user-onboarding/CommunityMessaging.png");
actionLabel = _t("Find your people");
break;
default:
title = _t("Welcome to %(brand)s", {
title = _t("onboarding|work_messaging_title");
description = _t("onboarding|free_e2ee_messaging_unlimited_voip", {
brand: SdkConfig.get("brand"),
});
image = require("../../../../res/img/user-onboarding/WorkMessaging.png");
actionLabel = _t("onboarding|work_messaging_action");
break;
case UseCase.CommunityMessaging:
title = _t("onboarding|community_messaging_title");
description = _t("onboarding|community_messaging_description");
image = require("../../../../res/img/user-onboarding/CommunityMessaging.png");
actionLabel = _t("onboarding|community_messaging_action");
break;
default:
title = _t("onboarding|welcome_to_brand", {
brand: SdkConfig.get("brand"),
});
description = _t(
"With free end-to-end encrypted messaging, and unlimited voice and video calls, %(brand)s is a great way to stay in touch.",
{
brand: SdkConfig.get("brand"),
},
);
image = require("../../../../res/img/user-onboarding/PersonalMessaging.png");
actionLabel = _t("Start your first chat");
actionLabel = _t("onboarding|personal_messaging_action");
break;
}

View file

@ -52,13 +52,13 @@ export function UserOnboardingList({ tasks }: Props): JSX.Element {
<div className="mx_UserOnboardingList_header">
<Heading size="3" className="mx_UserOnboardingList_title">
{waiting > 0
? _t("Only %(count)s steps to go", {
? _t("onboarding|only_n_steps_to_go", {
count: waiting,
})
: _t("You did it!")}
: _t("onboarding|you_did_it")}
</Heading>
<div className="mx_UserOnboardingList_hint">
{_t("Complete these to get the most out of %(brand)s", {
{_t("onboarding|complete_these", {
brand: SdkConfig.get("brand"),
})}
</div>