Merge branch 'develop' into germain-gg/fix-right-panel-member

This commit is contained in:
Germain 2023-09-01 12:36:49 +01:00
commit 1b8d00d1d4
141 changed files with 6493 additions and 5556 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

@ -2470,6 +2470,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

@ -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

@ -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

@ -270,12 +270,12 @@ export default class DateSeparator extends React.Component<IProps, IState> {
>
<IconizedContextMenuOptionList first>
<IconizedContextMenuOption
label={_t("Last week")}
label={_t("time|last_week")}
onClick={this.onLastWeekClicked}
data-testid="jump-to-date-last-week"
/>
<IconizedContextMenuOption
label={_t("Last month")}
label={_t("time|last_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

@ -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

@ -36,6 +36,7 @@ import RoomTopic from "../elements/RoomTopic";
import RoomName from "../elements/RoomName";
import { E2EStatus } from "../../../utils/ShieldUtils";
import { IOOBData } from "../../../stores/ThreepidInviteStore";
import { RoomKnocksBar } from "./RoomKnocksBar";
import { SearchScope } from "./SearchBar";
import { aboveLeftOf, ContextMenuTooltipButton, useContextMenu } from "../../structures/ContextMenu";
import RoomContextMenu from "../context_menus/RoomContextMenu";
@ -820,6 +821,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} />
</header>
);
}

View file

