mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 05:04:57 +08:00
Merge branch 'develop' into manancodes/Edited_message_ux_fix
This commit is contained in:
commit
c16aba5c3a
4
.github/workflows/cypress.yaml
vendored
4
.github/workflows/cypress.yaml
vendored
@ -174,11 +174,15 @@ jobs:
|
||||
record: true
|
||||
parallel: true
|
||||
command-prefix: "yarn percy exec --parallel --"
|
||||
command: "npx cypress-cloud run"
|
||||
config: '{"reporter":"cypress-multi-reporters", "reporterOptions": { "configFile": "cypress-ci-reporter-config.json" } }'
|
||||
ci-build-id: ${{ needs.prepare.outputs.uuid }}
|
||||
env:
|
||||
# pass the Dashboard record key as an environment variable
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
CURRENTS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
CURRENTS_PROJECT_ID: ${{ github.repository }}
|
||||
CURRENTS_API_URL: ${{ vars.CURRENTS_API_URL }}
|
||||
|
||||
# Use existing chromium rather than downloading another
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
|
||||
|
24
CHANGELOG.md
24
CHANGELOG.md
@ -1,3 +1,27 @@
|
||||
Changes in [3.81.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.81.0) (2023-09-26)
|
||||
=====================================================================================================
|
||||
|
||||
## ✨ Features
|
||||
* Make video & voice call buttons pin conference widget if unpinned ([\#11576](https://github.com/matrix-org/matrix-react-sdk/pull/11576)). Fixes vector-im/customer-retainer#72.
|
||||
* OIDC: persist refresh token ([\#11249](https://github.com/matrix-org/matrix-react-sdk/pull/11249)). Contributed by @kerryarchibald.
|
||||
* ElementR: Cross user verification ([\#11364](https://github.com/matrix-org/matrix-react-sdk/pull/11364)). Fixes vector-im/element-web#25752. Contributed by @florianduros.
|
||||
* Default intentional mentions ([\#11602](https://github.com/matrix-org/matrix-react-sdk/pull/11602)).
|
||||
* Notify users about denied access on ask-to-join rooms ([\#11480](https://github.com/matrix-org/matrix-react-sdk/pull/11480)). Contributed by @nurjinjafar.
|
||||
* Allow setting knock room directory visibility ([\#11529](https://github.com/matrix-org/matrix-react-sdk/pull/11529)). Contributed by @charlynguyen.
|
||||
|
||||
## 🐛 Bug Fixes
|
||||
* Revert "Fix regression around FacePile with overflow (#11527)" ([\#11634](https://github.com/matrix-org/matrix-react-sdk/pull/11634)). Fixes vector-im/element-web#26209.
|
||||
* Escape placeholder before injecting it into the style ([\#11607](https://github.com/matrix-org/matrix-react-sdk/pull/11607)).
|
||||
* Move ViewUser action callback to RoomView ([\#11495](https://github.com/matrix-org/matrix-react-sdk/pull/11495)). Fixes vector-im/element-web#26040.
|
||||
* Fix room timeline search toggling behaviour edge case ([\#11605](https://github.com/matrix-org/matrix-react-sdk/pull/11605)). Fixes vector-im/element-web#26105.
|
||||
* Avoid rendering view-message link in RoomKnocksBar unnecessarily ([\#11598](https://github.com/matrix-org/matrix-react-sdk/pull/11598)). Contributed by @charlynguyen.
|
||||
* Use knock rooms sync to reflect the knock state ([\#11596](https://github.com/matrix-org/matrix-react-sdk/pull/11596)). Fixes vector-im/element-web#26043 and vector-im/element-web#26044. Contributed by @charlynguyen.
|
||||
* Fix avatar in right panel not using the correct font ([\#11593](https://github.com/matrix-org/matrix-react-sdk/pull/11593)). Fixes vector-im/element-web#26061. Contributed by @MidhunSureshR.
|
||||
* Add waits in Spotlight Cypress tests, hoping this unflakes them ([\#11590](https://github.com/matrix-org/matrix-react-sdk/pull/11590)). Fixes vector-im/element-web#26053, vector-im/element-web#26140 vector-im/element-web#26139 and vector-im/element-web#26138. Contributed by @andybalaam.
|
||||
* Fix vertical alignment of default avatar font ([\#11582](https://github.com/matrix-org/matrix-react-sdk/pull/11582)). Fixes vector-im/element-web#26081.
|
||||
* Fix avatars in public room & space search being flex shrunk ([\#11580](https://github.com/matrix-org/matrix-react-sdk/pull/11580)). Fixes vector-im/element-web#26133.
|
||||
* Fix EventTile avatars being rendered with a size of 0 instead of hidden ([\#11558](https://github.com/matrix-org/matrix-react-sdk/pull/11558)). Fixes vector-im/element-web#26075.
|
||||
|
||||
Changes in [3.80.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.80.1) (2023-09-13)
|
||||
=====================================================================================================
|
||||
|
||||
|
@ -17,6 +17,8 @@ limitations under the License.
|
||||
import { defineConfig } from "cypress";
|
||||
import * as fs from "node:fs";
|
||||
|
||||
import registerPlugins from "./cypress/plugins";
|
||||
|
||||
export default defineConfig({
|
||||
video: true,
|
||||
projectId: "ppvnzg",
|
||||
@ -38,7 +40,7 @@ export default defineConfig({
|
||||
}
|
||||
});
|
||||
|
||||
return require("./cypress/plugins/index.ts").default(on, config);
|
||||
return registerPlugins(on, config);
|
||||
},
|
||||
baseUrl: "http://localhost:8080",
|
||||
specPattern: "cypress/e2e/**/*.spec.{js,jsx,ts,tsx}",
|
||||
|
@ -16,6 +16,8 @@ limitations under the License.
|
||||
|
||||
/// <reference types="cypress" />
|
||||
import installLogsPrinter from "cypress-terminal-report/src/installLogsPrinter";
|
||||
import cloudPlugin from "cypress-cloud/plugin";
|
||||
import { initPlugins } from "cypress-plugin-init";
|
||||
|
||||
import PluginEvents = Cypress.PluginEvents;
|
||||
import PluginConfigOptions = Cypress.PluginConfigOptions;
|
||||
@ -32,15 +34,22 @@ import { mailhogDocker } from "./mailhog";
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
export default function (on: PluginEvents, config: PluginConfigOptions) {
|
||||
docker(on, config);
|
||||
synapseDocker(on, config);
|
||||
dendriteDocker(on, config);
|
||||
slidingSyncProxyDocker(on, config);
|
||||
webserver(on, config);
|
||||
oAuthServer(on, config);
|
||||
log(on, config);
|
||||
initPlugins(
|
||||
on,
|
||||
[
|
||||
cloudPlugin,
|
||||
docker,
|
||||
synapseDocker,
|
||||
dendriteDocker,
|
||||
slidingSyncProxyDocker,
|
||||
webserver,
|
||||
oAuthServer,
|
||||
log,
|
||||
mailhogDocker,
|
||||
],
|
||||
config,
|
||||
);
|
||||
installLogsPrinter(on, {
|
||||
// printLogsToConsole: "always",
|
||||
});
|
||||
mailhogDocker(on, config);
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import "@percy/cypress";
|
||||
import "cypress-real-events";
|
||||
import "@testing-library/cypress/add-commands";
|
||||
import installLogsCollector from "cypress-terminal-report/src/installLogsCollector";
|
||||
import "cypress-cloud/support";
|
||||
|
||||
import "./config.json";
|
||||
import "./homeserver";
|
||||
|
11
package.json
11
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "matrix-react-sdk",
|
||||
"version": "3.80.1",
|
||||
"version": "3.81.0",
|
||||
"description": "SDK for matrix.org using React",
|
||||
"author": "matrix.org",
|
||||
"repository": {
|
||||
@ -23,7 +23,7 @@
|
||||
"package.json",
|
||||
".stylelintrc.js"
|
||||
],
|
||||
"main": "./src/index.ts",
|
||||
"main": "./lib/index.ts",
|
||||
"matrix_src_main": "./src/index.ts",
|
||||
"matrix_lib_main": "./lib/index.ts",
|
||||
"matrix_lib_typings": "./lib/index.d.ts",
|
||||
@ -102,7 +102,7 @@
|
||||
"matrix-encrypt-attachment": "^1.0.3",
|
||||
"matrix-events-sdk": "0.0.1",
|
||||
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
|
||||
"matrix-widget-api": "^1.6.0",
|
||||
"matrix-widget-api": "^1.5.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"minimist": "^1.2.5",
|
||||
"oidc-client-ts": "^2.2.4",
|
||||
@ -188,7 +188,9 @@
|
||||
"chokidar": "^3.5.1",
|
||||
"cypress": "^13.0.0",
|
||||
"cypress-axe": "^1.0.0",
|
||||
"cypress-cloud": "^2.0.0-beta.0",
|
||||
"cypress-multi-reporters": "^1.6.1",
|
||||
"cypress-plugin-init": "^0.0.8",
|
||||
"cypress-real-events": "^1.7.1",
|
||||
"cypress-terminal-report": "^5.3.2",
|
||||
"eslint": "8.48.0",
|
||||
@ -231,5 +233,6 @@
|
||||
"outputDirectory": "coverage",
|
||||
"outputName": "jest-sonar-report.xml",
|
||||
"relativePaths": true
|
||||
}
|
||||
},
|
||||
"typings": "./lib/index.d.ts"
|
||||
}
|
||||
|
@ -15,11 +15,13 @@ limitations under the License.
|
||||
*/
|
||||
|
||||
.mx_FacePile_more {
|
||||
/* Needed to calculate the offset on the face pile */
|
||||
--cpd-avatar-size: 28px;
|
||||
position: relative;
|
||||
border-radius: 100%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
background-color: $spacePanel-bg-color;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background-color: $panels;
|
||||
display: inline-block;
|
||||
|
||||
&::before {
|
||||
|
@ -28,7 +28,7 @@ limitations under the License.
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: 10.5px;
|
||||
left: 11px;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 16px;
|
||||
|
@ -126,7 +126,7 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
const msg = e.friendlyText || _t("Unknown error");
|
||||
const msg = e.friendlyText || _t("error|unknown");
|
||||
this.setState({
|
||||
errStr: msg,
|
||||
phase: Phase.Edit,
|
||||
|
@ -119,7 +119,7 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
const msg = e.friendlyText || _t("Unknown error");
|
||||
const msg = e.friendlyText || _t("error|unknown");
|
||||
this.setState({
|
||||
errStr: msg,
|
||||
phase: Phase.Edit,
|
||||
|
@ -30,7 +30,7 @@ export default class SpaceProvider extends RoomProvider {
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return _t("Spaces");
|
||||
return _t("common|spaces");
|
||||
}
|
||||
|
||||
public renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||
|
@ -345,7 +345,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||
<AccessibleTooltipButton
|
||||
className="mx_LeftPanel_exploreButton"
|
||||
onClick={this.onExplore}
|
||||
title={_t("Explore rooms")}
|
||||
title={_t("action|explore_rooms")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -384,7 +384,7 @@ export const showRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st
|
||||
oob_data: {
|
||||
avatarUrl: room?.avatar_url,
|
||||
// XXX: This logic is duplicated from the JS SDK which would normally decide what the name is.
|
||||
name: room?.name || roomAlias || _t("Unnamed room"),
|
||||
name: room?.name || roomAlias || _t("common|unnamed_room"),
|
||||
roomType,
|
||||
} as IOOBData,
|
||||
metricsTrigger: "RoomDirectory",
|
||||
@ -410,7 +410,7 @@ export const joinRoom = async (cli: MatrixClient, hierarchy: RoomHierarchy, room
|
||||
logger.warn("Got a non-MatrixError while joining room", err);
|
||||
SdkContextClass.instance.roomViewStore.showJoinRoomError(
|
||||
new MatrixError({
|
||||
error: _t("Unknown error"),
|
||||
error: _t("error|unknown"),
|
||||
}),
|
||||
roomId,
|
||||
);
|
||||
@ -673,7 +673,7 @@ const ManageButtons: React.FC<IManageButtonsProps> = ({ hierarchy, selected, set
|
||||
};
|
||||
}
|
||||
|
||||
let buttonText = _t("Saving…");
|
||||
let buttonText = _t("common|saving");
|
||||
if (!saving) {
|
||||
buttonText = selectionAllSuggested ? _t("space|unmark_suggested") : _t("space|mark_suggested");
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
||||
{canCreateRoom && (
|
||||
<>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("New room")}
|
||||
label={_t("action|new_room")}
|
||||
iconClassName="mx_RoomList_iconNewRoom"
|
||||
onClick={async (e): Promise<void> => {
|
||||
e.preventDefault();
|
||||
@ -139,7 +139,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
||||
/>
|
||||
{videoRoomsEnabled && (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("New video room")}
|
||||
label={_t("action|new_video_room")}
|
||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
||||
onClick={async (e): Promise<void> => {
|
||||
e.preventDefault();
|
||||
@ -164,7 +164,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
||||
</>
|
||||
)}
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Add existing room")}
|
||||
label={_t("action|add_existing_room")}
|
||||
iconClassName="mx_RoomList_iconAddExistingRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -303,8 +303,8 @@ const SpaceSetupFirstRooms: React.FC<{
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const numFields = 3;
|
||||
const placeholders = [_t("General"), _t("common|random"), _t("common|support")];
|
||||
const [roomNames, setRoomName] = useStateArray(numFields, [_t("General"), _t("common|random"), ""]);
|
||||
const placeholders = [_t("common|general"), _t("common|random"), _t("common|support")];
|
||||
const [roomNames, setRoomName] = useStateArray(numFields, [_t("common|general"), _t("common|random"), ""]);
|
||||
const fields = new Array(numFields).fill(0).map((x, i) => {
|
||||
const name = "roomName" + i;
|
||||
return (
|
||||
|
@ -99,7 +99,7 @@ export default function MemberAvatar({
|
||||
}
|
||||
: props.onClick
|
||||
}
|
||||
altText={_t("Profile picture")}
|
||||
altText={_t("common|user_avatar")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -174,11 +174,11 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||
let iconClassName: string | undefined;
|
||||
switch (echoChamber.notificationVolume) {
|
||||
case RoomNotifState.AllMessages:
|
||||
notificationLabel = _t("Default");
|
||||
notificationLabel = _t("notifications|default");
|
||||
iconClassName = "mx_RoomTile_iconNotificationsDefault";
|
||||
break;
|
||||
case RoomNotifState.AllMessagesLoud:
|
||||
notificationLabel = _t("All messages");
|
||||
notificationLabel = _t("notifications|all_messages");
|
||||
iconClassName = "mx_RoomTile_iconNotificationsAllMessages";
|
||||
break;
|
||||
case RoomNotifState.MentionsOnly:
|
||||
|
@ -61,7 +61,7 @@ export const RoomNotificationContextMenu: React.FC<IProps> = ({ room, onFinished
|
||||
|
||||
const allMessagesOption: JSX.Element = (
|
||||
<IconizedContextMenuRadio
|
||||
label={_t("All messages")}
|
||||
label={_t("notifications|all_messages")}
|
||||
active={notificationState === RoomNotifState.AllMessagesLoud}
|
||||
iconClassName="mx_RoomNotificationContextMenu_iconBellDot"
|
||||
onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessagesLoud))}
|
||||
@ -70,7 +70,7 @@ export const RoomNotificationContextMenu: React.FC<IProps> = ({ room, onFinished
|
||||
|
||||
const mentionsOption: JSX.Element = (
|
||||
<IconizedContextMenuRadio
|
||||
label={_t("Mentions & keywords")}
|
||||
label={_t("notifications|mentions_keywords")}
|
||||
active={notificationState === RoomNotifState.MentionsOnly}
|
||||
iconClassName="mx_RoomNotificationContextMenu_iconBellMentions"
|
||||
onClick={wrapHandler(() => setNotificationState(RoomNotifState.MentionsOnly))}
|
||||
|
@ -187,7 +187,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
|
||||
<IconizedContextMenuOption
|
||||
data-testid="new-video-room-option"
|
||||
iconClassName="mx_SpacePanel_iconPlus"
|
||||
label={_t("Video room")}
|
||||
label={_t("common|video_room")}
|
||||
onClick={onNewVideoRoomClick}
|
||||
>
|
||||
<BetaPill />
|
||||
|
@ -388,7 +388,7 @@ const defaultRendererFactory =
|
||||
);
|
||||
|
||||
export const defaultRoomsRenderer = defaultRendererFactory(_td("Rooms"));
|
||||
export const defaultSpacesRenderer = defaultRendererFactory(_td("Spaces"));
|
||||
export const defaultSpacesRenderer = defaultRendererFactory(_td("common|spaces"));
|
||||
export const defaultDmsRenderer = defaultRendererFactory(_td("Direct Messages"));
|
||||
|
||||
interface ISubspaceSelectorProps {
|
||||
@ -494,7 +494,7 @@ const AddExistingToSpaceDialog: React.FC<IProps> = ({ space, onCreateRoomClick,
|
||||
roomsRenderer={defaultRoomsRenderer}
|
||||
spacesRenderer={() => (
|
||||
<div className="mx_AddExistingToSpace_section">
|
||||
<h3>{_t("Spaces")}</h3>
|
||||
<h3>{_t("common|spaces")}</h3>
|
||||
<AccessibleButton
|
||||
kind="link"
|
||||
onClick={() => {
|
||||
|
@ -61,7 +61,7 @@ export default class ChangelogDialog extends React.Component<IProps, State> {
|
||||
const body = await res.json();
|
||||
this.setState({ [repo]: body.commits });
|
||||
} catch (err) {
|
||||
this.setState({ [repo]: err instanceof Error ? err.message : _t("Unknown error") });
|
||||
this.setState({ [repo]: err instanceof Error ? err.message : _t("error|unknown") });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ export default class ConfirmUserActionDialog extends React.Component<IProps, ISt
|
||||
onChange={this.onReasonChange}
|
||||
value={this.state.reason}
|
||||
className="mx_ConfirmUserActionDialog_reasonField"
|
||||
label={_t("Reason")}
|
||||
label={_t("room_settings|permissions|ban_reason")}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</form>
|
||||
|
@ -334,7 +334,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||
visibilitySection = (
|
||||
<LabelledCheckbox
|
||||
className="mx_CreateRoomDialog_labelledCheckbox"
|
||||
label={_t("Make this room visible in the public room directory.")}
|
||||
label={_t("room_settings|security|publish_room")}
|
||||
onChange={this.onIsPublicKnockRoomChange}
|
||||
value={this.state.isPublicKnockRoom}
|
||||
/>
|
||||
@ -353,9 +353,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||
microcopy = _t("create_room|encryption_forced");
|
||||
}
|
||||
} else {
|
||||
microcopy = _t(
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
|
||||
);
|
||||
microcopy = _t("settings|security|e2ee_default_disabled_warning");
|
||||
}
|
||||
e2eeSection = (
|
||||
<React.Fragment>
|
||||
@ -417,8 +415,10 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||
<JoinRuleDropdown
|
||||
label={_t("create_room|room_visibility_label")}
|
||||
labelInvite={_t("create_room|join_rule_invite")}
|
||||
labelKnock={this.askToJoinEnabled ? _t("Ask to join") : undefined}
|
||||
labelPublic={_t("Public room")}
|
||||
labelKnock={
|
||||
this.askToJoinEnabled ? _t("room_settings|security|join_rule_knock") : undefined
|
||||
}
|
||||
labelPublic={_t("common|public_room")}
|
||||
labelRestricted={
|
||||
this.supportsRestricted ? _t("create_room|join_rule_restricted") : undefined
|
||||
}
|
||||
@ -432,7 +432,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||
{aliasField}
|
||||
<details onToggle={this.onDetailsToggled} className="mx_CreateRoomDialog_details">
|
||||
<summary className="mx_CreateRoomDialog_details_summary">
|
||||
{this.state.detailsOpen ? _t("Hide advanced") : _t("Show advanced")}
|
||||
{this.state.detailsOpen ? _t("action|hide_advanced") : _t("action|show_advanced")}
|
||||
</summary>
|
||||
<LabelledToggleSwitch
|
||||
label={_t("create_room|unfederated", {
|
||||
|
@ -163,7 +163,7 @@ const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick
|
||||
<JoinRuleDropdown
|
||||
label={_t("Space visibility")}
|
||||
labelInvite={_t("Private space (invite only)")}
|
||||
labelPublic={_t("Public space")}
|
||||
labelPublic={_t("common|public_space")}
|
||||
labelRestricted={_t("create_room|join_rule_restricted")}
|
||||
width={478}
|
||||
value={joinRule}
|
||||
|
@ -214,7 +214,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
|
||||
className="mx_DeactivateAccountDialog"
|
||||
onFinished={this.props.onFinished}
|
||||
titleClass="danger"
|
||||
title={_t("Deactivate Account")}
|
||||
title={_t("settings|general|deactivate_section")}
|
||||
screenName="DeactivateAccount"
|
||||
>
|
||||
<div className="mx_Dialog_content">
|
||||
|
@ -47,7 +47,7 @@ export default class IntegrationsDisabledDialog extends React.Component<IProps>
|
||||
<div className="mx_IntegrationsDisabledDialog_content">
|
||||
<p>
|
||||
{_t("Enable '%(manageIntegrations)s' in Settings to do this.", {
|
||||
manageIntegrations: _t("Manage integrations"),
|
||||
manageIntegrations: _t("integration_manager|manage_title"),
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -143,7 +143,7 @@ export default class InteractiveAuthDialog<T> extends React.Component<Interactiv
|
||||
// Let's pick a title, body, and other params text that we'll show to the user. The order
|
||||
// is most specific first, so stagePhase > our props > defaults.
|
||||
|
||||
let title = this.state.authError ? "Error" : this.props.title || _t("Authentication");
|
||||
let title = this.state.authError ? "Error" : this.props.title || _t("common|authentication");
|
||||
let body = this.state.authError ? null : this.props.body;
|
||||
let continueText: string | undefined;
|
||||
let continueKind: string | undefined;
|
||||
|
@ -182,7 +182,7 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
|
||||
|
||||
let setupButtonCaption;
|
||||
if (this.state.backupStatus === BackupStatus.SERVER_BACKUP_BUT_DISABLED) {
|
||||
setupButtonCaption = _t("Connect this session to Key Backup");
|
||||
setupButtonCaption = _t("settings|security|key_backup_connect");
|
||||
} else {
|
||||
// if there's an error fetching the backup info, we'll just assume there's
|
||||
// no backup for the purpose of the button caption
|
||||
@ -203,7 +203,7 @@ export default class LogoutDialog extends React.Component<IProps, IState> {
|
||||
<button onClick={this.onLogoutConfirm}>{_t("I don't want my encrypted messages")}</button>
|
||||
</DialogButtons>
|
||||
<details>
|
||||
<summary>{_t("common|Advanced")}</summary>
|
||||
<summary>{_t("common|advanced")}</summary>
|
||||
<p>
|
||||
<button onClick={this.onExportE2eKeysClicked}>{_t("Manually export keys")}</button>
|
||||
</p>
|
||||
|
@ -417,7 +417,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||
<Field
|
||||
className="mx_ReportEventDialog_reason"
|
||||
element="textarea"
|
||||
label={_t("Reason")}
|
||||
label={_t("room_settings|permissions|ban_reason")}
|
||||
rows={5}
|
||||
onChange={this.onReasonChange}
|
||||
value={this.state.reason}
|
||||
@ -456,7 +456,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||
<Field
|
||||
className="mx_ReportEventDialog_reason"
|
||||
element="textarea"
|
||||
label={_t("Reason")}
|
||||
label={_t("room_settings|permissions|ban_reason")}
|
||||
rows={5}
|
||||
onChange={this.onReasonChange}
|
||||
value={this.state.reason}
|
||||
|
@ -134,7 +134,7 @@ class RoomSettingsDialog extends React.Component<IProps, IState> {
|
||||
tabs.push(
|
||||
new Tab(
|
||||
RoomSettingsTab.General,
|
||||
_td("General"),
|
||||
_td("common|general"),
|
||||
"mx_RoomSettingsDialog_settingsIcon",
|
||||
<GeneralRoomSettingsTab room={this.state.room} />,
|
||||
"RoomSettingsGeneral",
|
||||
@ -154,7 +154,7 @@ class RoomSettingsDialog extends React.Component<IProps, IState> {
|
||||
tabs.push(
|
||||
new Tab(
|
||||
RoomSettingsTab.Voip,
|
||||
_td("Voice & Video"),
|
||||
_td("settings|voip|title"),
|
||||
"mx_RoomSettingsDialog_voiceIcon",
|
||||
<VoipRoomSettingsTab room={this.state.room} />,
|
||||
),
|
||||
@ -197,7 +197,7 @@ class RoomSettingsDialog extends React.Component<IProps, IState> {
|
||||
tabs.push(
|
||||
new Tab(
|
||||
RoomSettingsTab.Bridges,
|
||||
_td("Bridges"),
|
||||
_td("room_settings|bridges|title"),
|
||||
"mx_RoomSettingsDialog_bridgesIcon",
|
||||
<BridgeSettingsTab room={this.state.room} />,
|
||||
"RoomSettingsBridges",
|
||||
@ -218,7 +218,7 @@ class RoomSettingsDialog extends React.Component<IProps, IState> {
|
||||
tabs.push(
|
||||
new Tab(
|
||||
RoomSettingsTab.Advanced,
|
||||
_td("common|Advanced"),
|
||||
_td("common|advanced"),
|
||||
"mx_RoomSettingsDialog_warningIcon",
|
||||
(
|
||||
<AdvancedRoomSettingsTab
|
||||
|
@ -67,8 +67,8 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||
const emailAddress = this.state.emailAddress;
|
||||
if (!Email.looksValid(emailAddress)) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Invalid Email Address"),
|
||||
description: _t("This doesn't appear to be a valid email address"),
|
||||
title: _t("settings|general|error_invalid_email"),
|
||||
description: _t("settings|general|error_invalid_email_detail"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -88,7 +88,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||
this.setState({ emailBusy: false });
|
||||
logger.error("Unable to add email address " + emailAddress + " " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Unable to add email address"),
|
||||
title: _t("settings|general|error_add_email"),
|
||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||
});
|
||||
},
|
||||
@ -123,7 +123,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||
|
||||
if (underlyingError instanceof MatrixError && underlyingError.errcode === "M_THREEPID_AUTH_FAILED") {
|
||||
const message =
|
||||
_t("Unable to verify email address.") +
|
||||
_t("settings|general|error_email_verification") +
|
||||
" " +
|
||||
_t(
|
||||
"Please check your email and click on the link it contains. Once this is done, click continue.",
|
||||
@ -137,7 +137,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||
} else {
|
||||
logger.error("Unable to verify email address: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Unable to verify email address."),
|
||||
title: _t("settings|general|error_email_verification"),
|
||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||
});
|
||||
}
|
||||
|
@ -55,13 +55,13 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
|
||||
return [
|
||||
new Tab(
|
||||
SpaceSettingsTab.General,
|
||||
_td("General"),
|
||||
_td("common|general"),
|
||||
"mx_SpaceSettingsDialog_generalIcon",
|
||||
<SpaceSettingsGeneralTab matrixClient={cli} space={space} />,
|
||||
),
|
||||
new Tab(
|
||||
SpaceSettingsTab.Visibility,
|
||||
_td("Visibility"),
|
||||
_td("room_settings|visibility|title"),
|
||||
"mx_SpaceSettingsDialog_visibilityIcon",
|
||||
<SpaceSettingsVisibilityTab matrixClient={cli} space={space} closeSettingsFn={onFinished} />,
|
||||
),
|
||||
@ -74,7 +74,7 @@ const SpaceSettingsDialog: React.FC<IProps> = ({ matrixClient: cli, space, onFin
|
||||
SettingsStore.getValue(UIFeature.AdvancedSettings)
|
||||
? new Tab(
|
||||
SpaceSettingsTab.Advanced,
|
||||
_td("common|Advanced"),
|
||||
_td("common|advanced"),
|
||||
"mx_RoomSettingsDialog_warningIcon",
|
||||
<AdvancedRoomSettingsTab room={space} closeSettingsFn={onFinished} />,
|
||||
)
|
||||
|
@ -77,7 +77,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
||||
tabs.push(
|
||||
new Tab(
|
||||
UserTab.General,
|
||||
_td("General"),
|
||||
_td("common|general"),
|
||||
"mx_UserSettingsDialog_settingsIcon",
|
||||
<GeneralUserSettingsTab closeSettingsFn={this.props.onFinished} />,
|
||||
"UserSettingsGeneral",
|
||||
@ -133,7 +133,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
||||
tabs.push(
|
||||
new Tab(
|
||||
UserTab.Voice,
|
||||
_td("Voice & Video"),
|
||||
_td("settings|voip|title"),
|
||||
"mx_UserSettingsDialog_voiceIcon",
|
||||
<VoiceUserSettingsTab />,
|
||||
"UserSettingsVoiceVideo",
|
||||
|
@ -33,7 +33,9 @@ interface Props {
|
||||
|
||||
export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsId }: Props): JSX.Element {
|
||||
let name =
|
||||
room.name || getDisplayAliasForAliasSet(room.canonical_alias ?? "", room.aliases ?? []) || _t("Unnamed room");
|
||||
room.name ||
|
||||
getDisplayAliasForAliasSet(room.canonical_alias ?? "", room.aliases ?? []) ||
|
||||
_t("common|unnamed_room");
|
||||
if (name.length > MAX_NAME_LENGTH) {
|
||||
name = `${name.substring(0, MAX_NAME_LENGTH)}...`;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element {
|
||||
const target = ev.target as HTMLElement;
|
||||
setGeneralMenuPosition(target.getBoundingClientRect());
|
||||
}}
|
||||
title={room.isSpaceRoom() ? _t("Space options") : _t("Room options")}
|
||||
title={room.isSpaceRoom() ? _t("space|context_menu|options") : _t("Room options")}
|
||||
isExpanded={generalMenuPosition !== null}
|
||||
/>
|
||||
)}
|
||||
|
@ -57,9 +57,8 @@ const FacePile: FC<IProps> = ({
|
||||
|
||||
const pileContents = (
|
||||
<>
|
||||
{/* XXX: The margin-left is a workaround for Compound's styling excluding this element and being overly specific */}
|
||||
{overflow ? <span className="mx_FacePile_more" style={{ marginLeft: `calc(${size} * -0.2)` }} /> : null}
|
||||
{faces}
|
||||
{overflow ? <span className="mx_FacePile_more" /> : null}
|
||||
</>
|
||||
);
|
||||
|
||||
|
@ -79,8 +79,8 @@ export default class TagComposer extends React.PureComponent<IProps, IState> {
|
||||
id={this.props.id ? this.props.id + "_field" : undefined}
|
||||
value={this.state.newTag}
|
||||
onChange={this.onInputChange}
|
||||
label={this.props.label || _t("Keyword")}
|
||||
placeholder={this.props.placeholder || _t("New keyword")}
|
||||
label={this.props.label || _t("notifications|keyword")}
|
||||
placeholder={this.props.placeholder || _t("notifications|keyword_new")}
|
||||
disabled={this.props.disabled}
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
@ -250,7 +250,7 @@ export default class LegacyCallEvent extends React.PureComponent<IProps, IState>
|
||||
if (this.state.callState === CallState.Connecting) {
|
||||
return (
|
||||
<div className="mx_LegacyCallEvent_content">
|
||||
{_t("Connecting")}
|
||||
{_t("voip|connecting")}
|
||||
{this.props.timestamp}
|
||||
</div>
|
||||
);
|
||||
|
@ -409,7 +409,7 @@ export const UserOptionsSection: React.FC<{
|
||||
kind="link"
|
||||
className={classNames("mx_UserInfo_field", { mx_UserInfo_destructive: !isIgnored })}
|
||||
>
|
||||
{isIgnored ? _t("Unignore") : _t("action|ignore")}
|
||||
{isIgnored ? _t("action|unignore") : _t("action|ignore")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
@ -1163,7 +1163,7 @@ export const PowerLevelEditor: React.FC<{
|
||||
logger.error("Failed to change power level " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("common|error"),
|
||||
description: _t("Failed to change power level"),
|
||||
description: _t("error|update_power_level"),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
@ -737,7 +737,7 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
switch (this.state.shieldReason) {
|
||||
case null:
|
||||
case EventShieldReason.UNKNOWN:
|
||||
shieldReasonMessage = _t("Unknown error");
|
||||
shieldReasonMessage = _t("error|unknown");
|
||||
break;
|
||||
|
||||
case EventShieldReason.UNVERIFIED_IDENTITY:
|
||||
|
@ -290,26 +290,24 @@ const CallButtons: FC<CallButtonsProps> = ({ room }) => {
|
||||
} else if (groupCallsEnabled) {
|
||||
if (useElementCallExclusively) {
|
||||
if (hasGroupCall) {
|
||||
return makeVideoCallButton(new DisabledWithReason(_t("Ongoing call")));
|
||||
return makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call")));
|
||||
} else if (mayCreateElementCalls) {
|
||||
return makeVideoCallButton("element");
|
||||
} else {
|
||||
return makeVideoCallButton(
|
||||
new DisabledWithReason(_t("You do not have permission to start video calls")),
|
||||
);
|
||||
return makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call")));
|
||||
}
|
||||
} else if (hasLegacyCall || hasJitsiWidget || hasGroupCall) {
|
||||
return (
|
||||
<>
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("Ongoing call")))}
|
||||
{makeVideoCallButton(new DisabledWithReason(_t("Ongoing call")))}
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call")))}
|
||||
{makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call")))}
|
||||
</>
|
||||
);
|
||||
} else if (functionalMembers.length <= 1) {
|
||||
return (
|
||||
<>
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("There's no one here to call")))}
|
||||
{makeVideoCallButton(new DisabledWithReason(_t("There's no one here to call")))}
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_no_one_here")))}
|
||||
{makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_one_here")))}
|
||||
</>
|
||||
);
|
||||
} else if (functionalMembers.length === 2) {
|
||||
@ -329,10 +327,10 @@ const CallButtons: FC<CallButtonsProps> = ({ room }) => {
|
||||
} else {
|
||||
const videoCallBehavior = mayCreateElementCalls
|
||||
? "element"
|
||||
: new DisabledWithReason(_t("You do not have permission to start video calls"));
|
||||
: new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call"));
|
||||
return (
|
||||
<>
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("You do not have permission to start voice calls")))}
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_voice_call")))}
|
||||
{makeVideoCallButton(videoCallBehavior)}
|
||||
</>
|
||||
);
|
||||
@ -340,15 +338,15 @@ const CallButtons: FC<CallButtonsProps> = ({ room }) => {
|
||||
} else if (hasLegacyCall || hasJitsiWidget) {
|
||||
return (
|
||||
<>
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("Ongoing call")))}
|
||||
{makeVideoCallButton(new DisabledWithReason(_t("Ongoing call")))}
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call")))}
|
||||
{makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_ongoing_call")))}
|
||||
</>
|
||||
);
|
||||
} else if (functionalMembers.length <= 1) {
|
||||
return (
|
||||
<>
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("There's no one here to call")))}
|
||||
{makeVideoCallButton(new DisabledWithReason(_t("There's no one here to call")))}
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_no_one_here")))}
|
||||
{makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_one_here")))}
|
||||
</>
|
||||
);
|
||||
} else if (functionalMembers.length === 2 || mayEditWidgets) {
|
||||
@ -361,8 +359,8 @@ const CallButtons: FC<CallButtonsProps> = ({ room }) => {
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("You do not have permission to start voice calls")))}
|
||||
{makeVideoCallButton(new DisabledWithReason(_t("You do not have permission to start video calls")))}
|
||||
{makeVoiceCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_voice_call")))}
|
||||
{makeVideoCallButton(new DisabledWithReason(_t("voip|disabled_no_perms_start_video_call")))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -764,7 +762,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||
|
||||
const buttons = this.props.showButtons ? this.renderButtons(isVideoRoom) : null;
|
||||
|
||||
let oobName = _t("Join Room");
|
||||
let oobName = _t("common|unnamed_room");
|
||||
if (this.props.oobData && this.props.oobData.name) {
|
||||
oobName = this.props.oobData.name;
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onH
|
||||
<AccessibleButton onClick={toggleExpanded}>
|
||||
{expanded
|
||||
? _t("action|collapse")
|
||||
: _t("Show %(count)s other previews", { count: previews.length - showPreviews.length })}
|
||||
: _t("timeline|url_preview|show_n_more", { count: previews.length - showPreviews.length })}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
@ -72,7 +72,7 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onH
|
||||
<AccessibleButton
|
||||
className="mx_LinkPreviewGroup_hide"
|
||||
onClick={onCancelClick}
|
||||
aria-label={_t("Close preview")}
|
||||
aria-label={_t("timeline|url_preview|close")}
|
||||
>
|
||||
<img
|
||||
className="mx_filterFlipColor"
|
||||
|
@ -354,9 +354,9 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||
let inviteButton: JSX.Element | undefined;
|
||||
|
||||
if (room?.getMyMembership() === "join" && shouldShowComponent(UIComponent.InviteUsers)) {
|
||||
let inviteButtonText = _t("Invite to this room");
|
||||
let inviteButtonText = _t("room|invite_this_room");
|
||||
if (room.isSpaceRoom()) {
|
||||
inviteButtonText = _t("Invite to this space");
|
||||
inviteButtonText = _t("space|invite_this_space");
|
||||
}
|
||||
|
||||
if (this.state.canInvite) {
|
||||
|
@ -215,7 +215,7 @@ const NewRoomIntro: React.FC = () => {
|
||||
defaultDispatcher.dispatch({ action: "view_invite", roomId });
|
||||
}}
|
||||
>
|
||||
{_t("Invite to this room")}
|
||||
{_t("room|invite_this_room")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
|
@ -50,7 +50,7 @@ const RoomBreadcrumbTile: React.FC<{ room: Room; onClick: (ev: ButtonEvent) => v
|
||||
<AccessibleTooltipButton
|
||||
className="mx_RoomBreadcrumbs_crumb"
|
||||
onClick={onClick}
|
||||
aria-label={_t("Room %(name)s", { name: room.name })}
|
||||
aria-label={_t("a11y|room_name", { name: room.name })}
|
||||
title={room.name}
|
||||
tooltipClassName="mx_RoomBreadcrumbs_Tooltip"
|
||||
onFocus={onFocus}
|
||||
@ -123,7 +123,7 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
|
||||
// NOTE: The CSSTransition timeout MUST match the timeout in our CSS!
|
||||
return (
|
||||
<CSSTransition appear={true} in={this.state.doAnimation} timeout={640} classNames="mx_RoomBreadcrumbs">
|
||||
<Toolbar className="mx_RoomBreadcrumbs" aria-label={_t("Recently visited rooms")}>
|
||||
<Toolbar className="mx_RoomBreadcrumbs" aria-label={_t("room_list|breadcrumbs_label")}>
|
||||
{tiles.slice(this.state.skipFirst ? 1 : 0)}
|
||||
</Toolbar>
|
||||
</CSSTransition>
|
||||
@ -131,7 +131,7 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
|
||||
} else {
|
||||
return (
|
||||
<div className="mx_RoomBreadcrumbs">
|
||||
<div className="mx_RoomBreadcrumbs_placeholder">{_t("No recently visited rooms")}</div>
|
||||
<div className="mx_RoomBreadcrumbs_placeholder">{_t("room_list|breadcrumbs_empty")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -131,12 +131,12 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
||||
{roomName}
|
||||
|
||||
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
|
||||
<Tooltip label={_t("Public room")}>
|
||||
<Tooltip label={_t("common|public_room")}>
|
||||
<PublicIcon
|
||||
width="16px"
|
||||
height="16px"
|
||||
className="text-secondary"
|
||||
aria-label={_t("Public room")}
|
||||
aria-label={_t("common|public_room")}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
@ -153,12 +153,12 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
||||
)}
|
||||
|
||||
{isDirectMessage && e2eStatus === E2EStatus.Warning && (
|
||||
<Tooltip label={_t("Untrusted")}>
|
||||
<Tooltip label={_t("room|header_untrusted_label")}>
|
||||
<ErrorIcon
|
||||
width="16px"
|
||||
height="16px"
|
||||
className="mx_Untrusted"
|
||||
aria-label={_t("Untrusted")}
|
||||
aria-label={_t("room|header_untrusted_label")}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
@ -51,13 +51,13 @@ const RoomInfoLine: FC<IProps> = ({ room }) => {
|
||||
let roomType: string;
|
||||
if (isVideoRoom) {
|
||||
iconClass = "mx_RoomInfoLine_video";
|
||||
roomType = _t("Video room");
|
||||
roomType = _t("common|video_room");
|
||||
} else if (joinRule === JoinRule.Public) {
|
||||
iconClass = "mx_RoomInfoLine_public";
|
||||
roomType = room.isSpaceRoom() ? _t("Public space") : _t("Public room");
|
||||
roomType = room.isSpaceRoom() ? _t("common|public_space") : _t("common|public_room");
|
||||
} else {
|
||||
iconClass = "mx_RoomInfoLine_private";
|
||||
roomType = room.isSpaceRoom() ? _t("Private space") : _t("Private room");
|
||||
roomType = room.isSpaceRoom() ? _t("common|private_space") : _t("common|private_room");
|
||||
}
|
||||
|
||||
let members: JSX.Element | undefined;
|
||||
|
@ -132,7 +132,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
|
||||
<IconizedContextMenuOptionList first>
|
||||
{showCreateRooms && (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Start new chat")}
|
||||
label={_t("action|start_new_chat")}
|
||||
iconClassName="mx_RoomList_iconStartChat"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -148,7 +148,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
|
||||
)}
|
||||
{showInviteUsers && (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Invite to space")}
|
||||
label={_t("action|invite_to_space")}
|
||||
iconClassName="mx_RoomList_iconInvite"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -172,8 +172,8 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
|
||||
onClick={openMenu}
|
||||
className="mx_RoomSublist_auxButton"
|
||||
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
||||
aria-label={_t("Add people")}
|
||||
title={_t("Add people")}
|
||||
aria-label={_t("action|add_people")}
|
||||
title={_t("action|add_people")}
|
||||
isExpanded={menuDisplayed}
|
||||
inputRef={handle}
|
||||
/>
|
||||
@ -222,7 +222,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||
contextMenuContent = (
|
||||
<IconizedContextMenuOptionList first>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore rooms")}
|
||||
label={_t("action|explore_rooms")}
|
||||
iconClassName="mx_RoomList_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -239,7 +239,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||
{showCreateRoom ? (
|
||||
<>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("New room")}
|
||||
label={_t("action|new_room")}
|
||||
iconClassName="mx_RoomList_iconNewRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -253,7 +253,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||
/>
|
||||
{videoRoomsEnabled && (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("New video room")}
|
||||
label={_t("action|new_video_room")}
|
||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -271,7 +271,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||
</IconizedContextMenuOption>
|
||||
)}
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Add existing room")}
|
||||
label={_t("action|add_existing_room")}
|
||||
iconClassName="mx_RoomList_iconAddExistingRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -292,7 +292,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||
{showCreateRoom && (
|
||||
<>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("New room")}
|
||||
label={_t("action|new_room")}
|
||||
iconClassName="mx_RoomList_iconNewRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -304,7 +304,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||
/>
|
||||
{videoRoomsEnabled && (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("New video room")}
|
||||
label={_t("action|new_video_room")}
|
||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -325,7 +325,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||
)}
|
||||
{showExploreRooms ? (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore public rooms")}
|
||||
label={_t("action|explore_public_rooms")}
|
||||
iconClassName="mx_RoomList_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
@ -203,7 +203,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
<>
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_RoomListHeader_iconNewRoom"
|
||||
label={_t("New room")}
|
||||
label={_t("action|new_room")}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@ -215,7 +215,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
{videoRoomsEnabled && (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
|
||||
label={_t("New video room")}
|
||||
label={_t("action|new_video_room")}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@ -243,7 +243,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
{inviteOption}
|
||||
{newRoomOptions}
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Explore rooms")}
|
||||
label={_t("action|explore_rooms")}
|
||||
iconClassName="mx_RoomListHeader_iconExplore"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -258,7 +258,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
}}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Add existing room")}
|
||||
label={_t("action|add_existing_room")}
|
||||
iconClassName="mx_RoomListHeader_iconPlus"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -296,7 +296,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
newRoomOpts = (
|
||||
<>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("Start new chat")}
|
||||
label={_t("action|start_new_chat")}
|
||||
iconClassName="mx_RoomListHeader_iconStartChat"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -307,7 +307,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
}}
|
||||
/>
|
||||
<IconizedContextMenuOption
|
||||
label={_t("New room")}
|
||||
label={_t("action|new_room")}
|
||||
iconClassName="mx_RoomListHeader_iconNewRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
@ -319,7 +319,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||
/>
|
||||
{videoRoomsEnabled && (
|
||||
<IconizedContextMenuOption
|
||||
label={_t("New video room")}
|
||||
label={_t("action|new_video_room")}
|
||||
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
@ -650,9 +650,9 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
||||
{({ onFocus, isActive, ref }) => {
|
||||
const tabIndex = isActive ? 0 : -1;
|
||||
|
||||
let ariaLabel = _t("Jump to first unread room.");
|
||||
let ariaLabel = _t("a11y_jump_first_unread_room");
|
||||
if (this.props.tagId === DefaultTagID.Invite) {
|
||||
ariaLabel = _t("Jump to first invite.");
|
||||
ariaLabel = _t("a11y|jump_first_invite");
|
||||
}
|
||||
|
||||
const badge = (
|
||||
|
@ -35,7 +35,7 @@ export default class TopUnreadMessagesBar extends React.PureComponent<IProps> {
|
||||
/>
|
||||
<AccessibleButton
|
||||
className="mx_TopUnreadMessagesBar_markAsRead"
|
||||
title={_t("Mark all as read")}
|
||||
title={_t("notifications|mark_all_read")}
|
||||
onClick={this.props.onCloseClick}
|
||||
/>
|
||||
</div>
|
||||
|
@ -55,7 +55,7 @@ export const AddPrivilegedUsers: React.FC<AddPrivilegedUsersProps> = ({ room, de
|
||||
if (powerLevelEvent === null) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("common|error"),
|
||||
description: _t("Failed to change power level"),
|
||||
description: _t("error|update_power_level"),
|
||||
});
|
||||
|
||||
return;
|
||||
@ -68,7 +68,7 @@ export const AddPrivilegedUsers: React.FC<AddPrivilegedUsersProps> = ({ room, de
|
||||
} catch (error) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("common|error"),
|
||||
description: _t("Failed to change power level"),
|
||||
description: _t("error|update_power_level"),
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@ -78,13 +78,13 @@ export const AddPrivilegedUsers: React.FC<AddPrivilegedUsersProps> = ({ room, de
|
||||
return (
|
||||
<form style={{ display: "flex" }} onSubmit={onSubmit}>
|
||||
<SettingsFieldset
|
||||
legend={_t("Add privileged users")}
|
||||
description={_t("Give one or multiple users in this room more privileges")}
|
||||
legend={_t("room_settings|permissions|add_privileged_user_heading")}
|
||||
description={_t("room_settings|permissions|add_privileged_user_description")}
|
||||
style={{ flexGrow: 1 }}
|
||||
>
|
||||
<AutocompleteInput
|
||||
provider={userProvider.current}
|
||||
placeholder={_t("Search users in this room…")}
|
||||
placeholder={_t("room_settings|permissions|add_privileged_user_filter_placeholder")}
|
||||
onSelectionChange={setSelectedUsers}
|
||||
selection={selectedUsers}
|
||||
additionalFilter={hasLowerOrEqualLevelThanDefaultLevelFilter}
|
||||
|
@ -42,7 +42,7 @@ export default class ChangeDisplayName extends React.Component {
|
||||
return (
|
||||
<EditableTextContainer
|
||||
getInitialValue={this.getDisplayName}
|
||||
placeholder={_t("No display name")}
|
||||
placeholder={_t("settings|general|name_placeholder")}
|
||||
blurToSubmit={true}
|
||||
onSubmit={this.changeDisplayName}
|
||||
/>
|
||||
|
@ -258,7 +258,7 @@ export default class CrossSigningPanel extends React.PureComponent<{}, IState> {
|
||||
<>
|
||||
{summarisedStatus}
|
||||
<details>
|
||||
<summary>{_t("common|Advanced")}</summary>
|
||||
<summary>{_t("common|advanced")}</summary>
|
||||
<table className="mx_CrossSigningPanel_statusList">
|
||||
<tr>
|
||||
<th scope="row">{_t("settings|security|cross_signing_public_keys")}</th>
|
||||
|
@ -227,11 +227,11 @@ export default class EventIndexPanel extends React.Component<{}, IState> {
|
||||
{EventIndexPeg.error && (
|
||||
<SettingsSubsectionText>
|
||||
<details>
|
||||
<summary>{_t("common|Advanced")}</summary>
|
||||
<summary>{_t("common|advanced")}</summary>
|
||||
<code>
|
||||
{EventIndexPeg.error instanceof Error
|
||||
? EventIndexPeg.error.message
|
||||
: _t("Unknown error")}
|
||||
: _t("error|unknown")}
|
||||
</code>
|
||||
<p>
|
||||
<AccessibleButton key="delete" kind="danger" onClick={this.confirmEventStoreReset}>
|
||||
|
@ -89,7 +89,7 @@ export default class IntegrationManager extends React.Component<IProps, IState>
|
||||
if (this.props.loading) {
|
||||
return (
|
||||
<div className="mx_IntegrationManager_loading">
|
||||
<Heading size="3">{_t("Connecting to integration manager…")}</Heading>
|
||||
<Heading size="3">{_t("integration_manager|connecting")}</Heading>
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
@ -98,8 +98,8 @@ export default class IntegrationManager extends React.Component<IProps, IState>
|
||||
if (!this.props.connected || this.state.errored) {
|
||||
return (
|
||||
<div className="mx_IntegrationManager_error">
|
||||
<Heading size="3">{_t("Cannot connect to integration manager")}</Heading>
|
||||
<p>{_t("The integration manager is offline or it cannot reach your homeserver.")}</p>
|
||||
<Heading size="3">{_t("integration_manager|error_connecting_heading")}</Heading>
|
||||
<p>{_t("integration_manager|error_connecting")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -131,15 +131,15 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
|
||||
const roomId = await upgradeRoom(room, targetVersion, opts.invite, true, true, true, (progress) => {
|
||||
const total = 2 + progress.updateSpacesTotal + progress.inviteUsersTotal;
|
||||
if (!progress.roomUpgraded) {
|
||||
fn(_t("Upgrading room"), 0, total);
|
||||
fn(_t("room_settings|security|join_rule_upgrade_upgrading_room"), 0, total);
|
||||
} else if (!progress.roomSynced) {
|
||||
fn(_t("Loading new room"), 1, total);
|
||||
fn(_t("room_settings|security|join_rule_upgrade_awaiting_room"), 1, total);
|
||||
} else if (
|
||||
progress.inviteUsersProgress !== undefined &&
|
||||
progress.inviteUsersProgress < progress.inviteUsersTotal
|
||||
) {
|
||||
fn(
|
||||
_t("Sending invites... (%(progress)s out of %(count)s)", {
|
||||
_t("room_settings|security|join_rule_upgrade_sending_invites", {
|
||||
progress: progress.inviteUsersProgress,
|
||||
count: progress.inviteUsersTotal,
|
||||
}),
|
||||
@ -151,7 +151,7 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
|
||||
progress.updateSpacesProgress < progress.updateSpacesTotal
|
||||
) {
|
||||
fn(
|
||||
_t("Updating spaces... (%(progress)s out of %(count)s)", {
|
||||
_t("room_settings|security|join_rule_upgrade_updating_spaces", {
|
||||
progress: progress.updateSpacesProgress,
|
||||
count: progress.updateSpacesTotal,
|
||||
}),
|
||||
@ -179,7 +179,11 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const upgradeRequiredPill = <span className="mx_JoinRuleSettings_upgradeRequired">{_t("Upgrade required")}</span>;
|
||||
const upgradeRequiredPill = (
|
||||
<span className="mx_JoinRuleSettings_upgradeRequired">
|
||||
{_t("room_settings|security|join_rule_upgrade_required")}
|
||||
</span>
|
||||
);
|
||||
|
||||
const definitions: IDefinition<JoinRule>[] = [
|
||||
{
|
||||
@ -213,11 +217,11 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
|
||||
let moreText;
|
||||
if (shownSpaces.length < restrictedAllowRoomIds.length) {
|
||||
if (shownSpaces.length > 0) {
|
||||
moreText = _t("& %(count)s more", {
|
||||
moreText = _t("room_settings|security|join_rule_restricted_n_more", {
|
||||
count: restrictedAllowRoomIds.length - shownSpaces.length,
|
||||
});
|
||||
} else {
|
||||
moreText = _t("Currently, %(count)s spaces have access", {
|
||||
moreText = _t("room_settings|security|join_rule_restricted_summary", {
|
||||
count: restrictedAllowRoomIds.length,
|
||||
});
|
||||
}
|
||||
@ -256,7 +260,7 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
|
||||
<div>
|
||||
<span>
|
||||
{_t(
|
||||
"Anyone in a space can find and join. <a>Edit which spaces can access here.</a>",
|
||||
"room_settings|security|join_rule_restricted_description",
|
||||
{},
|
||||
{
|
||||
a: (sub) => (
|
||||
@ -273,7 +277,7 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
|
||||
</span>
|
||||
|
||||
<div className="mx_JoinRuleSettings_spacesWithAccess">
|
||||
<h4>{_t("Spaces with access")}</h4>
|
||||
<h4>{_t("room_settings|security|join_rule_restricted_description_spaces")}</h4>
|
||||
{shownSpaces.map((room) => {
|
||||
return (
|
||||
<span key={room.roomId}>
|
||||
@ -288,21 +292,21 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
|
||||
);
|
||||
} else if (SpaceStore.instance.activeSpaceRoom) {
|
||||
description = _t(
|
||||
"Anyone in <spaceName/> can find and join. You can select other spaces too.",
|
||||
"room_settings|security|join_rule_restricted_description_active_space",
|
||||
{},
|
||||
{
|
||||
spaceName: () => <b>{SpaceStore.instance.activeSpaceRoom!.name}</b>,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
description = _t("Anyone in a space can find and join. You can select multiple spaces.");
|
||||
description = _t("room_settings|security|join_rule_restricted_description_prompt");
|
||||
}
|
||||
|
||||
definitions.splice(1, 0, {
|
||||
value: JoinRule.Restricted,
|
||||
label: (
|
||||
<>
|
||||
{_t("Space members")}
|
||||
{_t("room_settings|security|join_rule_restricted")}
|
||||
{preferredRestrictionVersion && upgradeRequiredPill}
|
||||
</>
|
||||
),
|
||||
@ -317,20 +321,20 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
|
||||
value: JoinRule.Knock,
|
||||
label: (
|
||||
<>
|
||||
{_t("Ask to join")}
|
||||
{_t("room_settings|security|join_rule_knock")}
|
||||
{preferredKnockVersion && upgradeRequiredPill}
|
||||
</>
|
||||
),
|
||||
description: (
|
||||
<>
|
||||
{_t("People cannot join unless access is granted.")}
|
||||
{_t("room_settings|security|join_rule_knock_description")}
|
||||
<LabelledCheckbox
|
||||
className="mx_JoinRuleSettings_labelledCheckbox"
|
||||
disabled={joinRule !== JoinRule.Knock}
|
||||
label={
|
||||
room.isSpaceRoom()
|
||||
? _t("Make this space visible in the public room directory.")
|
||||
: _t("Make this room visible in the public room directory.")
|
||||
? _t("room_settings|security|publish_space")
|
||||
: _t("room_settings|security|publish_room")
|
||||
}
|
||||
onChange={onIsPublicKnockRoomChange}
|
||||
value={isPublicKnockRoom}
|
||||
@ -359,21 +363,13 @@ const JoinRuleSettings: React.FC<JoinRuleSettingsProps> = ({
|
||||
(roomId) => !cli.getRoom(roomId)?.currentState.maySendStateEvent(EventType.SpaceChild, userId),
|
||||
);
|
||||
if (unableToUpdateSomeParents) {
|
||||
warning = (
|
||||
<b>
|
||||
{_t(
|
||||
"This room is in some spaces you're not an admin of. In those spaces, the old room will still be shown, but people will be prompted to join the new one.",
|
||||
)}
|
||||
</b>
|
||||
);
|
||||
warning = <b>{_t("room_settings|security|join_rule_restricted_upgrade_warning")}</b>;
|
||||
}
|
||||
|
||||
upgradeRequiredDialog(
|
||||
targetVersion,
|
||||
<>
|
||||
{_t(
|
||||
"This upgrade will allow members of selected spaces access to this room without an invite.",
|
||||
)}
|
||||
{_t("room_settings|security|join_rule_restricted_upgrade_description")}
|
||||
{warning}
|
||||
</>,
|
||||
);
|
||||
|
@ -749,7 +749,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
className="mx_UserNotifSettings_clearNotifsButton"
|
||||
data-testid="clear-notifications"
|
||||
>
|
||||
{_t("Mark all as read")}
|
||||
{_t("notifications|mark_all_read")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
}
|
||||
@ -759,7 +759,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
if (clearNotifsButton) {
|
||||
return (
|
||||
<div className="mx_UserNotifSettings_floatingSection">
|
||||
<div>{_t("Other")}</div>
|
||||
<div>{_t("notifications|class_other")}</div>
|
||||
{clearNotifsButton}
|
||||
</div>
|
||||
);
|
||||
@ -776,8 +776,8 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
onAdd={this.onKeywordAdd}
|
||||
onRemove={this.onKeywordRemove}
|
||||
disabled={this.state.phase === Phase.Persisting}
|
||||
label={_t("Keyword")}
|
||||
placeholder={_t("New keyword")}
|
||||
label={_t("notifications|keyword")}
|
||||
placeholder={_t("notifications|keyword_new")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -811,11 +811,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
{makeRadio(r, VectorState.Loud)}
|
||||
{this.state.ruleIdsWithError[r.ruleId] && (
|
||||
<div className="mx_UserNotifSettings_gridRowError">
|
||||
<Caption isError>
|
||||
{_t(
|
||||
"An error occurred when updating your notification preferences. Please try to toggle your option again.",
|
||||
)}
|
||||
</Caption>
|
||||
<Caption isError>{_t("settings|notifications|error_updating")}</Caption>
|
||||
</div>
|
||||
)}
|
||||
</fieldset>
|
||||
@ -824,13 +820,13 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
let sectionName: string;
|
||||
switch (category) {
|
||||
case RuleClass.VectorGlobal:
|
||||
sectionName = _t("Global");
|
||||
sectionName = _t("notifications|class_global");
|
||||
break;
|
||||
case RuleClass.VectorMentions:
|
||||
sectionName = _t("Mentions & keywords");
|
||||
sectionName = _t("notifications|mentions_keywords");
|
||||
break;
|
||||
case RuleClass.VectorOther:
|
||||
sectionName = _t("Other");
|
||||
sectionName = _t("notifications|class_other");
|
||||
break;
|
||||
default:
|
||||
throw new Error("Developer error: Unnamed notifications section: " + category);
|
||||
@ -865,7 +861,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
|
||||
return (
|
||||
<div className="mx_UserNotifSettings_floatingSection">
|
||||
<div>{_t("Notification targets")}</div>
|
||||
<div>{_t("settings|notifications|push_targets")}</div>
|
||||
<table>
|
||||
<tbody>{rows}</tbody>
|
||||
</table>
|
||||
@ -878,7 +874,7 @@ export default class Notifications extends React.PureComponent<IProps, IState> {
|
||||
// Ends up default centered
|
||||
return <Spinner />;
|
||||
} else if (this.state.phase === Phase.Error) {
|
||||
return <p data-testid="error-message">{_t("There was an error loading your notification settings.")}</p>;
|
||||
return <p data-testid="error-message">{_t("settings|notifications|error_loading")}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -123,8 +123,8 @@ export default class ProfileSettings extends React.Component<{}, IState> {
|
||||
} catch (err) {
|
||||
logger.log("Failed to save profile", err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Failed to save your profile"),
|
||||
description: err instanceof Error ? err.message : _t("The operation could not be completed"),
|
||||
title: _t("settings|general|error_saving_profile_title"),
|
||||
description: err instanceof Error ? err.message : _t("settings|general|error_saving_profile"),
|
||||
});
|
||||
}
|
||||
|
||||
@ -184,9 +184,9 @@ export default class ProfileSettings extends React.Component<{}, IState> {
|
||||
/>
|
||||
<div className="mx_ProfileSettings_profile">
|
||||
<div className="mx_ProfileSettings_profile_controls">
|
||||
<SettingsSubsectionHeading heading={_t("Profile")} />
|
||||
<SettingsSubsectionHeading heading={_t("common|profile")} />
|
||||
<Field
|
||||
label={_t("Display Name")}
|
||||
label={_t("common|display_name")}
|
||||
type="text"
|
||||
value={this.state.displayName}
|
||||
autoComplete="off"
|
||||
@ -201,7 +201,7 @@ export default class ProfileSettings extends React.Component<{}, IState> {
|
||||
<AvatarSetting
|
||||
avatarUrl={avatarUrl}
|
||||
avatarName={this.state.displayName || this.userId}
|
||||
avatarAltText={_t("Profile picture")}
|
||||
avatarAltText={_t("common|user_avatar")}
|
||||
uploadAvatar={this.uploadAvatar}
|
||||
removeAvatar={this.removeAvatar}
|
||||
/>
|
||||
|
@ -195,11 +195,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||
|
||||
private deleteBackup = (): void => {
|
||||
Modal.createDialog(QuestionDialog, {
|
||||
title: _t("Delete Backup"),
|
||||
description: _t(
|
||||
"Are you sure? You will lose your encrypted messages if your keys are not backed up properly.",
|
||||
),
|
||||
button: _t("Delete Backup"),
|
||||
title: _t("settings|security|delete_backup"),
|
||||
description: _t("settings|security|delete_backup_confirm_description"),
|
||||
button: _t("settings|security|delete_backup"),
|
||||
danger: true,
|
||||
onFinished: (proceed) => {
|
||||
if (!proceed) return;
|
||||
@ -253,36 +251,30 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||
if (error) {
|
||||
statusDescription = (
|
||||
<SettingsSubsectionText className="error">
|
||||
{_t("Unable to load key backup status")}
|
||||
{_t("settings|security|error_loading_key_backup_status")}
|
||||
</SettingsSubsectionText>
|
||||
);
|
||||
} else if (loading) {
|
||||
statusDescription = <Spinner />;
|
||||
} else if (backupInfo) {
|
||||
let restoreButtonCaption = _t("Restore from Backup");
|
||||
let restoreButtonCaption = _t("settings|security|restore_key_backup");
|
||||
|
||||
if (this.state.activeBackupVersion !== null) {
|
||||
statusDescription = (
|
||||
<SettingsSubsectionText>✅ {_t("This session is backing up your keys.")}</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>✅ {_t("settings|security|key_backup_active")}</SettingsSubsectionText>
|
||||
);
|
||||
} else {
|
||||
statusDescription = (
|
||||
<>
|
||||
<SettingsSubsectionText>
|
||||
{_t(
|
||||
"This session is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.",
|
||||
{},
|
||||
{ b: (sub) => <b>{sub}</b> },
|
||||
)}
|
||||
{_t("settings|security|key_backup_inactive", {}, { b: (sub) => <b>{sub}</b> })}
|
||||
</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>
|
||||
{_t(
|
||||
"Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.",
|
||||
)}
|
||||
{_t("settings|security|key_backup_connect_prompt")}
|
||||
</SettingsSubsectionText>
|
||||
</>
|
||||
);
|
||||
restoreButtonCaption = _t("Connect this session to Key Backup");
|
||||
restoreButtonCaption = _t("settings|security|key_backup_connect");
|
||||
}
|
||||
|
||||
let uploadStatus: ReactNode;
|
||||
@ -292,33 +284,38 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||
} else if (sessionsRemaining > 0) {
|
||||
uploadStatus = (
|
||||
<div>
|
||||
{_t("Backing up %(sessionsRemaining)s keys…", { sessionsRemaining })} <br />
|
||||
{_t("settings|security|key_backup_in_progress", { sessionsRemaining })} <br />
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
uploadStatus = (
|
||||
<div>
|
||||
{_t("All keys backed up")} <br />
|
||||
{_t("settings|security|key_backup_complete")} <br />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let trustedLocally: string | undefined;
|
||||
if (backupTrustInfo?.matchesDecryptionKey) {
|
||||
trustedLocally = _t("This backup can be restored on this session");
|
||||
trustedLocally = _t("settings|security|key_backup_can_be_restored");
|
||||
}
|
||||
|
||||
extraDetailsTableRows = (
|
||||
<>
|
||||
<tr>
|
||||
<th scope="row">{_t("Latest backup version on server:")}</th>
|
||||
<th scope="row">{_t("settings|security|key_backup_latest_version")}</th>
|
||||
<td>
|
||||
{backupInfo.version} ({_t("Algorithm:")} <code>{backupInfo.algorithm}</code>)
|
||||
{backupInfo.version} ({_t("settings|security|key_backup_algorithm")}{" "}
|
||||
<code>{backupInfo.algorithm}</code>)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{_t("Active backup version:")}</th>
|
||||
<td>{this.state.activeBackupVersion === null ? _t("None") : this.state.activeBackupVersion}</td>
|
||||
<th scope="row">{_t("settings|security|key_backup_active_version")}</th>
|
||||
<td>
|
||||
{this.state.activeBackupVersion === null
|
||||
? _t("settings|security|key_backup_active_version_none")
|
||||
: this.state.activeBackupVersion}
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
@ -339,7 +336,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||
if (!isSecureBackupRequired(MatrixClientPeg.safeGet())) {
|
||||
actions.push(
|
||||
<AccessibleButton key="delete" kind="danger" onClick={this.deleteBackup}>
|
||||
{_t("Delete Backup")}
|
||||
{_t("settings|security|delete_backup")}
|
||||
</AccessibleButton>,
|
||||
);
|
||||
}
|
||||
@ -347,11 +344,7 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||
statusDescription = (
|
||||
<>
|
||||
<SettingsSubsectionText>
|
||||
{_t(
|
||||
"Your keys are <b>not being backed up from this session</b>.",
|
||||
{},
|
||||
{ b: (sub) => <b>{sub}</b> },
|
||||
)}
|
||||
{_t("settings|security|key_backup_inactive_warning", {}, { b: (sub) => <b>{sub}</b> })}
|
||||
</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>
|
||||
{_t("Back up your keys before signing out to avoid losing them.")}
|
||||
@ -377,9 +370,9 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||
if (backupKeyCached) {
|
||||
backupKeyWellFormedText = ", ";
|
||||
if (backupKeyWellFormed) {
|
||||
backupKeyWellFormedText += _t("well formed");
|
||||
backupKeyWellFormedText += _t("settings|security|backup_key_well_formed");
|
||||
} else {
|
||||
backupKeyWellFormedText += _t("unexpected type");
|
||||
backupKeyWellFormedText += _t("settings|security|backup_key_unexpected_type");
|
||||
}
|
||||
}
|
||||
|
||||
@ -390,25 +383,21 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSubsectionText>
|
||||
{_t(
|
||||
"Back up your encryption keys with your account data in case you lose access to your sessions. Your keys will be secured with a unique Security Key.",
|
||||
)}
|
||||
</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>{_t("settings|security|backup_keys_description")}</SettingsSubsectionText>
|
||||
{statusDescription}
|
||||
<details>
|
||||
<summary>{_t("common|Advanced")}</summary>
|
||||
<summary>{_t("common|advanced")}</summary>
|
||||
<table className="mx_SecureBackupPanel_statusList">
|
||||
<tr>
|
||||
<th scope="row">{_t("Backup key stored:")}</th>
|
||||
<th scope="row">{_t("settings|security|backup_key_stored_status")}</th>
|
||||
<td>
|
||||
{backupKeyStored === true
|
||||
? _t("settings|security|cross_signing_in_4s")
|
||||
: _t("not stored")}
|
||||
: _t("settings|security|cross_signing_not_stored")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{_t("Backup key cached:")}</th>
|
||||
<th scope="row">{_t("settings|security|backup_key_cached_status")}</th>
|
||||
<td>
|
||||
{backupKeyCached
|
||||
? _t("settings|security|cross_signing_cached")
|
||||
@ -417,16 +406,20 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{_t("Secret storage public key:")}</th>
|
||||
<th scope="row">{_t("settings|security|4s_public_key_status")}</th>
|
||||
<td>
|
||||
{secretStorageKeyInAccount
|
||||
? _t("in account data")
|
||||
? _t("settings|security|4s_public_key_in_account_data")
|
||||
: _t("settings|security|cross_signing_not_found")}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{_t("Secret storage:")}</th>
|
||||
<td>{secretStorageReady ? _t("ready") : _t("not ready")}</td>
|
||||
<th scope="row">{_t("settings|security|secret_storage_status")}</th>
|
||||
<td>
|
||||
{secretStorageReady
|
||||
? _t("settings|security|secret_storage_ready")
|
||||
: _t("settings|security|secret_storage_not_ready")}
|
||||
</td>
|
||||
</tr>
|
||||
{extraDetailsTableRows}
|
||||
</table>
|
||||
|
@ -47,7 +47,7 @@ const REACHABILITY_TIMEOUT = 10000; // ms
|
||||
async function checkIdentityServerUrl(u: string): Promise<string | null> {
|
||||
const parsedUrl = parseUrl(u);
|
||||
|
||||
if (parsedUrl.protocol !== "https:") return _t("Identity server URL must be HTTPS");
|
||||
if (parsedUrl.protocol !== "https:") return _t("identity_server|url_not_https");
|
||||
|
||||
// XXX: duplicated logic from js-sdk but it's quite tied up in the validation logic in the
|
||||
// js-sdk so probably as easy to duplicate it than to separate it out so we can reuse it
|
||||
@ -56,12 +56,12 @@ async function checkIdentityServerUrl(u: string): Promise<string | null> {
|
||||
if (response.ok) {
|
||||
return null;
|
||||
} else if (response.status < 200 || response.status >= 300) {
|
||||
return _t("Not a valid identity server (status code %(code)s)", { code: response.status });
|
||||
return _t("identity_server|error_invalid", { code: response.status });
|
||||
} else {
|
||||
return _t("Could not connect to identity server");
|
||||
return _t("identity_server|error_connection");
|
||||
}
|
||||
} catch (e) {
|
||||
return _t("Could not connect to identity server");
|
||||
return _t("identity_server|error_connection");
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,7 +133,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||
return (
|
||||
<div>
|
||||
<InlineSpinner />
|
||||
{_t("Checking server")}
|
||||
{_t("identity_server|checking")}
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.error) {
|
||||
@ -191,9 +191,9 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||
// 3PIDs that would be left behind.
|
||||
if (save && currentClientIdServer && fullUrl !== currentClientIdServer) {
|
||||
const [confirmed] = await this.showServerChangeWarning({
|
||||
title: _t("Change identity server"),
|
||||
title: _t("identity_server|change"),
|
||||
unboundMessage: _t(
|
||||
"Disconnect from the identity server <current /> and connect to <new /> instead?",
|
||||
"identity_server|change_prompt",
|
||||
{},
|
||||
{
|
||||
current: (sub) => <b>{abbreviateUrl(currentClientIdServer)}</b>,
|
||||
@ -210,7 +210,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
errStr = _t("Terms of service not accepted or the identity server is invalid.");
|
||||
errStr = _t("identity_server|error_invalid_or_terms");
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
@ -226,9 +226,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||
title: _t("terms|identity_server_no_terms_title"),
|
||||
description: (
|
||||
<div>
|
||||
<span className="warning">
|
||||
{_t("The identity server you have chosen does not have any terms of service.")}
|
||||
</span>
|
||||
<span className="warning">{_t("identity_server|no_terms")}</span>
|
||||
<span> {_t("terms|identity_server_no_terms_description_2")}</span>
|
||||
</div>
|
||||
),
|
||||
@ -241,9 +239,9 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||
this.setState({ disconnectBusy: true });
|
||||
try {
|
||||
const [confirmed] = await this.showServerChangeWarning({
|
||||
title: _t("Disconnect identity server"),
|
||||
title: _t("identity_server|disconnect"),
|
||||
unboundMessage: _t(
|
||||
"Disconnect from the identity server <idserver />?",
|
||||
"identity_server|disconnect_server",
|
||||
{},
|
||||
{ idserver: (sub) => <b>{abbreviateUrl(this.state.currentClientIdServer)}</b> },
|
||||
),
|
||||
@ -294,54 +292,34 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||
if (!currentServerReachable) {
|
||||
message = (
|
||||
<div>
|
||||
<p>
|
||||
{_t(
|
||||
"You should <b>remove your personal data</b> from identity server <idserver /> before disconnecting. Unfortunately, identity server <idserver /> is currently offline or cannot be reached.",
|
||||
{},
|
||||
messageElements,
|
||||
)}
|
||||
</p>
|
||||
<p>{_t("You should:")}</p>
|
||||
<p>{_t("identity_server|disconnect_offline_warning", {}, messageElements)}</p>
|
||||
<p>{_t("identity_server|suggestions")}</p>
|
||||
<ul>
|
||||
<li>{_t("identity_server|suggestions_1")}</li>
|
||||
<li>
|
||||
{_t(
|
||||
"check your browser plugins for anything that might block the identity server (such as Privacy Badger)",
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
{_t(
|
||||
"contact the administrators of identity server <idserver />",
|
||||
"identity_server|suggestions_2",
|
||||
{},
|
||||
{
|
||||
idserver: messageElements.idserver,
|
||||
},
|
||||
)}
|
||||
</li>
|
||||
<li>{_t("wait and try again later")}</li>
|
||||
<li>{_t("identity_server|suggestions_3")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
danger = true;
|
||||
button = _t("Disconnect anyway");
|
||||
button = _t("identity_server|disconnect_anyway");
|
||||
} else if (boundThreepids.length) {
|
||||
message = (
|
||||
<div>
|
||||
<p>
|
||||
{_t(
|
||||
"You are still <b>sharing your personal data</b> on the identity server <idserver />.",
|
||||
{},
|
||||
messageElements,
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
{_t(
|
||||
"We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.",
|
||||
)}
|
||||
</p>
|
||||
<p>{_t("identity_server|disconnect_personal_data_warning_1", {}, messageElements)}</p>
|
||||
<p>{_t("identity_server|disconnect_personal_data_warning_2")}</p>
|
||||
</div>
|
||||
);
|
||||
danger = true;
|
||||
button = _t("Disconnect anyway");
|
||||
button = _t("identity_server|disconnect_anyway");
|
||||
} else {
|
||||
message = unboundMessage;
|
||||
}
|
||||
@ -382,37 +360,31 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||
let sectionTitle;
|
||||
let bodyText;
|
||||
if (idServerUrl) {
|
||||
sectionTitle = _t("Identity server (%(server)s)", { server: abbreviateUrl(idServerUrl) });
|
||||
sectionTitle = _t("identity_server|url", { server: abbreviateUrl(idServerUrl) });
|
||||
bodyText = _t(
|
||||
"You are currently using <server></server> to discover and be discoverable by existing contacts you know. You can change your identity server below.",
|
||||
"identity_server|description_connected",
|
||||
{},
|
||||
{ server: (sub) => <b>{abbreviateUrl(idServerUrl)}</b> },
|
||||
);
|
||||
if (this.props.missingTerms) {
|
||||
bodyText = _t(
|
||||
"If you don't want to use <server /> to discover and be discoverable by existing contacts you know, enter another identity server below.",
|
||||
"identity_server|change_server_prompt",
|
||||
{},
|
||||
{ server: (sub) => <b>{abbreviateUrl(idServerUrl)}</b> },
|
||||
);
|
||||
}
|
||||
} else {
|
||||
sectionTitle = _t("common|identity_server");
|
||||
bodyText = _t(
|
||||
"You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.",
|
||||
);
|
||||
bodyText = _t("identity_server|description_disconnected");
|
||||
}
|
||||
|
||||
let discoSection;
|
||||
if (idServerUrl) {
|
||||
let discoButtonContent: React.ReactNode = _t("action|disconnect");
|
||||
let discoBodyText = _t(
|
||||
"Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.",
|
||||
);
|
||||
let discoBodyText = _t("identity_server|disconnect_warning");
|
||||
if (this.props.missingTerms) {
|
||||
discoBodyText = _t(
|
||||
"Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.",
|
||||
);
|
||||
discoButtonContent = _t("Do not use an identity server");
|
||||
discoBodyText = _t("identity_server|description_optional");
|
||||
discoButtonContent = _t("identity_server|do_not_use");
|
||||
}
|
||||
if (this.state.disconnectBusy) {
|
||||
discoButtonContent = <InlineSpinner />;
|
||||
@ -431,7 +403,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||
<SettingsFieldset legend={sectionTitle} description={bodyText}>
|
||||
<form className="mx_SetIdServer" onSubmit={this.checkIdServer}>
|
||||
<Field
|
||||
label={_t("Enter a new identity server")}
|
||||
label={_t("identity_server|url_field_label")}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
placeholder={this.state.defaultIdServer}
|
||||
|
@ -63,12 +63,12 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
|
||||
if (currentManager) {
|
||||
managerName = `(${currentManager.name})`;
|
||||
bodyText = _t(
|
||||
"Use an integration manager <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs.",
|
||||
"integration_manager|use_im_default",
|
||||
{ serverName: currentManager.name },
|
||||
{ b: (sub) => <b>{sub}</b> },
|
||||
);
|
||||
} else {
|
||||
bodyText = _t("Use an integration manager to manage bots, widgets, and sticker packs.");
|
||||
bodyText = _t("integration_manager|use_im");
|
||||
}
|
||||
|
||||
return (
|
||||
@ -79,7 +79,7 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
|
||||
>
|
||||
<div className="mx_SettingsFlag">
|
||||
<div className="mx_SetIntegrationManager_heading_manager">
|
||||
<Heading size="2">{_t("Manage integrations")}</Heading>
|
||||
<Heading size="2">{_t("integration_manager|manage_title")}</Heading>
|
||||
<Heading size="3">{managerName}</Heading>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
@ -90,11 +90,7 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
|
||||
/>
|
||||
</div>
|
||||
<SettingsSubsectionText>{bodyText}</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>
|
||||
{_t(
|
||||
"Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.",
|
||||
)}
|
||||
</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>{_t("integration_manager|explainer")}</SettingsSubsectionText>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ export class ExistingEmailAddress extends React.Component<IExistingEmailAddressP
|
||||
.catch((err) => {
|
||||
logger.error("Unable to remove contact information: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Unable to remove contact information"),
|
||||
title: _t("settings|general|error_remove_3pid"),
|
||||
description: err && err.message ? err.message : _t("invite|failed_generic"),
|
||||
});
|
||||
});
|
||||
@ -99,7 +99,7 @@ export class ExistingEmailAddress extends React.Component<IExistingEmailAddressP
|
||||
return (
|
||||
<div className="mx_GeneralUserSettingsTab_section--discovery_existing">
|
||||
<span className="mx_GeneralUserSettingsTab_section--discovery_existing_promptText">
|
||||
{_t("Remove %(email)s?", { email: this.props.email.address })}
|
||||
{_t("settings|general|remove_email_prompt", { email: this.props.email.address })}
|
||||
</span>
|
||||
<AccessibleButton
|
||||
onClick={this.onActuallyRemove}
|
||||
@ -182,8 +182,8 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||
// TODO: Inline field validation
|
||||
if (!Email.looksValid(email)) {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Invalid Email Address"),
|
||||
description: _t("This doesn't appear to be a valid email address"),
|
||||
title: _t("settings|general|error_invalid_email"),
|
||||
description: _t("settings|general|error_invalid_email_detail"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -199,7 +199,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||
logger.error("Unable to add email address " + email + " " + err);
|
||||
this.setState({ verifying: false, continueDisabled: false, addTask: null });
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Unable to add email address"),
|
||||
title: _t("settings|general|error_add_email"),
|
||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||
});
|
||||
});
|
||||
@ -239,14 +239,12 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||
|
||||
if (underlyingError instanceof MatrixError && underlyingError.errcode === "M_THREEPID_AUTH_FAILED") {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Your email address hasn't been verified yet"),
|
||||
description: _t(
|
||||
"Click the link in the email you received to verify and then click continue again.",
|
||||
),
|
||||
title: _t("settings|general|email_not_verified"),
|
||||
description: _t("settings|general|email_verification_instructions"),
|
||||
});
|
||||
} else {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Unable to verify email address."),
|
||||
title: _t("settings|general|error_email_verification"),
|
||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||
});
|
||||
}
|
||||
@ -273,11 +271,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||
if (this.state.verifying) {
|
||||
addButton = (
|
||||
<div>
|
||||
<div>
|
||||
{_t(
|
||||
"We've sent you an email to verify your address. Please follow the instructions there and then click the button below.",
|
||||
)}
|
||||
</div>
|
||||
<div>{_t("settings|general|add_email_instructions")}</div>
|
||||
<AccessibleButton
|
||||
onClick={this.onContinueClick}
|
||||
kind="primary"
|
||||
@ -295,7 +289,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||
<form onSubmit={this.onAddClick} autoComplete="off" noValidate={true}>
|
||||
<Field
|
||||
type="text"
|
||||
label={_t("Email Address")}
|
||||
label={_t("settings|general|email_address_label")}
|
||||
autoComplete="email"
|
||||
disabled={this.props.disabled || this.state.verifying}
|
||||
value={this.state.newEmailAddress}
|
||||
|
@ -84,7 +84,7 @@ export class ExistingPhoneNumber extends React.Component<IExistingPhoneNumberPro
|
||||
.catch((err) => {
|
||||
logger.error("Unable to remove contact information: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Unable to remove contact information"),
|
||||
title: _t("settings|general|error_remove_3pid"),
|
||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||
});
|
||||
});
|
||||
@ -95,7 +95,7 @@ export class ExistingPhoneNumber extends React.Component<IExistingPhoneNumberPro
|
||||
return (
|
||||
<div className="mx_GeneralUserSettingsTab_section--discovery_existing">
|
||||
<span className="mx_GeneralUserSettingsTab_section--discovery_existing_promptText">
|
||||
{_t("Remove %(phone)s?", { phone: this.props.msisdn.address })}
|
||||
{_t("settings|general|remove_msisdn_prompt", { phone: this.props.msisdn.address })}
|
||||
</span>
|
||||
<AccessibleButton
|
||||
onClick={this.onActuallyRemove}
|
||||
@ -244,11 +244,11 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
|
||||
|
||||
if (underlyingError.errcode !== "M_THREEPID_AUTH_FAILED") {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Unable to verify phone number."),
|
||||
title: _t("settings|general|error_msisdn_verification"),
|
||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||
});
|
||||
} else {
|
||||
this.setState({ verifyError: _t("Incorrect verification code") });
|
||||
this.setState({ verifyError: _t("settings|general|incorrect_msisdn_verification") });
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -279,17 +279,14 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
|
||||
addVerifySection = (
|
||||
<div>
|
||||
<div>
|
||||
{_t(
|
||||
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.",
|
||||
{ msisdn: msisdn },
|
||||
)}
|
||||
{_t("settings|general|add_msisdn_instructions", { msisdn: msisdn })}
|
||||
<br />
|
||||
{this.state.verifyError}
|
||||
</div>
|
||||
<form onSubmit={this.onContinueClick} autoComplete="off" noValidate={true}>
|
||||
<Field
|
||||
type="text"
|
||||
label={_t("Verification code")}
|
||||
label={_t("settings|general|msisdn_verification_field_label")}
|
||||
autoComplete="off"
|
||||
disabled={this.props.disabled || this.state.continueDisabled}
|
||||
value={this.state.newPhoneNumberCode}
|
||||
@ -329,7 +326,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
|
||||
<div className="mx_PhoneNumbers_input">
|
||||
<Field
|
||||
type="text"
|
||||
label={_t("Phone Number")}
|
||||
label={_t("settings|general|msisdn_label")}
|
||||
autoComplete="tel-national"
|
||||
disabled={this.props.disabled || this.state.verifying}
|
||||
prefixComponent={phoneCountry}
|
||||
|
@ -49,7 +49,7 @@ const DeviceNameEditor: React.FC<Props & { stopEditing: () => void }> = ({ devic
|
||||
await saveDeviceName(deviceName);
|
||||
stopEditing();
|
||||
} catch (error) {
|
||||
setError(_t("Failed to set display name"));
|
||||
setError(_t("settings|sessions|error_set_name"));
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
@ -71,7 +71,7 @@ export const deleteDevicesWithInteractiveAuth = async (
|
||||
},
|
||||
};
|
||||
Modal.createDialog(InteractiveAuthDialog, {
|
||||
title: _t("Authentication"),
|
||||
title: _t("common|authentication"),
|
||||
matrixClient: matrixClient,
|
||||
authData: error.data as IAuthData,
|
||||
onFinished,
|
||||
|
@ -194,8 +194,8 @@ export const useOwnDevices = (): DevicesState => {
|
||||
await matrixClient.setDeviceDetails(deviceId, { display_name: deviceName });
|
||||
await refreshDevices();
|
||||
} catch (error) {
|
||||
logger.error("Error setting session display name", error);
|
||||
throw new Error(_t("Failed to set display name"));
|
||||
logger.error("Error setting device name", error);
|
||||
throw new Error(_t("settings|sessions|error_set_name"));
|
||||
}
|
||||
},
|
||||
[matrixClient, devices, refreshDevices],
|
||||
@ -217,7 +217,7 @@ export const useOwnDevices = (): DevicesState => {
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("Error setting pusher state", error);
|
||||
throw new Error(_t("Failed to set pusher state"));
|
||||
throw new Error(_t("settings|sessions|error_pusher_state"));
|
||||
} finally {
|
||||
await refreshDevices();
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||
this.changeBinding({
|
||||
bind: false,
|
||||
label: "revoke",
|
||||
errorTitle: _t("Unable to revoke sharing for email address"),
|
||||
errorTitle: _t("settings|general|error_revoke_email_discovery"),
|
||||
});
|
||||
};
|
||||
|
||||
@ -126,7 +126,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||
this.changeBinding({
|
||||
bind: true,
|
||||
label: "share",
|
||||
errorTitle: _t("Unable to share email address"),
|
||||
errorTitle: _t("settings|general|error_share_email_discovery"),
|
||||
});
|
||||
};
|
||||
|
||||
@ -152,15 +152,13 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||
|
||||
if (underlyingError instanceof MatrixError && underlyingError.errcode === "M_THREEPID_AUTH_FAILED") {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Your email address hasn't been verified yet"),
|
||||
description: _t(
|
||||
"Click the link in the email you received to verify and then click continue again.",
|
||||
),
|
||||
title: _t("settings|general|email_not_verified"),
|
||||
description: _t("settings|general|email_verification_instructions"),
|
||||
});
|
||||
} else {
|
||||
logger.error("Unable to verify email address: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Unable to verify email address."),
|
||||
title: _t("settings|general|error_email_verification"),
|
||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||
});
|
||||
}
|
||||
@ -178,7 +176,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||
if (verifying) {
|
||||
status = (
|
||||
<span>
|
||||
{_t("Verify the link in your inbox")}
|
||||
{_t("settings|general|discovery_email_verification_instructions")}
|
||||
<AccessibleButton
|
||||
className="mx_GeneralUserSettingsTab_section--discovery_existing_button"
|
||||
kind="primary_sm"
|
||||
@ -242,10 +240,8 @@ export default class EmailAddresses extends React.Component<IProps> {
|
||||
|
||||
return (
|
||||
<SettingsSubsection
|
||||
heading={_t("Email addresses")}
|
||||
description={
|
||||
(!hasEmails && _t("Discovery options will appear once you have added an email above.")) || undefined
|
||||
}
|
||||
heading={_t("settings|general|emails_heading")}
|
||||
description={(!hasEmails && _t("settings|general|discovery_email_empty")) || undefined}
|
||||
stretchContent
|
||||
>
|
||||
{content}
|
||||
|
@ -117,7 +117,7 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||
this.changeBinding({
|
||||
bind: false,
|
||||
label: "revoke",
|
||||
errorTitle: _t("Unable to revoke sharing for phone number"),
|
||||
errorTitle: _t("settings|general|error_revoke_msisdn_discovery"),
|
||||
});
|
||||
};
|
||||
|
||||
@ -127,7 +127,7 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||
this.changeBinding({
|
||||
bind: true,
|
||||
label: "share",
|
||||
errorTitle: _t("Unable to share phone number"),
|
||||
errorTitle: _t("settings|general|error_share_msisdn_discovery"),
|
||||
});
|
||||
};
|
||||
|
||||
@ -163,11 +163,11 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||
this.setState({ continueDisabled: false });
|
||||
if (underlyingError instanceof MatrixError && underlyingError.errcode !== "M_THREEPID_AUTH_FAILED") {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Unable to verify phone number."),
|
||||
title: _t("settings|general|error_msisdn_verification"),
|
||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||
});
|
||||
} else {
|
||||
this.setState({ verifyError: _t("Incorrect verification code") });
|
||||
this.setState({ verifyError: _t("settings|general|incorrect_msisdn_verification") });
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -181,14 +181,14 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||
status = (
|
||||
<span className="mx_GeneralUserSettingsTab_section--discovery_existing_verification">
|
||||
<span>
|
||||
{_t("Please enter verification code sent via text.")}
|
||||
{_t("settings|general|msisdn_verification_instructions")}
|
||||
<br />
|
||||
{this.state.verifyError}
|
||||
</span>
|
||||
<form onSubmit={this.onContinueClick} autoComplete="off" noValidate={true}>
|
||||
<Field
|
||||
type="text"
|
||||
label={_t("Verification code")}
|
||||
label={_t("settings|general|msisdn_verification_field_label")}
|
||||
autoComplete="off"
|
||||
disabled={this.state.continueDisabled}
|
||||
value={this.state.verificationCode}
|
||||
@ -247,13 +247,12 @@ export default class PhoneNumbers extends React.Component<IProps> {
|
||||
});
|
||||
}
|
||||
|
||||
const description =
|
||||
(!content && _t("Discovery options will appear once you have added a phone number above.")) || undefined;
|
||||
const description = (!content && _t("settings|general|discovery_msisdn_empty")) || undefined;
|
||||
|
||||
return (
|
||||
<SettingsSubsection
|
||||
data-testid="mx_DiscoveryPhoneNumbers"
|
||||
heading={_t("Phone numbers")}
|
||||
heading={_t("settings|general|msisdns_heading")}
|
||||
description={description}
|
||||
stretchContent
|
||||
>
|
||||
|
@ -51,7 +51,7 @@ export function NotificationPusherSettings(): JSX.Element {
|
||||
() => ({
|
||||
kind: "email",
|
||||
app_id: "m.email",
|
||||
app_display_name: _t("Email Notifications"),
|
||||
app_display_name: _t("notifications|email_pusher_app_display_name"),
|
||||
lang: navigator.language,
|
||||
data: {
|
||||
brand: SdkConfig.get().brand,
|
||||
@ -91,17 +91,16 @@ export function NotificationPusherSettings(): JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSubsection className="mx_NotificationPusherSettings" heading={_t("Email summary")}>
|
||||
<SettingsSubsection
|
||||
className="mx_NotificationPusherSettings"
|
||||
heading={_t("settings|notifications|email_section")}
|
||||
>
|
||||
<SettingsSubsectionText className="mx_NotificationPusherSettings_description">
|
||||
{_t("Receive an email summary of missed notifications")}
|
||||
{_t("settings|notifications|email_description")}
|
||||
</SettingsSubsectionText>
|
||||
<div className="mx_SettingsSubsection_description mx_NotificationPusherSettings_detail">
|
||||
<SettingsSubsectionText>
|
||||
{_t(
|
||||
"Select which emails you want to send summaries to. Manage your emails in <button>General</button>.",
|
||||
{},
|
||||
{ button: generalTabButton },
|
||||
)}
|
||||
{_t("settings|notifications|email_select", {}, { button: generalTabButton })}
|
||||
</SettingsSubsectionText>
|
||||
</div>
|
||||
<SettingsIndent>
|
||||
@ -118,7 +117,7 @@ export function NotificationPusherSettings(): JSX.Element {
|
||||
</SettingsIndent>
|
||||
</SettingsSubsection>
|
||||
{notificationTargets.length > 0 && (
|
||||
<SettingsSubsection heading={_t("Notification targets")}>
|
||||
<SettingsSubsection heading={_t("settings|notifications|push_targets")}>
|
||||
<ul>
|
||||
{pushers
|
||||
.filter((it) => it.kind !== "email")
|
||||
|
@ -58,21 +58,6 @@ function toDefaultLevels(levels: NotificationSettings["defaultLevels"]): Notific
|
||||
}
|
||||
}
|
||||
|
||||
const NotificationOptions = [
|
||||
{
|
||||
value: NotificationDefaultLevels.AllMessages,
|
||||
label: _t("All messages"),
|
||||
},
|
||||
{
|
||||
value: NotificationDefaultLevels.PeopleMentionsKeywords,
|
||||
label: _t("People, Mentions and Keywords"),
|
||||
},
|
||||
{
|
||||
value: NotificationDefaultLevels.MentionsKeywords,
|
||||
label: _t("Mentions and Keywords only"),
|
||||
},
|
||||
];
|
||||
|
||||
function boldText(text: string): JSX.Element {
|
||||
return <strong>{text}</strong>;
|
||||
}
|
||||
@ -101,6 +86,21 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
const [updatingUnread, setUpdatingUnread] = useState<boolean>(false);
|
||||
const hasUnreadNotifications = useHasUnreadNotifications();
|
||||
|
||||
const NotificationOptions = [
|
||||
{
|
||||
value: NotificationDefaultLevels.AllMessages,
|
||||
label: _t("notifications|all_messages"),
|
||||
},
|
||||
{
|
||||
value: NotificationDefaultLevels.PeopleMentionsKeywords,
|
||||
label: _t("settings|notifications|people_mentions_keywords"),
|
||||
},
|
||||
{
|
||||
value: NotificationDefaultLevels.MentionsKeywords,
|
||||
label: _t("settings|notifications|mentions_keywords_only"),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mx_NotificationSettings2">
|
||||
{hasPendingChanges && model !== null && (
|
||||
@ -110,7 +110,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
onAction={() => reconcile(model!)}
|
||||
>
|
||||
{_t(
|
||||
"<strong>Update:</strong>We’ve simplified Notifications Settings to make options easier to find. Some custom settings you’ve chosen in the past are not shown here, but they’re still active. If you proceed, some of your settings may change. <a>Learn more</a>",
|
||||
"settings|notifications|labs_notice_prompt",
|
||||
{},
|
||||
{
|
||||
strong: boldText,
|
||||
@ -140,7 +140,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
}
|
||||
/>
|
||||
<LabelledToggleSwitch
|
||||
label={_t("Show message preview in desktop notification")}
|
||||
label={_t("settings|notifications|desktop_notification_message_preview")}
|
||||
value={desktopShowBody}
|
||||
onChange={(value) =>
|
||||
SettingsStore.setValue("notificationBodyEnabled", null, SettingLevel.DEVICE, value)
|
||||
@ -155,8 +155,8 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
/>
|
||||
</div>
|
||||
<SettingsSubsection
|
||||
heading={_t("I want to be notified for (Default Setting)")}
|
||||
description={_t("This setting will be applied by default to all your rooms.")}
|
||||
heading={_t("settings|notifications|default_setting_section")}
|
||||
description={_t("settings|notifications|default_setting_description")}
|
||||
>
|
||||
<StyledRadioGroup
|
||||
name="defaultNotificationLevel"
|
||||
@ -182,8 +182,8 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
/>
|
||||
</SettingsSubsection>
|
||||
<SettingsSubsection
|
||||
heading={_t("Play a sound for")}
|
||||
description={_t("Applied by default to all rooms on all devices.")}
|
||||
heading={_t("settings|notifications|play_sound_for_section")}
|
||||
description={_t("settings|notifications|play_sound_for_description")}
|
||||
>
|
||||
<LabelledCheckbox
|
||||
label="People"
|
||||
@ -200,7 +200,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
<LabelledCheckbox
|
||||
label={_t("Mentions and Keywords")}
|
||||
label={_t("settings|notifications|mentions_keywords")}
|
||||
value={settings.sound.mentions !== undefined}
|
||||
disabled={disabled}
|
||||
onChange={(value) => {
|
||||
@ -214,7 +214,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
<LabelledCheckbox
|
||||
label={_t("Audio and Video calls")}
|
||||
label={_t("settings|notifications|voip")}
|
||||
value={settings.sound.calls !== undefined}
|
||||
disabled={disabled}
|
||||
onChange={(value) => {
|
||||
@ -228,9 +228,9 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
</SettingsSubsection>
|
||||
<SettingsSubsection heading={_t("Other things we think you might be interested in:")}>
|
||||
<SettingsSubsection heading={_t("settings|notifications|other_section")}>
|
||||
<LabelledCheckbox
|
||||
label={_t("Invited to a room")}
|
||||
label={_t("settings|notifications|invites")}
|
||||
value={settings.activity.invite}
|
||||
disabled={disabled}
|
||||
onChange={(value) => {
|
||||
@ -244,7 +244,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
<LabelledCheckbox
|
||||
label={_t("New room activity, upgrades and status messages occur")}
|
||||
label={_t("settings|notifications|room_activity")}
|
||||
value={settings.activity.status_event}
|
||||
disabled={disabled}
|
||||
onChange={(value) => {
|
||||
@ -258,7 +258,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
<LabelledCheckbox
|
||||
label={_t("Messages sent by bots")}
|
||||
label={_t("settings|notifications|notices")}
|
||||
value={settings.activity.bot_notices}
|
||||
disabled={disabled}
|
||||
onChange={(value) => {
|
||||
@ -273,9 +273,9 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
/>
|
||||
</SettingsSubsection>
|
||||
<SettingsSubsection
|
||||
heading={_t("Mentions and Keywords")}
|
||||
heading={_t("settings|notifications|mentions_keywords")}
|
||||
description={_t(
|
||||
"Show a badge <badge/> when keywords are used in a room.",
|
||||
"settings|notifications|keywords",
|
||||
{},
|
||||
{
|
||||
badge: <StatelessNotificationBadge symbol="1" count={1} color={NotificationColor.Grey} />,
|
||||
@ -283,7 +283,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
)}
|
||||
>
|
||||
<LabelledCheckbox
|
||||
label={_t("Notify when someone mentions using @room")}
|
||||
label={_t("settings|notifications|notify_at_room")}
|
||||
value={settings.mentions.room}
|
||||
disabled={disabled}
|
||||
onChange={(value) => {
|
||||
@ -297,7 +297,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
<LabelledCheckbox
|
||||
label={_t("Notify when someone mentions using @displayname or %(mxid)s", {
|
||||
label={_t("settings|notifications|notify_mention", {
|
||||
mxid: cli.getUserId()!,
|
||||
})}
|
||||
value={settings.mentions.user}
|
||||
@ -313,8 +313,8 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
}}
|
||||
/>
|
||||
<LabelledCheckbox
|
||||
label={_t("Notify when someone uses a keyword")}
|
||||
byline={_t("Enter keywords here, or use for spelling variations or nicknames")}
|
||||
label={_t("settings|notifications|notify_keyword")}
|
||||
byline={_t("settings|notifications|keywords_prompt")}
|
||||
value={settings.mentions.keywords}
|
||||
disabled={disabled}
|
||||
onChange={(value) => {
|
||||
@ -343,12 +343,12 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
keywords: model!.keywords.filter((it) => it !== keyword),
|
||||
});
|
||||
}}
|
||||
label={_t("Keyword")}
|
||||
placeholder={_t("New keyword")}
|
||||
label={_t("notifications|keyword")}
|
||||
placeholder={_t("notifications|keyword_new")}
|
||||
/>
|
||||
</SettingsSubsection>
|
||||
<NotificationPusherSettings />
|
||||
<SettingsSubsection heading={_t("Quick Actions")}>
|
||||
<SettingsSubsection heading={_t("settings|notifications|quick_actions_section")}>
|
||||
{hasUnreadNotifications && (
|
||||
<AccessibleButton
|
||||
kind="primary_outline"
|
||||
@ -359,7 +359,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
setUpdatingUnread(false);
|
||||
}}
|
||||
>
|
||||
{_t("Mark all messages as read")}
|
||||
{_t("settings|notifications|quick_actions_mark_all_read")}
|
||||
</AccessibleButton>
|
||||
)}
|
||||
<AccessibleButton
|
||||
@ -369,7 +369,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||
reconcile(DefaultNotificationSettings);
|
||||
}}
|
||||
>
|
||||
{_t("Reset to default settings")}
|
||||
{_t("settings|notifications|quick_actions_reset")}
|
||||
</AccessibleButton>
|
||||
</SettingsSubsection>
|
||||
</SettingsSection>
|
||||
|
@ -155,8 +155,14 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
|
||||
|
||||
return (
|
||||
<SettingsTab>
|
||||
<SettingsSection heading={_t("common|Advanced")}>
|
||||
<SettingsSubsection heading={room.isSpaceRoom() ? _t("Space information") : _t("Room information")}>
|
||||
<SettingsSection heading={_t("common|advanced")}>
|
||||
<SettingsSubsection
|
||||
heading={
|
||||
room.isSpaceRoom()
|
||||
? _t("room_settings|advanced|information_section_space")
|
||||
: _t("room_settings|advanced|information_section_room")
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<span>{_t("room_settings|advanced|room_id")}</span>
|
||||
<CopyableText getTextToCopy={() => this.props.room.roomId}>
|
||||
|
@ -63,7 +63,7 @@ export default class BridgeSettingsTab extends React.Component<IProps> {
|
||||
<div>
|
||||
<p>
|
||||
{_t(
|
||||
"This room is bridging messages to the following platforms. <a>Learn more.</a>",
|
||||
"room_settings|bridges|description",
|
||||
{},
|
||||
{
|
||||
// TODO: We don't have this link yet: this will prevent the translators
|
||||
@ -85,7 +85,7 @@ export default class BridgeSettingsTab extends React.Component<IProps> {
|
||||
content = (
|
||||
<p>
|
||||
{_t(
|
||||
"This room isn't bridging messages to any platforms. <a>Learn more.</a>",
|
||||
"room_settings|bridges|empty",
|
||||
{},
|
||||
{
|
||||
// TODO: We don't have this link yet: this will prevent the translators
|
||||
@ -103,7 +103,7 @@ export default class BridgeSettingsTab extends React.Component<IProps> {
|
||||
|
||||
return (
|
||||
<SettingsTab>
|
||||
<SettingsSection heading={_t("Bridges")}>{content}</SettingsSection>
|
||||
<SettingsSection heading={_t("room_settings|bridges|title")}>{content}</SettingsSection>
|
||||
</SettingsTab>
|
||||
);
|
||||
}
|
||||
|
@ -85,11 +85,11 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
|
||||
|
||||
return (
|
||||
<SettingsTab data-testid="General">
|
||||
<SettingsSection heading={_t("General")}>
|
||||
<SettingsSection heading={_t("common|general")}>
|
||||
<RoomProfileSettings roomId={room.roomId} />
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection heading={_t("Room Addresses")}>
|
||||
<SettingsSection heading={_t("room_settings|general|aliases_section")}>
|
||||
<AliasSettings
|
||||
roomId={room.roomId}
|
||||
canSetCanonicalAlias={canSetCanonical}
|
||||
@ -98,7 +98,7 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
|
||||
/>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection heading={_t("Other")}>
|
||||
<SettingsSection heading={_t("room_settings|general|other_section")}>
|
||||
{urlPreviewSettings}
|
||||
{leaveSection}
|
||||
</SettingsSection>
|
||||
|
@ -163,7 +163,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||
currentUploadedFile = (
|
||||
<div>
|
||||
<span>
|
||||
{_t("Uploaded sound")}: <code>{this.state.uploadedFile.name}</code>
|
||||
{_t("room_settings|notifications|uploaded_sound")}: <code>{this.state.uploadedFile.name}</code>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@ -181,10 +181,10 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||
className: "mx_NotificationSettingsTab_defaultEntry",
|
||||
label: (
|
||||
<>
|
||||
{_t("Default")}
|
||||
{_t("notifications|default")}
|
||||
<div className="mx_NotificationSettingsTab_microCopy">
|
||||
{_t(
|
||||
"Get notifications as set up in your <a>settings</a>",
|
||||
"room_settings|notifications|settings_link",
|
||||
{},
|
||||
{
|
||||
a: (sub) => (
|
||||
@ -206,9 +206,9 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||
className: "mx_NotificationSettingsTab_allMessagesEntry",
|
||||
label: (
|
||||
<>
|
||||
{_t("All messages")}
|
||||
{_t("notifications|all_messages")}
|
||||
<div className="mx_NotificationSettingsTab_microCopy">
|
||||
{_t("Get notified for every message")}
|
||||
{_t("notifications|all_messages_description")}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
@ -218,10 +218,10 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||
className: "mx_NotificationSettingsTab_mentionsKeywordsEntry",
|
||||
label: (
|
||||
<>
|
||||
{_t("@mentions & keywords")}
|
||||
{_t("notifications|mentions_and_keywords")}
|
||||
<div className="mx_NotificationSettingsTab_microCopy">
|
||||
{_t(
|
||||
"Get notified only with mentions and keywords as set up in your <a>settings</a>",
|
||||
"notifications|mentions_and_keywords_description",
|
||||
{},
|
||||
{
|
||||
a: (sub) => (
|
||||
@ -245,7 +245,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||
<>
|
||||
{_t("common|off")}
|
||||
<div className="mx_NotificationSettingsTab_microCopy">
|
||||
{_t("You won't get any notifications")}
|
||||
{_t("notifications|mute_description")}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
@ -256,11 +256,12 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SettingsSubsection heading={_t("Sounds")}>
|
||||
<SettingsSubsection heading={_t("room_settings|notifications|sounds_section")}>
|
||||
<div>
|
||||
<div className="mx_SettingsTab_subsectionText">
|
||||
<span>
|
||||
{_t("Notification sound")}: <code>{this.state.currentSound}</code>
|
||||
{_t("room_settings|notifications|notification_sound")}:{" "}
|
||||
<code>{this.state.currentSound}</code>
|
||||
</span>
|
||||
</div>
|
||||
<AccessibleButton
|
||||
@ -273,7 +274,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="mx_Heading_h4">{_t("Set a new custom sound")}</h4>
|
||||
<h4 className="mx_Heading_h4">{_t("room_settings|notifications|custom_sound_prompt")}</h4>
|
||||
<div className="mx_SettingsFlag">
|
||||
<form autoComplete="off" noValidate={true}>
|
||||
<input
|
||||
@ -283,7 +284,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||
onClick={chromeFileInputFix}
|
||||
onChange={this.onSoundUploadChanged}
|
||||
accept="audio/*"
|
||||
aria-label={_t("Upload custom sound")}
|
||||
aria-label={_t("room_settings|notifications|upload_sound_label")}
|
||||
/>
|
||||
</form>
|
||||
|
||||
@ -295,7 +296,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||
onClick={this.triggerUploader}
|
||||
kind="primary"
|
||||
>
|
||||
{_t("Browse")}
|
||||
{_t("room_settings|notifications|browse_button")}
|
||||
</AccessibleButton>
|
||||
|
||||
<AccessibleButton
|
||||
|
@ -52,7 +52,7 @@ const SeeMoreOrLess: VFC<{ roomMember: RoomMember }> = ({ roomMember }) => {
|
||||
</p>
|
||||
{shouldTruncate && (
|
||||
<AccessibleButton kind="link" onClick={() => setSeeMore(!seeMore)}>
|
||||
{seeMore ? _t("See less") : _t("See more")}
|
||||
{seeMore ? _t("room_settings|people|see_less") : _t("room_settings|people|see_more")}
|
||||
</AccessibleButton>
|
||||
)}
|
||||
</>
|
||||
@ -151,7 +151,7 @@ export const PeopleRoomSettingsTab: VFC<{ room: Room }> = ({ room }) => {
|
||||
return (
|
||||
<SettingsTab>
|
||||
<SettingsSection heading={_t("common|people")}>
|
||||
<SettingsFieldset legend={_t("Asking to join")}>
|
||||
<SettingsFieldset legend={_t("room_settings|people|knock_section")}>
|
||||
{knockMembers.length ? (
|
||||
knockMembers.map((knockMember) => (
|
||||
<Knock
|
||||
@ -164,7 +164,7 @@ export const PeopleRoomSettingsTab: VFC<{ room: Room }> = ({ room }) => {
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<p className="mx_PeopleRoomSettingsTab_paragraph">{_t("No requests")}</p>
|
||||
<p className="mx_PeopleRoomSettingsTab_paragraph">{_t("room_settings|people|knock_empty")}</p>
|
||||
)}
|
||||
</SettingsFieldset>
|
||||
</SettingsSection>
|
||||
|
@ -95,7 +95,7 @@ export class BannedUser extends React.Component<IBannedUserProps> {
|
||||
logger.error("Failed to unban: " + err);
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("common|error"),
|
||||
description: _t("Failed to unban"),
|
||||
description: _t("room_settings|permissions|error_unbanning"),
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -119,9 +119,11 @@ export class BannedUser extends React.Component<IBannedUserProps> {
|
||||
return (
|
||||
<li>
|
||||
{unbanButton}
|
||||
<span title={_t("Banned by %(displayName)s", { displayName: this.props.by })}>
|
||||
<span title={_t("room_settings|permissions|banned_by", { displayName: this.props.by })}>
|
||||
<strong>{this.props.member.name}</strong> {userId}
|
||||
{this.props.reason ? " " + _t("Reason") + ": " + this.props.reason : ""}
|
||||
{this.props.reason
|
||||
? " " + _t("room_settings|permissions|ban_reason") + ": " + this.props.reason
|
||||
: ""}
|
||||
</span>
|
||||
</li>
|
||||
);
|
||||
@ -205,10 +207,8 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||
logger.error(e);
|
||||
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Error changing power level requirement"),
|
||||
description: _t(
|
||||
"An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.",
|
||||
),
|
||||
title: _t("room_settings|permissions|error_changing_pl_reqs_title"),
|
||||
description: _t("room_settings|permissions|error_changing_pl_reqs_description"),
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -230,10 +230,8 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||
logger.error(e);
|
||||
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Error changing power level"),
|
||||
description: _t(
|
||||
"An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.",
|
||||
),
|
||||
title: _t("room_settings|permissions|error_changing_pl_title"),
|
||||
description: _t("room_settings|permissions|error_changing_pl_description"),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -277,7 +277,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
className="mx_SettingsTab_showAdvanced"
|
||||
aria-expanded={this.state.showAdvancedSection}
|
||||
>
|
||||
{this.state.showAdvancedSection ? _t("Hide advanced") : _t("Show advanced")}
|
||||
{this.state.showAdvancedSection ? _t("action|hide_advanced") : _t("action|show_advanced")}
|
||||
</AccessibleButton>
|
||||
{this.state.showAdvancedSection && this.renderAdvanced()}
|
||||
</div>
|
||||
@ -285,7 +285,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsFieldset legend={_t("Access")} description={description}>
|
||||
<SettingsFieldset legend={_t("room_settings|access|title")} description={description}>
|
||||
<JoinRuleSettings
|
||||
room={room}
|
||||
beforeChange={this.onBeforeJoinRuleChange}
|
||||
@ -301,8 +301,8 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
|
||||
private onJoinRuleChangeError = (error: Error): void => {
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Failed to update the join rules"),
|
||||
description: error.message ?? _t("Unknown failure"),
|
||||
title: _t("room_settings|security|error_join_rule_change_title"),
|
||||
description: error.message ?? _t("room_settings|security|error_join_rule_change_unknown"),
|
||||
});
|
||||
};
|
||||
|
||||
@ -411,7 +411,7 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||
value={guestAccess === GuestAccess.CanJoin}
|
||||
onChange={this.onGuestAccessChange}
|
||||
disabled={!canSetGuestAccess}
|
||||
label={_t("Enable guest access")}
|
||||
label={_t("room_settings|visibility|guest_access_label")}
|
||||
/>
|
||||
<p>{_t("room_settings|security|guest_access_warning")}</p>
|
||||
</div>
|
||||
|
@ -81,14 +81,14 @@ const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ room }) => {
|
||||
return (
|
||||
<LabelledToggleSwitch
|
||||
data-testid="element-call-switch"
|
||||
label={_t("Enable %(brand)s as an additional calling option in this room", { brand })}
|
||||
caption={_t("%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.", {
|
||||
label={_t("room_settings|voip|enable_element_call_label", { brand })}
|
||||
caption={_t("room_settings|voip|enable_element_call_caption", {
|
||||
brand,
|
||||
})}
|
||||
value={elementCallEnabled}
|
||||
onChange={onChange}
|
||||
disabled={!maySend}
|
||||
tooltip={_t("You do not have sufficient permissions to change this.")}
|
||||
tooltip={_t("room_settings|voip|enable_element_call_no_permissions_tooltip")}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -100,8 +100,8 @@ interface Props {
|
||||
export const VoipRoomSettingsTab: React.FC<Props> = ({ room }) => {
|
||||
return (
|
||||
<SettingsTab>
|
||||
<SettingsSection heading={_t("Voice & Video")}>
|
||||
<SettingsSubsection heading={_t("Call type")}>
|
||||
<SettingsSection heading={_t("settings|voip|title")}>
|
||||
<SettingsSubsection heading={_t("room_settings|voip|call_type_section")}>
|
||||
<ElementCallSwitch room={room} />
|
||||
</SettingsSubsection>
|
||||
</SettingsSection>
|
||||
|
@ -99,7 +99,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
|
||||
onClick={() => this.setState({ showAdvanced: !this.state.showAdvanced })}
|
||||
aria-expanded={this.state.showAdvanced}
|
||||
>
|
||||
{this.state.showAdvanced ? _t("Hide advanced") : _t("Show advanced")}
|
||||
{this.state.showAdvanced ? _t("action|hide_advanced") : _t("action|show_advanced")}
|
||||
</AccessibleButton>
|
||||
);
|
||||
|
||||
|
@ -282,16 +282,16 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||
|
||||
const errorMessage = extractErrorMessageFromError(
|
||||
err,
|
||||
_t("Unknown password change error (%(stringifiedError)s)", {
|
||||
_t("settings|general|error_password_change_unknown", {
|
||||
stringifiedError: String(err),
|
||||
}),
|
||||
);
|
||||
|
||||
let errorMessageToDisplay = errorMessage;
|
||||
if (underlyingError instanceof HTTPError && underlyingError.httpStatus === 403) {
|
||||
errorMessageToDisplay = _t("Failed to change password. Is your password correct?");
|
||||
errorMessageToDisplay = _t("settings|general|error_password_change_403");
|
||||
} else if (underlyingError instanceof HTTPError) {
|
||||
errorMessageToDisplay = _t("%(errorMessage)s (HTTP status %(httpStatus)s)", {
|
||||
errorMessageToDisplay = _t("settings|general|error_password_change_http", {
|
||||
errorMessage,
|
||||
httpStatus: underlyingError.httpStatus,
|
||||
});
|
||||
@ -299,13 +299,13 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||
|
||||
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("Error changing password"),
|
||||
title: _t("settings|general|error_password_change_title"),
|
||||
description: errorMessageToDisplay,
|
||||
});
|
||||
};
|
||||
|
||||
private onPasswordChanged = (): void => {
|
||||
const description = _t("Your password was successfully changed.");
|
||||
const description = _t("settings|general|password_change_success");
|
||||
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
||||
Modal.createDialog(ErrorDialog, {
|
||||
title: _t("common|success"),
|
||||
@ -346,7 +346,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||
threepidSection = (
|
||||
<>
|
||||
<SettingsSubsection
|
||||
heading={_t("Email addresses")}
|
||||
heading={_t("settings|general|emails_heading")}
|
||||
stretchContent
|
||||
data-testid="mx_AccountEmailAddresses"
|
||||
>
|
||||
@ -354,7 +354,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||
</SettingsSubsection>
|
||||
|
||||
<SettingsSubsection
|
||||
heading={_t("Phone numbers")}
|
||||
heading={_t("settings|general|msisdns_heading")}
|
||||
stretchContent
|
||||
data-testid="mx_AccountPhoneNumbers"
|
||||
>
|
||||
@ -368,7 +368,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||
if (this.state.canChangePassword) {
|
||||
passwordChangeSection = (
|
||||
<>
|
||||
<SettingsSubsectionText>{_t("Set a new account password…")}</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>{_t("settings|general|password_change_section")}</SettingsSubsectionText>
|
||||
<ChangePassword
|
||||
className="mx_GeneralUserSettingsTab_section--account_changePassword"
|
||||
rowClassName=""
|
||||
@ -388,7 +388,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||
<>
|
||||
<SettingsSubsectionText data-testid="external-account-management-outer">
|
||||
{_t(
|
||||
"Your account details are managed separately at <code>%(hostname)s</code>.",
|
||||
"settings|general|external_account_management",
|
||||
{ hostname },
|
||||
{ code: (sub) => <code>{sub}</code> },
|
||||
)}
|
||||
@ -457,10 +457,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||
if (this.state.requiredPolicyInfo.hasTerms) {
|
||||
const intro = (
|
||||
<SettingsSubsectionText>
|
||||
{_t(
|
||||
"Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.",
|
||||
{ serverName: this.state.idServerName },
|
||||
)}
|
||||
{_t("settings|general|discovery_needs_terms", { serverName: this.state.idServerName })}
|
||||
</SettingsSubsectionText>
|
||||
);
|
||||
return (
|
||||
@ -504,14 +501,14 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||
private renderManagementSection(): JSX.Element {
|
||||
// TODO: Improve warning text for account deactivation
|
||||
return (
|
||||
<SettingsSection heading={_t("Deactivate account")}>
|
||||
<SettingsSection heading={_t("settings|general|deactivate_section")}>
|
||||
<SettingsSubsection
|
||||
heading={_t("Account management")}
|
||||
heading={_t("settings|general|account_management_section")}
|
||||
data-testid="account-management-section"
|
||||
description={_t("Deactivating your account is a permanent action — be careful!")}
|
||||
description={_t("settings|general|deactivate_warning")}
|
||||
>
|
||||
<AccessibleButton onClick={this.onDeactivateClicked} kind="danger">
|
||||
{_t("Deactivate Account")}
|
||||
{_t("settings|general|deactivate_section")}
|
||||
</AccessibleButton>
|
||||
</SettingsSubsection>
|
||||
</SettingsSection>
|
||||
@ -549,7 +546,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||
const heading = (
|
||||
<Heading size="2">
|
||||
{discoWarning}
|
||||
{_t("Discovery")}
|
||||
{_t("settings|general|discovery_section")}
|
||||
</Heading>
|
||||
);
|
||||
discoverySection = (
|
||||
@ -561,7 +558,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||
|
||||
return (
|
||||
<SettingsTab data-testid="mx_GeneralUserSettingsTab">
|
||||
<SettingsSection heading={_t("General")}>
|
||||
<SettingsSection heading={_t("common|general")}>
|
||||
<ProfileSettings />
|
||||
{this.renderAccountSection()}
|
||||
{this.renderLanguageSection()}
|
||||
|
@ -322,7 +322,7 @@ export default class HelpUserSettingsTab extends React.Component<IProps, IState>
|
||||
</SettingsSubsection>
|
||||
{this.renderLegal()}
|
||||
{this.renderCredits()}
|
||||
<SettingsSubsection heading={_t("common|Advanced")}>
|
||||
<SettingsSubsection heading={_t("common|advanced")}>
|
||||
<SettingsSubsectionText>
|
||||
{_t(
|
||||
"setting|help_about|homeserver",
|
||||
|
@ -143,7 +143,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
||||
const name = room ? room.name : list.roomId;
|
||||
|
||||
const renderRules = (rules: ListRule[]): JSX.Element => {
|
||||
if (rules.length === 0) return <i>{_t("None")}</i>;
|
||||
if (rules.length === 0) return <i>{_t("labs_mjolnir|rules_empty")}</i>;
|
||||
|
||||
const tiles: JSX.Element[] = [];
|
||||
for (const rule of rules) {
|
||||
|
@ -152,7 +152,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
||||
</SettingsSubsection>
|
||||
)}
|
||||
|
||||
<SettingsSubsection heading={_t("Spaces")}>
|
||||
<SettingsSubsection heading={_t("common|spaces")}>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.SPACES_SETTINGS, SettingLevel.ACCOUNT)}
|
||||
</SettingsSubsection>
|
||||
|
||||
@ -204,7 +204,7 @@ export default class PreferencesUserSettingsTab extends React.Component<IProps,
|
||||
{this.renderGroup(PreferencesUserSettingsTab.ROOM_DIRECTORY_SETTINGS)}
|
||||
</SettingsSubsection>
|
||||
|
||||
<SettingsSubsection heading={_t("General")} stretchContent>
|
||||
<SettingsSubsection heading={_t("common|general")} stretchContent>
|
||||
{this.renderGroup(PreferencesUserSettingsTab.GENERAL_SETTINGS)}
|
||||
|
||||
<SettingsFlag name="Electron.showTrayIcon" level={SettingLevel.PLATFORM} hideIfCannotSet />
|
||||
|
@ -63,7 +63,7 @@ export class IgnoredUser extends React.Component<IIgnoredUserProps> {
|
||||
aria-describedby={id}
|
||||
disabled={this.props.inProgress}
|
||||
>
|
||||
{_t("Unignore")}
|
||||
{_t("action|unignore")}
|
||||
</AccessibleButton>
|
||||
<span id={id}>{this.props.userId}</span>
|
||||
</div>
|
||||
@ -225,7 +225,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||
const { waitingUnignored, ignoredUserIds } = this.state;
|
||||
|
||||
const userIds = !ignoredUserIds?.length
|
||||
? _t("You have no ignored users.")
|
||||
? _t("settings|security|ignore_users_empty")
|
||||
: ignoredUserIds.map((u) => {
|
||||
return (
|
||||
<IgnoredUser
|
||||
@ -238,7 +238,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||
});
|
||||
|
||||
return (
|
||||
<SettingsSubsection heading={_t("Ignored users")}>
|
||||
<SettingsSubsection heading={_t("settings|security|ignore_users_section")}>
|
||||
<SettingsSubsectionText>{userIds}</SettingsSubsectionText>
|
||||
</SettingsSubsection>
|
||||
);
|
||||
@ -301,9 +301,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||
if (!privateShouldBeEncrypted(MatrixClientPeg.safeGet())) {
|
||||
warning = (
|
||||
<div className="mx_SecurityUserSettingsTab_warning">
|
||||
{_t(
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
|
||||
)}
|
||||
{_t("settings|security|e2ee_default_disabled_warning")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -320,9 +318,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||
<SettingsSection heading={_t("common|privacy")}>
|
||||
<SettingsSubsection
|
||||
heading={_t("common|analytics")}
|
||||
description={_t(
|
||||
"Share anonymous data to help us identify issues. Nothing personal. No third parties.",
|
||||
)}
|
||||
description={_t("settings|security|analytics_description")}
|
||||
>
|
||||
<AccessibleButton kind="link" onClick={onClickAnalyticsLearnMore}>
|
||||
{_t("action|learn_more")}
|
||||
@ -346,7 +342,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||
// only show the section if there's something to show
|
||||
if (ignoreUsersPanel || invitesPanel || e2ePanel) {
|
||||
advancedSection = (
|
||||
<SettingsSection heading={_t("common|Advanced")}>
|
||||
<SettingsSection heading={_t("common|advanced")}>
|
||||
{ignoreUsersPanel}
|
||||
{invitesPanel}
|
||||
{e2ePanel}
|
||||
|
@ -302,9 +302,7 @@ const SessionManagerTab: React.FC = () => {
|
||||
disabled={!!signingOutDeviceIds.length}
|
||||
/>
|
||||
}
|
||||
description={_t(
|
||||
"For best security, verify your sessions and sign out from any session that you don't recognize or use anymore.",
|
||||
)}
|
||||
description={_t("settings|sessions|best_security_note")}
|
||||
data-testid="other-sessions-section"
|
||||
stretchContent
|
||||
>
|
||||
|
@ -67,9 +67,7 @@ const SidebarUserSettingsTab: React.FC = () => {
|
||||
<SettingsSection heading={_t("settings|sidebar|title")}>
|
||||
<SettingsSubsection
|
||||
heading={_t("settings|sidebar|metaspaces_subsection")}
|
||||
description={_t(
|
||||
"Spaces are ways to group rooms and people. Alongside the spaces you're in, you can use some pre-built ones too.",
|
||||
)}
|
||||
description={_t("settings|sidebar|spaces_explainer")}
|
||||
>
|
||||
<StyledCheckbox
|
||||
checked={!!homeEnabled}
|
||||
@ -93,10 +91,12 @@ const SidebarUserSettingsTab: React.FC = () => {
|
||||
className="mx_SidebarUserSettingsTab_checkbox mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
|
||||
data-testid="mx_SidebarUserSettingsTab_homeAllRoomsCheckbox"
|
||||
>
|
||||
<SettingsSubsectionText>{_t("Show all rooms")}</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>
|
||||
{_t("settings|sidebar|metaspaces_home_all_rooms")}
|
||||
</SettingsSubsectionText>
|
||||
<SettingsSubsectionText>
|
||||
{_t("settings|sidebar|metaspaces_home_all_rooms_description")}
|
||||
</SettingsSubsectionText>
|
||||
</StyledCheckbox>
|
||||
|
||||
<StyledCheckbox
|
||||
|
@ -159,29 +159,30 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
|
||||
if (!this.state.mediaDevices) {
|
||||
requestButton = (
|
||||
<div>
|
||||
<p>{_t("Missing media permissions, click the button below to request.")}</p>
|
||||
<p>{_t("settings|voip|missing_permissions_prompt")}</p>
|
||||
<AccessibleButton onClick={this.requestMediaPermissions} kind="primary">
|
||||
{_t("Request media permissions")}
|
||||
{_t("settings|voip|request_permissions")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
);
|
||||
} else if (this.state.mediaDevices) {
|
||||
speakerDropdown = this.renderDropdown(MediaDeviceKindEnum.AudioOutput, _t("Audio Output")) || (
|
||||
<p>{_t("No Audio Outputs detected")}</p>
|
||||
);
|
||||
speakerDropdown = this.renderDropdown(
|
||||
MediaDeviceKindEnum.AudioOutput,
|
||||
_t("settings|voip|audio_output"),
|
||||
) || <p>{_t("settings|voip|audio_output_empty")}</p>;
|
||||
microphoneDropdown = this.renderDropdown(MediaDeviceKindEnum.AudioInput, _t("common|microphone")) || (
|
||||
<p>{_t("No Microphones detected")}</p>
|
||||
<p>{_t("settings|voip|audio_input_empty")}</p>
|
||||
);
|
||||
webcamDropdown = this.renderDropdown(MediaDeviceKindEnum.VideoInput, _t("common|camera")) || (
|
||||
<p>{_t("No Webcams detected")}</p>
|
||||
<p>{_t("settings|voip|video_input_empty")}</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsTab>
|
||||
<SettingsSection heading={_t("Voice & Video")}>
|
||||
<SettingsSection heading={_t("settings|voip|title")}>
|
||||
{requestButton}
|
||||
<SettingsSubsection heading={_t("Voice settings")} stretchContent>
|
||||
<SettingsSubsection heading={_t("settings|voip|voice_section")} stretchContent>
|
||||
{speakerDropdown}
|
||||
{microphoneDropdown}
|
||||
<LabelledToggleSwitch
|
||||
@ -190,18 +191,18 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
|
||||
await MediaDeviceHandler.setAudioAutoGainControl(v);
|
||||
this.setState({ audioAutoGainControl: MediaDeviceHandler.getAudioAutoGainControl() });
|
||||
}}
|
||||
label={_t("Automatically adjust the microphone volume")}
|
||||
label={_t("settings|voip|voice_agc")}
|
||||
data-testid="voice-auto-gain"
|
||||
/>
|
||||
</SettingsSubsection>
|
||||
<SettingsSubsection heading={_t("Video settings")} stretchContent>
|
||||
<SettingsSubsection heading={_t("settings|voip|video_section")} stretchContent>
|
||||
{webcamDropdown}
|
||||
<SettingsFlag name="VideoView.flipVideoHorizontally" level={SettingLevel.ACCOUNT} />
|
||||
</SettingsSubsection>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection heading={_t("common|Advanced")}>
|
||||
<SettingsSubsection heading={_t("Voice processing")}>
|
||||
<SettingsSection heading={_t("common|advanced")}>
|
||||
<SettingsSubsection heading={_t("settings|voip|voice_processing")}>
|
||||
<LabelledToggleSwitch
|
||||
value={this.state.audioNoiseSuppression}
|
||||
onChange={async (v): Promise<void> => {
|
||||
@ -221,7 +222,7 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
|
||||
data-testid="voice-echo-cancellation"
|
||||
/>
|
||||
</SettingsSubsection>
|
||||
<SettingsSubsection heading={_t("Connection")}>
|
||||
<SettingsSubsection heading={_t("settings|voip|connection_section")}>
|
||||
<SettingsFlag
|
||||
name="webRtcAllowPeerToPeer"
|
||||
level={SettingLevel.DEVICE}
|
||||
|
@ -145,7 +145,7 @@ const SpaceChildrenPicker: React.FC<IProps> = ({
|
||||
|
||||
{state === Target.Specific && (
|
||||
<SpecificChildrenPicker
|
||||
filterPlaceholder={_t("Search %(spaceName)s", { spaceName: space.name })}
|
||||
filterPlaceholder={_t("space|search_children", { spaceName: space.name })}
|
||||
rooms={spaceChildren}
|
||||
selected={selected}
|
||||
onChange={(isSelected: boolean, room: Room) => {
|
||||
|
@ -193,8 +193,8 @@ export const SpaceCreateForm: React.FC<ISpaceCreateFormProps> = ({
|
||||
onChange={setAlias}
|
||||
domain={domain}
|
||||
value={alias}
|
||||
placeholder={name ? nameToLocalpart(name) : _t("create_space|name_placeholder")}
|
||||
label={_t("Address")}
|
||||
placeholder={name ? nameToLocalpart(name) : _t("create_space|address_placeholder")}
|
||||
label={_t("create_space|address_label")}
|
||||
disabled={busy}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
@ -284,7 +284,7 @@ const SpaceCreateMenu: React.FC<{
|
||||
if (visibility === null) {
|
||||
body = (
|
||||
<React.Fragment>
|
||||
<h2>{_t("Create a space")}</h2>
|
||||
<h2>{_t("create_space|label")}</h2>
|
||||
<p>{_t("create_space|explainer")}</p>
|
||||
|
||||
<SpaceCreateMenuType
|
||||
@ -322,7 +322,7 @@ const SpaceCreateMenu: React.FC<{
|
||||
: _t("create_space|private_heading")}
|
||||
</h2>
|
||||
<p>
|
||||
{_t("create_space|add_details_prompt")} {_t("You can change these anytime.")}
|
||||
{_t("create_space|add_details_prompt")} {_t("create_space|add_details_prompt_2")}
|
||||
</p>
|
||||
|
||||
<SpaceCreateForm
|
||||
@ -341,7 +341,7 @@ const SpaceCreateMenu: React.FC<{
|
||||
/>
|
||||
|
||||
<AccessibleButton kind="primary" onClick={onSpaceCreateClick} disabled={busy}>
|
||||
{busy ? _t("Creating…") : _t("action|create")}
|
||||
{busy ? _t("create_space|creating") : _t("action|create")}
|
||||
</AccessibleButton>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
@ -101,7 +101,7 @@ export const HomeButtonContextMenu: React.FC<ComponentProps<typeof SpaceContextM
|
||||
<IconizedContextMenuOptionList first>
|
||||
<IconizedContextMenuCheckbox
|
||||
iconClassName="mx_SpacePanel_noIcon"
|
||||
label={_t("Show all rooms")}
|
||||
label={_t("settings|sidebar|metaspaces_home_all_rooms")}
|
||||
active={allRoomsInHome}
|
||||
onClick={() => {
|
||||
onFinished();
|
||||
@ -245,7 +245,7 @@ const CreateSpaceButton: React.FC<Pick<IInnerSpacePanelProps, "isPanelCollapsed"
|
||||
className={classNames("mx_SpaceButton_new", {
|
||||
mx_SpaceButton_newCancel: menuDisplayed,
|
||||
})}
|
||||
label={menuDisplayed ? _t("action|cancel") : _t("Create a space")}
|
||||
label={menuDisplayed ? _t("action|cancel") : _t("create_space|label")}
|
||||
onClick={onNewClick}
|
||||
isNarrow={isPanelCollapsed}
|
||||
innerRef={handle}
|
||||
@ -297,7 +297,7 @@ const InnerSpacePanel = React.memo<IInnerSpacePanelProps>(
|
||||
}
|
||||
element="ul"
|
||||
role="tree"
|
||||
aria-label={_t("Spaces")}
|
||||
aria-label={_t("common|spaces")}
|
||||
>
|
||||
{metaSpacesSection}
|
||||
{invites.map((s) => (
|
||||
|
@ -52,7 +52,7 @@ const SpacePublicShare: React.FC<IProps> = ({ space, onFinished }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{_t("Share invite link")}
|
||||
{_t("space|invite_link")}
|
||||
<div>{copiedText}</div>
|
||||
</AccessibleButton>
|
||||
{space.canInvite(MatrixClientPeg.safeGet().getSafeUserId()) &&
|
||||
@ -64,8 +64,8 @@ const SpacePublicShare: React.FC<IProps> = ({ space, onFinished }) => {
|
||||
showRoomInviteDialog(space.roomId);
|
||||
}}
|
||||
>
|
||||
{_t("Invite people")}
|
||||
<div>{_t("Invite with email or username")}</div>
|
||||
{_t("space|invite")}
|
||||
<div>{_t("space|invite_description")}</div>
|
||||
</AccessibleButton>
|
||||
) : null}
|
||||
</div>
|
||||
|
@ -90,15 +90,15 @@ const SpaceSettingsGeneralTab: React.FC<IProps> = ({ matrixClient: cli, space })
|
||||
const failures = results.filter((r) => r.status === "rejected");
|
||||
if (failures.length > 0) {
|
||||
logger.error("Failed to save space settings: ", failures);
|
||||
setError(_t("Failed to save space settings."));
|
||||
setError(_t("room_settings|general|error_save_space_settings"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsTab>
|
||||
<SettingsSection heading={_t("General")}>
|
||||
<SettingsSection heading={_t("common|general")}>
|
||||
<div>
|
||||
<div>{_t("Edit settings relating to your space.")}</div>
|
||||
<div>{_t("room_settings|general|description_space")}</div>
|
||||
|
||||
{error && <div className="mx_SpaceRoomView_errorText">{error}</div>}
|
||||
|
||||
@ -122,18 +122,18 @@ const SpaceSettingsGeneralTab: React.FC<IProps> = ({ matrixClient: cli, space })
|
||||
{_t("action|cancel")}
|
||||
</AccessibleButton>
|
||||
<AccessibleButton onClick={onSave} disabled={busy} kind="primary">
|
||||
{busy ? _t("Saving…") : _t("Save Changes")}
|
||||
{busy ? _t("common|saving") : _t("room_settings|general|save")}
|
||||
</AccessibleButton>
|
||||
</div>
|
||||
|
||||
<SettingsSubsection heading={_t("Leave Space")}>
|
||||
<SettingsSubsection heading={_t("room_settings|general|leave_space")}>
|
||||
<AccessibleButton
|
||||
kind="danger"
|
||||
onClick={() => {
|
||||
leaveSpace(space);
|
||||
}}
|
||||
>
|
||||
{_t("Leave Space")}
|
||||
{_t("room_settings|general|leave_space")}
|
||||
</AccessibleButton>
|
||||
</SettingsSubsection>
|
||||
</SettingsSection>
|
||||
|
@ -64,7 +64,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
|
||||
},
|
||||
"",
|
||||
),
|
||||
() => setError(_t("Failed to update the guest access of this space")),
|
||||
() => setError(_t("room_settings|visibility|error_update_guest_access")),
|
||||
);
|
||||
const [historyVisibility, setHistoryVisibility] = useLocalEcho<HistoryVisibility>(
|
||||
() =>
|
||||
@ -79,7 +79,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
|
||||
},
|
||||
"",
|
||||
),
|
||||
() => setError(_t("Failed to update the history visibility of this space")),
|
||||
() => setError(_t("room_settings|visibility|error_update_history_visibility")),
|
||||
);
|
||||
|
||||
const [showAdvancedSection, toggleAdvancedSection] = useStateToggle();
|
||||
@ -100,7 +100,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
|
||||
className="mx_SettingsTab_showAdvanced"
|
||||
aria-expanded={showAdvancedSection}
|
||||
>
|
||||
{showAdvancedSection ? _t("Hide advanced") : _t("Show advanced")}
|
||||
{showAdvancedSection ? _t("action|hide_advanced") : _t("action|show_advanced")}
|
||||
</AccessibleButton>
|
||||
|
||||
{showAdvancedSection && (
|
||||
@ -109,12 +109,12 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
|
||||
value={guestAccessEnabled}
|
||||
onChange={setGuestAccessEnabled}
|
||||
disabled={!canSetGuestAccess}
|
||||
label={_t("Enable guest access")}
|
||||
label={_t("room_settings|visibility|guest_access_label")}
|
||||
/>
|
||||
<p>
|
||||
{_t("Guests can join a space without having an account.")}
|
||||
{_t("room_settings|visibility|guest_access_explainer")}
|
||||
<br />
|
||||
{_t("This may be useful for public spaces.")}
|
||||
{_t("room_settings|visibility|guest_access_explainer_public_space")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@ -125,7 +125,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
|
||||
let addressesSection: JSX.Element | undefined;
|
||||
if (space.getJoinRule() === JoinRule.Public) {
|
||||
addressesSection = (
|
||||
<SettingsSection heading={_t("Address")}>
|
||||
<SettingsSection heading={_t("room_settings|visibility|alias_section")}>
|
||||
<AliasSettings
|
||||
roomId={space.roomId}
|
||||
canSetCanonicalAlias={canSetCanonical}
|
||||
@ -139,7 +139,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
|
||||
|
||||
return (
|
||||
<SettingsTab>
|
||||
<SettingsSection heading={_t("Visibility")}>
|
||||
<SettingsSection heading={_t("room_settings|visibility|title")}>
|
||||
{error && (
|
||||
<div data-testid="space-settings-error" className="mx_SpaceRoomView_errorText">
|
||||
{error}
|
||||
@ -148,12 +148,12 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
|
||||
|
||||
<SettingsFieldset
|
||||
data-testid="access-fieldset"
|
||||
legend={_t("Access")}
|
||||
description={_t("Decide who can view and join %(spaceName)s.", { spaceName: space.name })}
|
||||
legend={_t("room_settings|access|title")}
|
||||
description={_t("room_settings|access|description_space", { spaceName: space.name })}
|
||||
>
|
||||
<JoinRuleSettings
|
||||
room={space}
|
||||
onError={(): void => setError(_t("Failed to update the visibility of this space"))}
|
||||
onError={(): void => setError(_t("room_settings|visibility|error_failed_save"))}
|
||||
closeSettingsFn={closeSettingsFn}
|
||||
/>
|
||||
{advancedSection}
|
||||
@ -166,12 +166,12 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
|
||||
);
|
||||
}}
|
||||
disabled={!canSetHistoryVisibility}
|
||||
label={_t("Preview Space")}
|
||||
label={_t("room_settings|visibility|history_visibility_anyone_space")}
|
||||
/>
|
||||
<p>
|
||||
{_t("Allow people to preview your space before they join.")}
|
||||
{_t("room_settings|visibility|history_visibility_anyone_space_description")}
|
||||
<br />
|
||||
<b>{_t("Recommended for public spaces.")}</b>
|
||||
<b>{_t("room_settings|visibility|history_visibility_anyone_space_recommendation")}</b>
|
||||
</p>
|
||||
</div>
|
||||
</SettingsFieldset>
|
||||
|
@ -95,9 +95,9 @@ export const SpaceButton: React.FC<IButtonProps> = ({
|
||||
|
||||
let notifBadge;
|
||||
if (spaceKey && notificationState) {
|
||||
let ariaLabel = _t("Jump to first unread room.");
|
||||
let ariaLabel = _t("a11y_jump_first_unread_room");
|
||||
if (space?.getMyMembership() === "invite") {
|
||||
ariaLabel = _t("Jump to first invite.");
|
||||
ariaLabel = _t("a11y|jump_first_invite");
|
||||
}
|
||||
|
||||
const jumpToNotification = (ev: MouseEvent): void => {
|
||||
@ -371,7 +371,7 @@ export class SpaceItem extends React.PureComponent<IItemProps, IItemState> {
|
||||
className={isInvite ? "mx_SpaceButton_invite" : undefined}
|
||||
selected={selected}
|
||||
label={this.state.name}
|
||||
contextMenuTooltip={_t("Space options")}
|
||||
contextMenuTooltip={_t("space|context_menu|options")}
|
||||
notificationState={notificationState}
|
||||
isNarrow={isPanelCollapsed}
|
||||
size={isNested ? "24px" : "32px"}
|
||||
|
@ -285,7 +285,7 @@ export const Lobby: FC<LobbyProps> = ({ room, joinCallButtonDisabledTooltip, con
|
||||
disabled={connecting || joinCallButtonDisabledTooltip !== undefined}
|
||||
onClick={onConnectClick}
|
||||
label={_t("action|join")}
|
||||
tooltip={connecting ? _t("Connecting") : joinCallButtonDisabledTooltip}
|
||||
tooltip={connecting ? _t("voip|connecting") : joinCallButtonDisabledTooltip}
|
||||
alignment={Alignment.Bottom}
|
||||
/>
|
||||
</div>
|
||||
@ -397,7 +397,7 @@ const JoinCallView: FC<JoinCallViewProps> = ({ room, resizing, call }) => {
|
||||
|
||||
facePile = (
|
||||
<div className="mx_CallView_participants">
|
||||
{_t("%(count)s people joined", { count: members.length })}
|
||||
{_t("voip|n_people_joined", { count: members.length })}
|
||||
<FacePile members={shownMembers} size="24px" overflow={overflow} />
|
||||
</div>
|
||||
);
|
||||
|
@ -440,10 +440,10 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||
const cli = MatrixClientPeg.safeGet();
|
||||
const callRoomId = LegacyCallHandler.instance.roomIdForCall(call);
|
||||
const transferTargetRoom = callRoomId ? cli.getRoom(callRoomId) : null;
|
||||
const transferTargetName = transferTargetRoom ? transferTargetRoom.name : _t("unknown person");
|
||||
const transferTargetName = transferTargetRoom ? transferTargetRoom.name : _t("voip|unknown_person");
|
||||
const transfereeCallRoomId = LegacyCallHandler.instance.roomIdForCall(transfereeCall);
|
||||
const transfereeRoom = transfereeCallRoomId ? cli.getRoom(transfereeCallRoomId) : null;
|
||||
const transfereeName = transfereeRoom ? transfereeRoom.name : _t("unknown person");
|
||||
const transfereeName = transfereeRoom ? transfereeRoom.name : _t("voip|unknown_person");
|
||||
|
||||
holdTransferContent = (
|
||||
<div className="mx_LegacyCallView_status">
|
||||
@ -508,7 +508,7 @@ export default class LegacyCallView extends React.Component<IProps, IState> {
|
||||
<RoomAvatar room={callRoom} size={avatarSize} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx_LegacyCallView_status">{_t("Connecting")}</div>
|
||||
<div className="mx_LegacyCallView_status">{_t("voip|connecting")}</div>
|
||||
{secondaryFeedElement}
|
||||
</div>
|
||||
);
|
||||
|
@ -143,7 +143,7 @@ export class Media {
|
||||
public downloadSource(): Promise<Response> {
|
||||
const src = this.srcHttp;
|
||||
if (!src) {
|
||||
throw new UserFriendlyError("Failed to download source media, no source url was found");
|
||||
throw new UserFriendlyError("error|download_media");
|
||||
}
|
||||
return fetch(src);
|
||||
}
|
||||
|
@ -190,16 +190,16 @@ export const useRoomCall = (
|
||||
let videoCallDisabledReason: string | null;
|
||||
switch (state) {
|
||||
case State.NoPermission:
|
||||
voiceCallDisabledReason = _t("You do not have permission to start voice calls");
|
||||
videoCallDisabledReason = _t("You do not have permission to start video calls");
|
||||
voiceCallDisabledReason = _t("voip|disabled_no_perms_start_voice_call");
|
||||
videoCallDisabledReason = _t("voip|disabled_no_perms_start_video_call");
|
||||
break;
|
||||
case State.Ongoing:
|
||||
voiceCallDisabledReason = _t("Ongoing call");
|
||||
videoCallDisabledReason = _t("Ongoing call");
|
||||
voiceCallDisabledReason = _t("voip|disabled_ongoing_call");
|
||||
videoCallDisabledReason = _t("voip|disabled_ongoing_call");
|
||||
break;
|
||||
case State.NoOneHere:
|
||||
voiceCallDisabledReason = _t("There's no one here to call");
|
||||
videoCallDisabledReason = _t("There's no one here to call");
|
||||
voiceCallDisabledReason = _t("voip|disabled_no_one_here");
|
||||
videoCallDisabledReason = _t("voip|disabled_no_one_here");
|
||||
break;
|
||||
case State.Unpinned:
|
||||
case State.NoCall:
|
||||
|
@ -31,7 +31,7 @@ const getRoomName = (room?: Room, oobName = ""): string => room?.name || oobName
|
||||
* @returns {string} the room name
|
||||
*/
|
||||
export function useRoomName(room?: Room, oobData?: IOOBData): string {
|
||||
let oobName = _t("Join Room");
|
||||
let oobName = _t("common|unnamed_room");
|
||||
if (oobData && oobData.name) {
|
||||
oobName = oobData.name;
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
{
|
||||
"Create new room": "إنشاء غرفة جديدة",
|
||||
"Failed to change password. Is your password correct?": "فشلت عملية تعديل الكلمة السرية. هل كلمتك السرية صحيحة ؟",
|
||||
"Send": "إرسال",
|
||||
"Unavailable": "غير متوفر",
|
||||
"All Rooms": "كل الغُرف",
|
||||
"All messages": "كل الرسائل",
|
||||
"Changelog": "سِجل التغييرات",
|
||||
"Thank you!": "شكرًا !",
|
||||
"Permission Required": "التصريح مطلوب",
|
||||
@ -33,11 +31,9 @@
|
||||
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
|
||||
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s": "%(weekDayName)s، %(day)s %(monthName)s %(fullYear)s",
|
||||
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s، %(day)s %(monthName)s %(fullYear)s %(time)s",
|
||||
"Default": "المبدئي",
|
||||
"Restricted": "مقيد",
|
||||
"Moderator": "مشرف",
|
||||
"Logs sent": "تم ارسال سجل الاحداث",
|
||||
"Reason": "السبب",
|
||||
"You signed in to a new session without verifying it:": "قمت بتسجيل الدخول لجلسة جديدة من غير التحقق منها:",
|
||||
"Verify your other session using one of the options below.": "أكِّد جلستك الأخرى باستخدام أحد الخيارات أدناه.",
|
||||
"%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s%(userId)s تم تسجيل الدخول لجلسة جديدة من غير التحقق منها:",
|
||||
@ -122,7 +118,6 @@
|
||||
"There was an error updating the room's alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "حدث خطأ أثناء تحديث العناوين البديلة للغرفة. قد لا يسمح به الخادم أو حدث فشل مؤقت.",
|
||||
"There was an error updating the room's main address. It may not be allowed by the server or a temporary failure occurred.": "حدث خطأ أثناء تحديث العنوان الرئيسي للغرفة. قد لا يسمح به الخادم أو حدث فشل مؤقت.",
|
||||
"Error updating main address": "تعذر تحديث العنوان الرئيسي",
|
||||
"Mark all as read": "أشر عليها بأنها قرأت",
|
||||
"Jump to first unread message.": "الانتقال إلى أول رسالة غير مقروءة.",
|
||||
"Invited by %(sender)s": "دُعيت من %(sender)s",
|
||||
"Revoke invite": "إبطال الدعوة",
|
||||
@ -139,8 +134,6 @@
|
||||
"This room has already been upgraded.": "سبق وأن تمت ترقية هذه الغرفة.",
|
||||
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "ستؤدي ترقية هذه الغرفة إلى إغلاق النسخة الحالية للغرفة وإنشاء غرفة تمت ترقيتها بنفس الاسم.",
|
||||
"Room options": "خيارات الغرفة",
|
||||
"Jump to first invite.": "الانتقال لأول دعوة.",
|
||||
"Jump to first unread room.": "الانتقال لأول غرفة لم تقرأ.",
|
||||
"%(roomName)s is not accessible at this time.": "لا يمكن الوصول إلى %(roomName)s في الوقت الحالي.",
|
||||
"%(roomName)s does not exist.": "الغرفة %(roomName)s ليست موجودة.",
|
||||
"%(roomName)s can't be previewed. Do you want to join it?": "لا يمكن معاينة %(roomName)s. هل تريد الانضمام إليها؟",
|
||||
@ -169,7 +162,6 @@
|
||||
"Join the conversation with an account": "انضم للمحادثة بحساب",
|
||||
"Historical": "تاريخي",
|
||||
"Low priority": "أولوية منخفضة",
|
||||
"Explore public rooms": "استكشف الغرف العامة",
|
||||
"Add room": "أضف غرفة",
|
||||
"Rooms": "الغرف",
|
||||
"Show Widgets": "إظهار عناصر الواجهة",
|
||||
@ -181,8 +173,6 @@
|
||||
"other": "(~%(count)s نتائج)"
|
||||
},
|
||||
"Unnamed room": "غرفة بلا اسم",
|
||||
"No recently visited rooms": "لا توجد غرف تمت زيارتها مؤخرًا",
|
||||
"Room %(name)s": "الغرفة %(name)s",
|
||||
"Replying": "الرد",
|
||||
"%(duration)sd": "%(duration)sي",
|
||||
"%(duration)sh": "%(duration)sس",
|
||||
@ -195,80 +185,20 @@
|
||||
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (قوة %(powerLevelNumber)s)",
|
||||
"Filter room members": "تصفية أعضاء الغرفة",
|
||||
"Invited": "مدعو",
|
||||
"Invite to this room": "ادع لهذه الغرفة",
|
||||
"and %(count)s others...": {
|
||||
"one": "وواحدة أخرى...",
|
||||
"other": "و %(count)s أخر..."
|
||||
},
|
||||
"Close preview": "إغلاق المعاينة",
|
||||
"Scroll to most recent messages": "انتقل إلى أحدث الرسائل",
|
||||
"The authenticity of this encrypted message can't be guaranteed on this device.": "لا يمكن ضمان موثوقية هذه الرسالة المشفرة على هذا الجهاز.",
|
||||
"Encrypted by a deleted session": "مشفرة باتصال محذوف",
|
||||
"Unencrypted": "غير مشفر",
|
||||
"Encrypted by an unverified session": "مشفرة باتصال لم يتم التحقق منه",
|
||||
"Ignored users": "المستخدمون المتجاهَلون",
|
||||
"None": "لا شيء",
|
||||
"To report a Matrix-related security issue, please read the Matrix.org <a>Security Disclosure Policy</a>.": "للإبلاغ عن مشكلة أمنية متعلقة بMatrix ، يرجى قراءة <a>سياسة الإفصاح الأمني</a> في Matrix.org.",
|
||||
"General": "عام",
|
||||
"Discovery": "الاكتشاف",
|
||||
"Deactivate account": "تعطيل الحساب",
|
||||
"Deactivate Account": "تعطيل الحساب",
|
||||
"Account management": "إدارة الحساب",
|
||||
"Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "وافق على شروط خدمة خادم الهوية %(serverName)s لتكون قابلاً للاكتشاف عن طريق عنوان البريد الإلكتروني أو رقم الهاتف.",
|
||||
"Phone numbers": "أرقام الهواتف",
|
||||
"Email addresses": "عنوان البريد الإلكتروني",
|
||||
"Manage integrations": "إدارة التكاملات",
|
||||
"Enter a new identity server": "أدخل خادم هوية جديدًا",
|
||||
"Do not use an identity server": "لا تستخدم خادم هوية",
|
||||
"Using an identity server is optional. If you choose not to use an identity server, you won't be discoverable by other users and you won't be able to invite others by email or phone.": "استخدام خادم الهوية اختياري. إذا اخترت عدم استخدام خادم هوية ، فلن يتمكن المستخدمون الآخرون من اكتشافك ولن تتمكن من دعوة الآخرين عبر البريد الإلكتروني أو الهاتف.",
|
||||
"Disconnecting from your identity server will mean you won't be discoverable by other users and you won't be able to invite others by email or phone.": "قطع الاتصال بخادم الهوية الخاص بك يعني أنك لن تكون قابلاً للاكتشاف من قبل المستخدمين الآخرين ولن تتمكن من دعوة الآخرين عبر البريد الإلكتروني أو الهاتف.",
|
||||
"You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "أنت لا تستخدم حاليًا خادم هوية. لاكتشاف جهات الاتصال الحالية التي تعرفها وتكون قابلاً للاكتشاف ، أضف واحداً أدناه.",
|
||||
"If you don't want to use <server /> to discover and be discoverable by existing contacts you know, enter another identity server below.": "إذا كنت لا تريد استخدام <server /> لاكتشاف جهات الاتصال الموجودة التي تعرفها وتكون قابلاً للاكتشاف ، فأدخل خادم هوية آخر أدناه.",
|
||||
"You are currently using <server></server> to discover and be discoverable by existing contacts you know. You can change your identity server below.": "أنت تستخدم حاليًا <server> </server> لاكتشاف جهات الاتصال الحالية التي تعرفها وتجعل نفسك قابلاً للاكتشاف. يمكنك تغيير خادم الهوية الخاص بك أدناه.",
|
||||
"We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "نوصي بإزالة عناوين البريد الإلكتروني وأرقام الهواتف من خادم الهوية قبل قطع الاتصال.",
|
||||
"You are still <b>sharing your personal data</b> on the identity server <idserver />.": "لا زالت <b>بياناتك الشخصية مشاعة</b> على خادم الهوية <idserver />.",
|
||||
"Disconnect anyway": "افصل على أي حال",
|
||||
"wait and try again later": "انتظر وعاوِد لاحقًا",
|
||||
"contact the administrators of identity server <idserver />": "اتصل بمديري خادم الهوية <idserver />",
|
||||
"check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "تحقق من المكونات الإضافية للمتصفح الخاص بك بحثًا عن أي شيء قد يحظر خادم الهوية (مثل Privacy Badger)",
|
||||
"You should:": "يجب عليك:",
|
||||
"You should <b>remove your personal data</b> from identity server <idserver /> before disconnecting. Unfortunately, identity server <idserver /> is currently offline or cannot be reached.": "لابد من محو <b>بيانات الشخصية</b> من خادم الهوية <idserver /> قبل الانفصال. لسوء الحظ ، خادم الهوية <idserver /> حاليًّا خارج الشبكة أو لا يمكن الوصول إليه.",
|
||||
"Disconnect from the identity server <idserver />?": "انفصل عن خادم الهوية <idserver />؟",
|
||||
"Disconnect identity server": "افصل خادم الهوية",
|
||||
"The identity server you have chosen does not have any terms of service.": "خادم الهوية الذي اخترت ليس له شروط خدمة.",
|
||||
"Terms of service not accepted or the identity server is invalid.": "شروط الخدمة لم تُقبل أو أن خادم الهوية مردود.",
|
||||
"Disconnect from the identity server <current /> and connect to <new /> instead?": "انفصل عن خادم الهوية <current /> واتصل بآخر <new /> بدلاً منه؟",
|
||||
"Change identity server": "تغيير خادم الهوية",
|
||||
"Checking server": "فحص خادم",
|
||||
"not ready": "غير جاهز",
|
||||
"ready": "جاهز",
|
||||
"Secret storage:": "التخزين السري:",
|
||||
"in account data": "بيانات في حساب",
|
||||
"Secret storage public key:": "المفتاح العام للتخزين السري:",
|
||||
"Backup key cached:": "المفتاح الاحتياطي المحفوظ (في cache):",
|
||||
"Backup key stored:": "المفتاح الاختياطي المحفوظ:",
|
||||
"not stored": "لم يُحفظ",
|
||||
"unexpected type": "نوع غير متوقع",
|
||||
"well formed": "مشكل جيّداً",
|
||||
"Back up your keys before signing out to avoid losing them.": "أضف مفاتيحك للاحتياطي قبل تسجيل الخروج لتتجنب ضياعها.",
|
||||
"Your keys are <b>not being backed up from this session</b>.": "مفاتيحك <b>لا احتياطيَّ لها من هذا الاتصال</b>.",
|
||||
"Algorithm:": "الخوارزمية:",
|
||||
"Backup version:": "نسخة الاحتياطي:",
|
||||
"This backup is trusted because it has been restored on this session": "هذا الاحتياطي موثوق به لأنه تمت استعادته في هذا الاتصال",
|
||||
"All keys backed up": "جميع المفاتيح منسوخة في الاحتياطي",
|
||||
"Connect this session to Key Backup": "اربط هذا الاتصال باحتياطي مفتاح",
|
||||
"Connect this session to key backup before signing out to avoid losing any keys that may only be on this session.": "اربط هذا الاتصال باحتياطي قبل تسجيل الخروج لتجنب فقدان أي مفاتيح قد تكون موجودة فقط في هذا الاتصال.",
|
||||
"This session is <b>not backing up your keys</b>, but you do have an existing backup you can restore from and add to going forward.": "هذا الاتصال <b>لم يعتمد الاحتياطي لمفاتيحك</b> لكن لديك احتياطي يمكنك الاتسرجاع منه والإضافة إليه فيما بعد.",
|
||||
"Restore from Backup": "استعادة من الاحتياطي",
|
||||
"Unable to load key backup status": "تعذر حمل حالة النسخ الاحتياطي للمفتاح",
|
||||
"Are you sure? You will lose your encrypted messages if your keys are not backed up properly.": "هل أنت واثق؟ ستفقد رسائلك المشفرة إذا لم يتم نسخ المفاتيح احتياطيًا بشكل صحيح.",
|
||||
"Delete Backup": "حذف الحتياطي",
|
||||
"Profile picture": "الصورة الشخصية",
|
||||
"Display Name": "الاسم الظاهر",
|
||||
"Profile": "الملف الشخصي",
|
||||
"The operation could not be completed": "تعذر إتمام العملية",
|
||||
"Failed to save your profile": "تعذر حفظ ملفك الشخصي",
|
||||
"Notification targets": "أهداف الإشعار",
|
||||
"You've successfully verified your device!": "لقد نجحت في التحقق من جهازك!",
|
||||
"Verify all users in a room to ensure it's secure.": "تحقق من جميع المستخدمين في الغرفة للتأكد من أنها آمنة.",
|
||||
"Almost there! Is %(displayName)s showing the same shield?": "أوشكت على الوصول! هل يظهر %(displayName)s نفس الدرع؟",
|
||||
@ -284,7 +214,6 @@
|
||||
"Deactivate user?": "إلغاء نشاط المستخدم؟",
|
||||
"Are you sure?": "هل متأكد أنت؟",
|
||||
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "لن تكون قادرًا على التراجع عن هذا التغيير لأنك ترقي المستخدم ليكون له نفس مستوى الطاقة لديك.",
|
||||
"Failed to change power level": "تعذر تغيير مستوى القوة",
|
||||
"Failed to mute user": "تعذر كتم المستخدم",
|
||||
"Failed to ban user": "تعذر حذف المستخدم",
|
||||
"Remove recent messages": "احذف الرسائل الحديثة",
|
||||
@ -335,13 +264,8 @@
|
||||
"Room avatar": "صورة الغرفة",
|
||||
"Room Topic": "موضوع الغرفة",
|
||||
"Room Name": "اسم الغرفة",
|
||||
"The integration manager is offline or it cannot reach your homeserver.": "مدري التكامل غير متصل بالإنرتنت أو لا يمكنه الوصول إلى خادمك الوسيط.",
|
||||
"Cannot connect to integration manager": "لا يمكن الاتصال بمدير التكامل",
|
||||
"Failed to set display name": "تعذر تعيين الاسم الظاهر",
|
||||
"Authentication": "المصادقة",
|
||||
"Set up": "تأسيس",
|
||||
"Warning!": "إنذار!",
|
||||
"No display name": "لا اسم ظاهر",
|
||||
"Show more": "أظهر أكثر",
|
||||
"Dog": "كلب",
|
||||
"IRC display name width": "عرض الاسم الظاهر لIRC",
|
||||
@ -353,53 +277,6 @@
|
||||
"You have verified this user. This user has verified all of their sessions.": "لقد تحققت من هذا المستخدم. لقد تحقق هذا المستخدم من جميع اتصالاته.",
|
||||
"You have not verified this user.": "أنت لم تتحقق من هذا المستخدم.",
|
||||
"This user has not verified all of their sessions.": "هذا المستخدم لم يتحقق من جميع اتصالاته.",
|
||||
"Phone Number": "رقم الهاتف",
|
||||
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "تم إرسال رسالة نصية إلى +%(msisdn)s. الرجاء إدخال رمز التحقق الذي فيها.",
|
||||
"Remove %(phone)s?": "حذف %(phone)s؟",
|
||||
"Email Address": "عنوان بريد الكتروني",
|
||||
"We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "لقد أرسلنا إليك بريدًا إلكترونيًا للتحقق من عنوانك. يرجى اتباع التعليمات الموجودة هناك ثم نقر الزر أدناه.",
|
||||
"Unable to add email address": "تعذرت إضافة عنوان البريد الإلكتروني",
|
||||
"This doesn't appear to be a valid email address": "لا يبدو عنوان البريد الإلكتروني هذا صالحاً",
|
||||
"Invalid Email Address": "عنوان البريد الإلكتروني غير صالح",
|
||||
"Remove %(email)s?": "حذف %(email)s؟",
|
||||
"Unable to remove contact information": "غير قادر على إزالة معلومات التواصل",
|
||||
"Discovery options will appear once you have added a phone number above.": "ستظهر خيارات الاكتشاف بمجرد إضافة رقم هاتف أعلاه.",
|
||||
"Verification code": "رمز التحقق",
|
||||
"Please enter verification code sent via text.": "الرجاء إدخال رمز التحقق المرسل عبر النص.",
|
||||
"Incorrect verification code": "رمز التحقق غير صحيح",
|
||||
"Unable to verify phone number.": "تعذر التحقق من رقم الهاتف.",
|
||||
"Unable to share phone number": "تعذرت مشاركة رقم الهاتف",
|
||||
"Unable to revoke sharing for phone number": "تعذر إبطال مشاركة رقم الهاتف",
|
||||
"Discovery options will appear once you have added an email above.": "ستظهر خيارات الاكتشاف بمجرد إضافة بريد إلكتروني أعلاه.",
|
||||
"Verify the link in your inbox": "تحقق من الرابط في بريدك الوارد",
|
||||
"Unable to verify email address.": "تعذر التحقق من عنوان البريد الإلكتروني.",
|
||||
"Click the link in the email you received to verify and then click continue again.": "انقر الرابط الواصل لبريدك الإلكتروني للتحقق ثم انقر \"متابعة\" مرة أخرى.",
|
||||
"Your email address hasn't been verified yet": "لم يتم التحقق من عنوان بريدك الإلكتروني حتى الآن",
|
||||
"Unable to share email address": "تعذرت مشاركة البريد الإلتكروني",
|
||||
"Unable to revoke sharing for email address": "تعذر إبطال مشاركة عنوان البريد الإلكتروني",
|
||||
"An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "تعذر تغيير مستوى قوة المستخدم. تأكد من أن لديك صلاحيات كافية وحاول مرة أخرى.",
|
||||
"Error changing power level": "تعذر تغيير مستوى القوة",
|
||||
"An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "تعذر تغيير مستوى قوة الغرفة. تأكد من أن لديك صلاحيات كافية وحاول مرة أخرى.",
|
||||
"Error changing power level requirement": "تعذر تغيير متطلبات مستوى القوة",
|
||||
"Banned by %(displayName)s": "حظره %(displayName)s",
|
||||
"Failed to unban": "تعذر فك الحظر",
|
||||
"Browse": "تصفح",
|
||||
"Set a new custom sound": "تعيين صوت مخصص جديد",
|
||||
"Notification sound": "صوت الإشعار",
|
||||
"Sounds": "الأصوات",
|
||||
"Uploaded sound": "صوت تمام الرفع",
|
||||
"Room Addresses": "عناوين الغرف",
|
||||
"Bridges": "الجسور",
|
||||
"This room is bridging messages to the following platforms. <a>Learn more.</a>": "تعمل هذه الغرفة على توصيل الرسائل بالمنصات التالية. <a> اعرف المزيد. </a>",
|
||||
"Room information": "معلومات الغرفة",
|
||||
"Voice & Video": "الصوت والفيديو",
|
||||
"Audio Output": "مخرج الصوت",
|
||||
"No Webcams detected": "لم يتم الكشف عن كاميرات الويب",
|
||||
"No Microphones detected": "لم يتم الكشف عن أجهزة ميكروفون",
|
||||
"No Audio Outputs detected": "لم يتم الكشف عن مخرجات الصوت",
|
||||
"Request media permissions": "اطلب الإذن للوسائط",
|
||||
"Missing media permissions, click the button below to request.": "إذن الوسائط مفقود ، انقر الزر أدناه لطلب الإذن.",
|
||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "قام مسؤول الخادم بتعطيل التشفير من طرف إلى طرف أصلاً في الغرف الخاصة والرسائل الخاصّة.",
|
||||
"Ok": "حسنا",
|
||||
"Your homeserver has exceeded one of its resource limits.": "لقد تجاوز خادمك أحد حدود موارده.",
|
||||
"Your homeserver has exceeded its user limit.": "لقد تجاوز خادمك حد عدد المستخدمين.",
|
||||
@ -462,14 +339,6 @@
|
||||
"American Samoa": "ساموا الأمريكية",
|
||||
"Algeria": "الجزائر",
|
||||
"Åland Islands": "جزر آلاند",
|
||||
"Explore rooms": "استكشِف الغرف",
|
||||
"Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.": "يتلقى مديرو التكامل بيانات الضبط، ويمكنهم تعديل عناصر واجهة المستخدم، وإرسال دعوات الغرف، وتعيين مستويات القوة نيابة عنك.",
|
||||
"Use an integration manager to manage bots, widgets, and sticker packs.": "استخدم مدير التكامل لإدارة البوتات وعناصر الواجهة وحزم الملصقات.",
|
||||
"Use an integration manager <b>(%(serverName)s)</b> to manage bots, widgets, and sticker packs.": "استخدم مدير التكامل <b>(%(serverName)s)</b> لإدارة البوتات وعناصر الواجهة وحزم الملصقات.",
|
||||
"Identity server (%(server)s)": "خادوم الهوية (%(server)s)",
|
||||
"Could not connect to identity server": "تعذر الاتصال بخادوم الهوية",
|
||||
"Not a valid identity server (status code %(code)s)": "ليس خادوم هوية صالح (رمز الحالة %(code)s)",
|
||||
"Identity server URL must be HTTPS": "يجب أن يستعمل رابط (URL) خادوم الهوية ميفاق HTTPS",
|
||||
"Paraguay": "باراغواي",
|
||||
"Netherlands": "هولندا",
|
||||
"Greece": "اليونان",
|
||||
@ -615,7 +484,12 @@
|
||||
"on": "مشتغل",
|
||||
"off": "مطفأ",
|
||||
"copied": "نُسخ!",
|
||||
"Advanced": "متقدم"
|
||||
"advanced": "متقدم",
|
||||
"general": "عام",
|
||||
"profile": "الملف الشخصي",
|
||||
"display_name": "الاسم الظاهر",
|
||||
"user_avatar": "الصورة الشخصية",
|
||||
"authentication": "المصادقة"
|
||||
},
|
||||
"action": {
|
||||
"continue": "واصِل",
|
||||
@ -670,7 +544,9 @@
|
||||
"review": "مراجعة",
|
||||
"manage": "إدارة",
|
||||
"mention": "إشارة",
|
||||
"unban": "فك الحظر"
|
||||
"unban": "فك الحظر",
|
||||
"explore_rooms": "استكشِف الغرف",
|
||||
"explore_public_rooms": "استكشف الغرف العامة"
|
||||
},
|
||||
"labs": {
|
||||
"pinning": "تثبيت الرسالة",
|
||||
@ -759,7 +635,8 @@
|
||||
"noisy": "مزعج",
|
||||
"error_permissions_denied": "لا يملك %(brand)s التصريح لإرسال التنبيهات. من فضلك تحقّق من إعدادات المتصفح",
|
||||
"error_permissions_missing": "لم تقدّم التصريح اللازم كي يُرسل %(brand)s التنبيهات. من فضلك أعِد المحاولة",
|
||||
"error_title": "تعذر تفعيل التنبيهات"
|
||||
"error_title": "تعذر تفعيل التنبيهات",
|
||||
"push_targets": "أهداف الإشعار"
|
||||
},
|
||||
"appearance": {
|
||||
"heading": "تخصيص مظهرك",
|
||||
@ -784,7 +661,14 @@
|
||||
"inline_url_previews_room_account": "تمكين معاينة الروابط لهذه الغرفة (يؤثر عليك فقط)",
|
||||
"inline_url_previews_room": "تمكين معاينة الروابط أصلاً لأي مشارك في هذه الغرفة",
|
||||
"voip": {
|
||||
"mirror_local_feed": "محاكاة تغذية الفيديو المحلية"
|
||||
"mirror_local_feed": "محاكاة تغذية الفيديو المحلية",
|
||||
"missing_permissions_prompt": "إذن الوسائط مفقود ، انقر الزر أدناه لطلب الإذن.",
|
||||
"request_permissions": "اطلب الإذن للوسائط",
|
||||
"audio_output": "مخرج الصوت",
|
||||
"audio_output_empty": "لم يتم الكشف عن مخرجات الصوت",
|
||||
"audio_input_empty": "لم يتم الكشف عن أجهزة ميكروفون",
|
||||
"video_input_empty": "لم يتم الكشف عن كاميرات الويب",
|
||||
"title": "الصوت والفيديو"
|
||||
},
|
||||
"security": {
|
||||
"send_analytics": "إرسال بيانات التحليلات",
|
||||
@ -817,7 +701,30 @@
|
||||
"encryption_individual_verification_mode": "تحقق بشكل فردي من كل اتصال يستخدمه المستخدم لتمييزه أنه موثوق ، دون الوثوق بالأجهزة الموقعة بالتبادل.",
|
||||
"message_search_disabled": "تخزين الرسائل المشفرة بشكل آمن (في cache) محليًا حتى تظهر في نتائج البحث.",
|
||||
"message_search_unsupported": "%(brand)s يفقد بعض المكونات المطلوبة لحفظ آمن محليًّا للرسائل المشفرة. إذا أدرت تجربة هذه الخاصية، فأنشئ %(brand)s على سطح المكتب مع <nativeLink>إضافة مكونات البحث</nativeLink>.",
|
||||
"message_search_unsupported_web": "%(brand)s لا يستطيع تخزين الرسائل المشفرة محليًّا (في cache) بشكل آمن طالما أنه يعمل على متصفح ويب. استخدم <desktopLink>%(brand)s على سطح المكتب</desktopLink> لتظهر لك الرسائل المشفرة في نتائج البحث."
|
||||
"message_search_unsupported_web": "%(brand)s لا يستطيع تخزين الرسائل المشفرة محليًّا (في cache) بشكل آمن طالما أنه يعمل على متصفح ويب. استخدم <desktopLink>%(brand)s على سطح المكتب</desktopLink> لتظهر لك الرسائل المشفرة في نتائج البحث.",
|
||||
"backup_key_well_formed": "مشكل جيّداً",
|
||||
"backup_key_unexpected_type": "نوع غير متوقع",
|
||||
"backup_key_stored_status": "المفتاح الاختياطي المحفوظ:",
|
||||
"cross_signing_not_stored": "لم يُحفظ",
|
||||
"backup_key_cached_status": "المفتاح الاحتياطي المحفوظ (في cache):",
|
||||
"4s_public_key_status": "المفتاح العام للتخزين السري:",
|
||||
"4s_public_key_in_account_data": "بيانات في حساب",
|
||||
"secret_storage_status": "التخزين السري:",
|
||||
"secret_storage_ready": "جاهز",
|
||||
"secret_storage_not_ready": "غير جاهز",
|
||||
"delete_backup": "حذف الحتياطي",
|
||||
"delete_backup_confirm_description": "هل أنت واثق؟ ستفقد رسائلك المشفرة إذا لم يتم نسخ المفاتيح احتياطيًا بشكل صحيح.",
|
||||
"error_loading_key_backup_status": "تعذر حمل حالة النسخ الاحتياطي للمفتاح",
|
||||
"restore_key_backup": "استعادة من الاحتياطي",
|
||||
"key_backup_inactive": "هذا الاتصال <b>لم يعتمد الاحتياطي لمفاتيحك</b> لكن لديك احتياطي يمكنك الاتسرجاع منه والإضافة إليه فيما بعد.",
|
||||
"key_backup_connect_prompt": "اربط هذا الاتصال باحتياطي قبل تسجيل الخروج لتجنب فقدان أي مفاتيح قد تكون موجودة فقط في هذا الاتصال.",
|
||||
"key_backup_connect": "اربط هذا الاتصال باحتياطي مفتاح",
|
||||
"key_backup_complete": "جميع المفاتيح منسوخة في الاحتياطي",
|
||||
"key_backup_algorithm": "الخوارزمية:",
|
||||
"key_backup_inactive_warning": "مفاتيحك <b>لا احتياطيَّ لها من هذا الاتصال</b>.",
|
||||
"key_backup_active_version_none": "لا شيء",
|
||||
"ignore_users_section": "المستخدمون المتجاهَلون",
|
||||
"e2ee_default_disabled_warning": "قام مسؤول الخادم بتعطيل التشفير من طرف إلى طرف أصلاً في الغرف الخاصة والرسائل الخاصّة."
|
||||
},
|
||||
"preferences": {
|
||||
"room_list_heading": "قائمة الغرفة",
|
||||
@ -837,7 +744,42 @@
|
||||
"add_msisdn_confirm_sso_button": "أكّد إضافتك لرقم الهاتف هذا باستعمال الولوج الموحّد لإثبات هويّتك.",
|
||||
"add_msisdn_confirm_button": "أكّد إضافة رقم الهاتف",
|
||||
"add_msisdn_confirm_body": "انقر الزر بالأسفل لتأكيد إضافة رقم الهاتف هذا.",
|
||||
"add_msisdn_dialog_title": "أضِف رقم الهاتف"
|
||||
"add_msisdn_dialog_title": "أضِف رقم الهاتف",
|
||||
"name_placeholder": "لا اسم ظاهر",
|
||||
"error_saving_profile_title": "تعذر حفظ ملفك الشخصي",
|
||||
"error_saving_profile": "تعذر إتمام العملية",
|
||||
"error_password_change_403": "فشلت عملية تعديل الكلمة السرية. هل كلمتك السرية صحيحة ؟",
|
||||
"emails_heading": "عنوان البريد الإلكتروني",
|
||||
"msisdns_heading": "أرقام الهواتف",
|
||||
"discovery_needs_terms": "وافق على شروط خدمة خادم الهوية %(serverName)s لتكون قابلاً للاكتشاف عن طريق عنوان البريد الإلكتروني أو رقم الهاتف.",
|
||||
"deactivate_section": "تعطيل الحساب",
|
||||
"account_management_section": "إدارة الحساب",
|
||||
"discovery_section": "الاكتشاف",
|
||||
"error_revoke_email_discovery": "تعذر إبطال مشاركة عنوان البريد الإلكتروني",
|
||||
"error_share_email_discovery": "تعذرت مشاركة البريد الإلتكروني",
|
||||
"email_not_verified": "لم يتم التحقق من عنوان بريدك الإلكتروني حتى الآن",
|
||||
"email_verification_instructions": "انقر الرابط الواصل لبريدك الإلكتروني للتحقق ثم انقر \"متابعة\" مرة أخرى.",
|
||||
"error_email_verification": "تعذر التحقق من عنوان البريد الإلكتروني.",
|
||||
"discovery_email_verification_instructions": "تحقق من الرابط في بريدك الوارد",
|
||||
"discovery_email_empty": "ستظهر خيارات الاكتشاف بمجرد إضافة بريد إلكتروني أعلاه.",
|
||||
"error_revoke_msisdn_discovery": "تعذر إبطال مشاركة رقم الهاتف",
|
||||
"error_share_msisdn_discovery": "تعذرت مشاركة رقم الهاتف",
|
||||
"error_msisdn_verification": "تعذر التحقق من رقم الهاتف.",
|
||||
"incorrect_msisdn_verification": "رمز التحقق غير صحيح",
|
||||
"msisdn_verification_instructions": "الرجاء إدخال رمز التحقق المرسل عبر النص.",
|
||||
"msisdn_verification_field_label": "رمز التحقق",
|
||||
"discovery_msisdn_empty": "ستظهر خيارات الاكتشاف بمجرد إضافة رقم هاتف أعلاه.",
|
||||
"error_set_name": "تعذر تعيين الاسم الظاهر",
|
||||
"error_remove_3pid": "غير قادر على إزالة معلومات التواصل",
|
||||
"remove_email_prompt": "حذف %(email)s؟",
|
||||
"error_invalid_email": "عنوان البريد الإلكتروني غير صالح",
|
||||
"error_invalid_email_detail": "لا يبدو عنوان البريد الإلكتروني هذا صالحاً",
|
||||
"error_add_email": "تعذرت إضافة عنوان البريد الإلكتروني",
|
||||
"add_email_instructions": "لقد أرسلنا إليك بريدًا إلكترونيًا للتحقق من عنوانك. يرجى اتباع التعليمات الموجودة هناك ثم نقر الزر أدناه.",
|
||||
"email_address_label": "عنوان بريد الكتروني",
|
||||
"remove_msisdn_prompt": "حذف %(phone)s؟",
|
||||
"add_msisdn_instructions": "تم إرسال رسالة نصية إلى +%(msisdn)s. الرجاء إدخال رمز التحقق الذي فيها.",
|
||||
"msisdn_label": "رقم الهاتف"
|
||||
}
|
||||
},
|
||||
"devtools": {
|
||||
@ -975,6 +917,9 @@
|
||||
"lightbox_title": "%(senderDisplayName)s غير صورة الغرفة %(roomName)s",
|
||||
"removed": "%(senderDisplayName)s حذف صورة الغرفة.",
|
||||
"changed_img": "%(senderDisplayName)s غير صورة الغرفة إلى <img/>"
|
||||
},
|
||||
"url_preview": {
|
||||
"close": "إغلاق المعاينة"
|
||||
}
|
||||
},
|
||||
"slash_command": {
|
||||
@ -1138,7 +1083,14 @@
|
||||
"send_event_type": "إرسال أحداث من نوع %(eventType)s",
|
||||
"title": "الأدوار والصلاحيات",
|
||||
"permissions_section": "الصلاحيات",
|
||||
"permissions_section_description_room": "حدد الأدوار المطلوبة لتغيير أجزاء مختلفة من الغرفة"
|
||||
"permissions_section_description_room": "حدد الأدوار المطلوبة لتغيير أجزاء مختلفة من الغرفة",
|
||||
"error_unbanning": "تعذر فك الحظر",
|
||||
"banned_by": "حظره %(displayName)s",
|
||||
"ban_reason": "السبب",
|
||||
"error_changing_pl_reqs_title": "تعذر تغيير متطلبات مستوى القوة",
|
||||
"error_changing_pl_reqs_description": "تعذر تغيير مستوى قوة الغرفة. تأكد من أن لديك صلاحيات كافية وحاول مرة أخرى.",
|
||||
"error_changing_pl_title": "تعذر تغيير مستوى القوة",
|
||||
"error_changing_pl_description": "تعذر تغيير مستوى قوة المستخدم. تأكد من أن لديك صلاحيات كافية وحاول مرة أخرى."
|
||||
},
|
||||
"security": {
|
||||
"strict_encryption": "لا ترسل أبدًا رسائل مشفرة إلى اتصالات التي لم يتم التحقق منها في هذه الغرفة من هذا الاتصال",
|
||||
@ -1163,14 +1115,28 @@
|
||||
"default_url_previews_off": "معاينات URL معطلة بشكل أصلي للمشاركين في هذه الغرفة.",
|
||||
"url_preview_encryption_warning": "في الغرف المشفرة ، مثل هذه الغرفة ، يتم تعطيل معاينات URL أصلاً للتأكد من أن خادمك الوسيط (حيث يتم إنشاء المعاينات) لا يمكنه جمع معلومات حول الروابط التي تراها في هذه الغرفة.",
|
||||
"url_preview_explainer": "عندما يضع شخص ما عنوان URL في رسالته ، يمكن عرض معاينة عنوان URL لإعطاء مزيد من المعلومات حول هذا الرابط مثل العنوان والوصف وصورة من موقع الويب.",
|
||||
"url_previews_section": "معاينة الروابط"
|
||||
"url_previews_section": "معاينة الروابط",
|
||||
"aliases_section": "عناوين الغرف",
|
||||
"other_section": "أخرى"
|
||||
},
|
||||
"advanced": {
|
||||
"unfederated": "لا يمكن الوصول إلى هذه الغرفة بواسطة خوادم Matrix البعيدة",
|
||||
"room_upgrade_button": "قم بترقية هذه الغرفة إلى إصدار الغرفة الموصى به",
|
||||
"room_predecessor": "عرض رسائل أقدم في %(roomName)s.",
|
||||
"room_version_section": "إصدار الغرفة",
|
||||
"room_version": "إصدار الغرفة:"
|
||||
"room_version": "إصدار الغرفة:",
|
||||
"information_section_room": "معلومات الغرفة"
|
||||
},
|
||||
"bridges": {
|
||||
"description": "تعمل هذه الغرفة على توصيل الرسائل بالمنصات التالية. <a> اعرف المزيد. </a>",
|
||||
"title": "الجسور"
|
||||
},
|
||||
"notifications": {
|
||||
"uploaded_sound": "صوت تمام الرفع",
|
||||
"sounds_section": "الأصوات",
|
||||
"notification_sound": "صوت الإشعار",
|
||||
"custom_sound_prompt": "تعيين صوت مخصص جديد",
|
||||
"browse_button": "تصفح"
|
||||
}
|
||||
},
|
||||
"encryption": {
|
||||
@ -1264,7 +1230,8 @@
|
||||
"other": "أظهر %(count)s زيادة"
|
||||
},
|
||||
"show_less": "أظهر أقل",
|
||||
"notification_options": "خيارات الإشعارات"
|
||||
"notification_options": "خيارات الإشعارات",
|
||||
"breadcrumbs_empty": "لا توجد غرف تمت زيارتها مؤخرًا"
|
||||
},
|
||||
"a11y": {
|
||||
"n_unread_messages_mentions": {
|
||||
@ -1275,7 +1242,9 @@
|
||||
"one": "رسالة واحدة غير مقروءة.",
|
||||
"other": "%(count)s من الرسائل غير مقروءة."
|
||||
},
|
||||
"unread_messages": "رسائل غير المقروءة."
|
||||
"unread_messages": "رسائل غير المقروءة.",
|
||||
"jump_first_invite": "الانتقال لأول دعوة.",
|
||||
"room_name": "الغرفة %(name)s"
|
||||
},
|
||||
"setting": {
|
||||
"help_about": {
|
||||
@ -1426,7 +1395,8 @@
|
||||
"lists_heading": "قوائم متشرك بها",
|
||||
"lists_description_1": "سيؤدي الاشتراك في قائمة الحظر إلى انضمامك إليها!",
|
||||
"lists_description_2": "إذا لم يكن هذا ما تريده ، فيرجى استخدام أداة مختلفة لتجاهل المستخدمين.",
|
||||
"lists_new_label": "معرف الغرفة أو عنوان قائمة الحظر"
|
||||
"lists_new_label": "معرف الغرفة أو عنوان قائمة الحظر",
|
||||
"rules_empty": "لا شيء"
|
||||
},
|
||||
"room": {
|
||||
"drop_file_prompt": "قم بإسقاط الملف هنا ليُرفَع",
|
||||
@ -1449,7 +1419,8 @@
|
||||
"favourite": "تفضيل",
|
||||
"low_priority": "أولوية منخفضة",
|
||||
"forget": "انسَ الغرفة"
|
||||
}
|
||||
},
|
||||
"invite_this_room": "ادع لهذه الغرفة"
|
||||
},
|
||||
"space": {
|
||||
"context_menu": {
|
||||
@ -1505,13 +1476,18 @@
|
||||
"enable_prompt_toast_description": "تمكين إشعارات سطح المكتب",
|
||||
"colour_none": "لا شيء",
|
||||
"colour_bold": "ثخين",
|
||||
"error_change_title": "تغيير إعدادات الإشعار"
|
||||
"error_change_title": "تغيير إعدادات الإشعار",
|
||||
"mark_all_read": "أشر عليها بأنها قرأت",
|
||||
"class_other": "أخرى",
|
||||
"default": "المبدئي",
|
||||
"all_messages": "كل الرسائل"
|
||||
},
|
||||
"error": {
|
||||
"admin_contact_short": "تواصل مع <a>مدير الخادم</a> الخاص بك.",
|
||||
"non_urgent_echo_failure_toast": "خادمك لا يتجاوب مع بعض <a>الطلبات</a>.",
|
||||
"failed_copy": "تعذر النسخ",
|
||||
"something_went_wrong": "هناك خطأ ما!"
|
||||
"something_went_wrong": "هناك خطأ ما!",
|
||||
"update_power_level": "تعذر تغيير مستوى القوة"
|
||||
},
|
||||
"room_summary_card_back_action_label": "معلومات الغرفة",
|
||||
"analytics": {
|
||||
@ -1520,5 +1496,42 @@
|
||||
"lightbox": {
|
||||
"rotate_left": "أدر لليسار",
|
||||
"rotate_right": "أدر لليمين"
|
||||
},
|
||||
"a11y_jump_first_unread_room": "الانتقال لأول غرفة لم تقرأ.",
|
||||
"integration_manager": {
|
||||
"error_connecting_heading": "لا يمكن الاتصال بمدير التكامل",
|
||||
"error_connecting": "مدري التكامل غير متصل بالإنرتنت أو لا يمكنه الوصول إلى خادمك الوسيط.",
|
||||
"use_im_default": "استخدم مدير التكامل <b>(%(serverName)s)</b> لإدارة البوتات وعناصر الواجهة وحزم الملصقات.",
|
||||
"use_im": "استخدم مدير التكامل لإدارة البوتات وعناصر الواجهة وحزم الملصقات.",
|
||||
"manage_title": "إدارة التكاملات",
|
||||
"explainer": "يتلقى مديرو التكامل بيانات الضبط، ويمكنهم تعديل عناصر واجهة المستخدم، وإرسال دعوات الغرف، وتعيين مستويات القوة نيابة عنك."
|
||||
},
|
||||
"identity_server": {
|
||||
"url_not_https": "يجب أن يستعمل رابط (URL) خادوم الهوية ميفاق HTTPS",
|
||||
"error_invalid": "ليس خادوم هوية صالح (رمز الحالة %(code)s)",
|
||||
"error_connection": "تعذر الاتصال بخادوم الهوية",
|
||||
"checking": "فحص خادم",
|
||||
"change": "تغيير خادم الهوية",
|
||||
"change_prompt": "انفصل عن خادم الهوية <current /> واتصل بآخر <new /> بدلاً منه؟",
|
||||
"error_invalid_or_terms": "شروط الخدمة لم تُقبل أو أن خادم الهوية مردود.",
|
||||
"no_terms": "خادم الهوية الذي اخترت ليس له شروط خدمة.",
|
||||
"disconnect": "افصل خادم الهوية",
|
||||
"disconnect_server": "انفصل عن خادم الهوية <idserver />؟",
|
||||
"disconnect_offline_warning": "لابد من محو <b>بيانات الشخصية</b> من خادم الهوية <idserver /> قبل الانفصال. لسوء الحظ ، خادم الهوية <idserver /> حاليًّا خارج الشبكة أو لا يمكن الوصول إليه.",
|
||||
"suggestions": "يجب عليك:",
|
||||
"suggestions_1": "تحقق من المكونات الإضافية للمتصفح الخاص بك بحثًا عن أي شيء قد يحظر خادم الهوية (مثل Privacy Badger)",
|
||||
"suggestions_2": "اتصل بمديري خادم الهوية <idserver />",
|
||||
"suggestions_3": "انتظر وعاوِد لاحقًا",
|
||||
"disconnect_anyway": "افصل على أي حال",
|
||||
"disconnect_personal_data_warning_1": "لا زالت <b>بياناتك الشخصية مشاعة</b> على خادم الهوية <idserver />.",
|
||||
"disconnect_personal_data_warning_2": "نوصي بإزالة عناوين البريد الإلكتروني وأرقام الهواتف من خادم الهوية قبل قطع الاتصال.",
|
||||
"url": "خادوم الهوية (%(server)s)",
|
||||
"description_connected": "أنت تستخدم حاليًا <server> </server> لاكتشاف جهات الاتصال الحالية التي تعرفها وتجعل نفسك قابلاً للاكتشاف. يمكنك تغيير خادم الهوية الخاص بك أدناه.",
|
||||
"change_server_prompt": "إذا كنت لا تريد استخدام <server /> لاكتشاف جهات الاتصال الموجودة التي تعرفها وتكون قابلاً للاكتشاف ، فأدخل خادم هوية آخر أدناه.",
|
||||
"description_disconnected": "أنت لا تستخدم حاليًا خادم هوية. لاكتشاف جهات الاتصال الحالية التي تعرفها وتكون قابلاً للاكتشاف ، أضف واحداً أدناه.",
|
||||
"disconnect_warning": "قطع الاتصال بخادم الهوية الخاص بك يعني أنك لن تكون قابلاً للاكتشاف من قبل المستخدمين الآخرين ولن تتمكن من دعوة الآخرين عبر البريد الإلكتروني أو الهاتف.",
|
||||
"description_optional": "استخدام خادم الهوية اختياري. إذا اخترت عدم استخدام خادم هوية ، فلن يتمكن المستخدمون الآخرون من اكتشافك ولن تتمكن من دعوة الآخرين عبر البريد الإلكتروني أو الهاتف.",
|
||||
"do_not_use": "لا تستخدم خادم هوية",
|
||||
"url_field_label": "أدخل خادم هوية جديدًا"
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user