@ -14,38 +14,41 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { useCallback, useMemo } from "react";
import { Body as BodyText, IconButton } from "@vector-im/compound-web";
import React, { useEffect, useMemo, useState } from "react";
import { Body as BodyText, IconButton, Tooltip } from "@vector-im/compound-web";
import { Icon as VideoCallIcon } from "@vector-im/compound-design-tokens/icons/video-call.svg";
import { Icon as VoiceCallIcon } from "@vector-im/compound-design-tokens/icons/voice-call.svg";
import { Icon as ThreadsIcon } from "@vector-im/compound-design-tokens/icons/threads-solid.svg";
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 } from "matrix-js-sdk/src/matrix";
import { EventType, JoinRule, type Room } from "matrix-js-sdk/src/matrix";
import 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 { 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";
import LegacyCallHandler from "../../../LegacyCallHandler";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { Action } from "../../../dispatcher/actions";
import { useRoomThreadNotifications } from "../../../hooks/room/useRoomThreadNotifications";
import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { useGlobalNotificationState } from "../../../hooks/useGlobalNotificationState";
import SdkConfig from "../../../SdkConfig";
import { useFeatureEnabled } from "../../../hooks/useSettings";
import { placeCall } from "../../../utils/room/placeCall";
import { useEncryptionStatus } from "../../../hooks/useEncryptionStatus";
import { E2EStatus } from "../../../utils/ShieldUtils";
import FacePile from "../elements/FacePile";
import { setPhase } from "../../../utils/room/setPhase";
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
@ -66,19 +69,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 directRoomsList = useAccountData<Record<string, string[]>>(client, EventType.Direct);
const isDirectMessage = useMemo(() => {
for (const [, dmRoomList] of Object.entries(directRoomsList)) {
if (dmRoomList.includes(room?.roomId ?? "")) {
return true;
}
}
return false;
}, [directRoomsList, room?.roomId]);
const members = useRoomMembers(room, 2500);
const memberCount = useRoomMemberCount(room, { throttleWait: 2500 });
const { voiceCallDisabledReason, voiceCallType, videoCallDisabledReason, videoCallType } = useRoomCallStatus(room);
@ -91,37 +85,21 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
return SdkConfig.get("element_call").use_exclusively && groupCallsEnabled;
}, [groupCallsEnabled]);
const placeCall = useCallback(
async (callType: CallType, platformCallType: typeof voiceCallType) => {
switch (platformCallType) {
case "legacy_or_jitsi":
await LegacyCallHandler.instance.placeCall(room.roomId, callType);
break;
// TODO: Remove the jitsi_or_element_call case and
// use the commented code below
case "element_call":
case "jitsi_or_element_call":
defaultDispatcher.dispatch<ViewRoomPayload>({
action: Action.ViewRoom,
room_id: room.roomId,
view_call: true,
metricsTrigger: undefined,
});
break;
// case "jitsi_or_element_call":
// TODO: Open dropdown menu to choice between
// EC and Jitsi. Waiting on Compound's dropdown
// component
// break;
}
},
[room.roomId],
);
const threadNotifications = useRoomThreadNotifications(room);
const globalNotificationState = useGlobalNotificationState();
const directRoomsList = useAccountData<Record<string, string[]>>(client, EventType.Direct);
const [isDirectMessage, setDirectMessage] = useState(false);
useEffect(() => {
for (const [, dmRoomList] of Object.entries(directRoomsList)) {
if (dmRoomList.includes(room?.roomId ?? "")) {
setDirectMessage(true);
break;
}
}
}, [room, directRoomsList]);
const e2eStatus = useEncryptionStatus(client, room);
return (
<Flex
as="header"
@ -132,7 +110,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
setPhase(RightPanelPhases.RoomSummary);
}}
>
<DecoratedRoomAvatar room={room} size="40px" displayBadge={false} />
<RoomAvatar room={room} size="40px" />
<Box flex="1" className="mx_RoomHeader_info">
<BodyText
as="div"
@ -142,8 +120,42 @@ 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("common|verified")}>
<VerifiedIcon
width="16px"
height="16px"
className="mx_Verified"
aria-label={_t("common|verified")}
/>
</Tooltip>
)}
{isDirectMessage && e2eStatus === E2EStatus.Warning && (
<Tooltip label={_t("Untrusted")}>
<ErrorIcon
width="16px"
height="16px"
className="mx_Untrusted"
aria-label={_t("Untrusted")}
/>
</Tooltip>
)}
</BodyText>
{roomTopic && (
<BodyText as="div" size="sm" className="mx_RoomHeader_topic">
@ -156,8 +168,8 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
<IconButton
disabled={!!voiceCallDisabledReason}
title={!voiceCallDisabledReason ? _t("Voice call") : voiceCallDisabledReason!}
onClick={async () => {
placeCall(CallType.Voice, voiceCallType);
onClick={() => {
placeCall(room, CallType.Voice, voiceCallType);
}}
>
<VoiceCallIcon />
@ -167,7 +179,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
disabled={!!videoCallDisabledReason}
title={!videoCallDisabledReason ? _t("Video call") : videoCallDisabledReason!}
onClick={() => {
placeCall(CallType.Video, videoCallType);
placeCall(room, CallType.Video, videoCallType);
}}
>
<VideoCallIcon />
@ -208,7 +220,7 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
size="20px"
overflow={false}
>
{memberCount.toLocaleString(getCurrentLanguage())}
{formatCount(memberCount)}
</FacePile>
</BodyText>
)}

View file

@ -0,0 +1,159 @@
/*
Copyright 2023 Nordeck IT + Consulting GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { EventTimeline, JoinRule, MatrixError, Room, RoomStateEvent } from "matrix-js-sdk/src/matrix";
import React, { ReactElement, ReactNode, useCallback, useState, VFC } from "react";
import { Icon as CheckIcon } from "../../../../res/img/feather-customised/check.svg";
import { Icon as XIcon } from "../../../../res/img/feather-customised/x.svg";
import dis from "../../../dispatcher/dispatcher";
import { useTypedEventEmitterState } from "../../../hooks/useEventEmitter";
import { _t } from "../../../languageHandler";
import Modal from "../../../Modal";
import MemberAvatar from "../avatars/MemberAvatar";
import ErrorDialog from "../dialogs/ErrorDialog";
import { RoomSettingsTab } from "../dialogs/RoomSettingsDialog";
import AccessibleButton from "../elements/AccessibleButton";
import Heading from "../typography/Heading";
export const RoomKnocksBar: VFC<{ room: Room }> = ({ room }) => {
const [disabled, setDisabled] = useState(false);
const knockMembers = useTypedEventEmitterState(
room,
RoomStateEvent.Members,
useCallback(() => room.getMembersWithMembership("knock"), [room]),
);
const knockMembersCount = knockMembers.length;
if (room.getJoinRule() !== JoinRule.Knock || knockMembersCount === 0) return null;
const client = room.client;
const userId = client.getUserId() || "";
const canInvite = room.canInvite(userId);
const member = room.getMember(userId);
const state = room.getLiveTimeline().getState(EventTimeline.FORWARDS);
const canKick = member && state ? state.hasSufficientPowerLevelFor("kick", member.powerLevel) : false;
if (!canInvite && !canKick) return null;
const onError = (error: MatrixError): void => {
Modal.createDialog(ErrorDialog, { title: error.name, description: error.message });
};
const handleApprove = (userId: string): void => {
setDisabled(true);
client
.invite(room.roomId, userId)
.catch(onError)
.finally(() => setDisabled(false));
};
const handleDeny = (userId: string): void => {
setDisabled(true);
client
.kick(room.roomId, userId)
.catch(onError)
.finally(() => setDisabled(false));
};
const handleOpenRoomSettings = (): void =>
dis.dispatch({ action: "open_room_settings", room_id: room.roomId, initial_tab_id: RoomSettingsTab.People });
let buttons: ReactElement = (
<AccessibleButton
className="mx_RoomKnocksBar_action"
kind="primary"
onClick={handleOpenRoomSettings}
title={_t("action|view")}
>
{_t("action|view")}
</AccessibleButton>
);
let names: string = knockMembers
.slice(0, 2)
.map((knockMember) => knockMember.name)
.join(", ");
let link: ReactNode = null;
switch (knockMembersCount) {
case 1: {
buttons = (
<>
<AccessibleButton
className="mx_RoomKnocksBar_action"
disabled={!canKick || disabled}
kind="icon_primary_outline"
onClick={() => handleDeny(knockMembers[0].userId)}
title={_t("action|deny")}
>
<XIcon width={18} height={18} />
</AccessibleButton>
<AccessibleButton
className="mx_RoomKnocksBar_action"
disabled={!canInvite || disabled}
kind="icon_primary"
onClick={() => handleApprove(knockMembers[0].userId)}
title={_t("action|approve")}
>
<CheckIcon width={18} height={18} />
</AccessibleButton>
</>
);
names = `${knockMembers[0].name} (${knockMembers[0].userId})`;
link = (
<AccessibleButton
className="mx_RoomKnocksBar_link"
element="a"
kind="link_inline"
onClick={handleOpenRoomSettings}
>
{_t("action|view_message")}
</AccessibleButton>
);
break;
}
case 2: {
names = _t("%(names)s and %(name)s", { names: knockMembers[0].name, name: knockMembers[1].name });
break;
}
case 3: {
names = _t("%(names)s and %(name)s", { names, name: knockMembers[2].name });
break;
}
default:
names = _t("%(names)s and %(count)s others", { names, count: knockMembersCount - 2 });
}
return (
<div className="mx_RoomKnocksBar">
{knockMembers.slice(0, 2).map((knockMember) => (
<MemberAvatar
className="mx_RoomKnocksBar_avatar"
key={knockMember.userId}
member={knockMember}
size="32px"
/>
))}
<div className="mx_RoomKnocksBar_content">
<Heading size="4">{_t("%(count)s people asking to join", { count: knockMembersCount })}</Heading>
<p className="mx_RoomKnocksBar_paragraph">
{names}
{link}
</p>
</div>
{buttons}
</div>
);
};

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>