Merge branch 'develop' into andybalaam/fix-receipt-flakes
This commit is contained in:
commit
d7d0f54e9b
184 changed files with 14091 additions and 12814 deletions
|
@ -293,7 +293,9 @@ function findRoomByName(room: string): Chainable<Room> {
|
||||||
export function openThread(rootMessage: string) {
|
export function openThread(rootMessage: string) {
|
||||||
cy.log("Open thread", rootMessage);
|
cy.log("Open thread", rootMessage);
|
||||||
cy.get(".mx_RoomView_body", { log: false }).within(() => {
|
cy.get(".mx_RoomView_body", { log: false }).within(() => {
|
||||||
cy.contains(".mx_EventTile[data-scroll-tokens]", rootMessage, { log: false })
|
cy.findAllByText(rootMessage)
|
||||||
|
.filter(".mx_EventTile_body")
|
||||||
|
.parents(".mx_EventTile[data-scroll-tokens]")
|
||||||
.realHover()
|
.realHover()
|
||||||
.findByRole("button", { name: "Reply in thread", log: false })
|
.findByRole("button", { name: "Reply in thread", log: false })
|
||||||
.click();
|
.click();
|
||||||
|
|
|
@ -84,7 +84,6 @@
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"filesize": "10.0.12",
|
"filesize": "10.0.12",
|
||||||
"focus-visible": "^5.2.0",
|
|
||||||
"gfm.css": "^1.1.2",
|
"gfm.css": "^1.1.2",
|
||||||
"glob-to-regexp": "^0.4.1",
|
"glob-to-regexp": "^0.4.1",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
|
|
|
@ -230,7 +230,7 @@ textarea:focus {
|
||||||
/* accessible (focusable) components. Not intended for buttons, but */
|
/* accessible (focusable) components. Not intended for buttons, but */
|
||||||
/* should be used on things like focusable containers where the outline */
|
/* should be used on things like focusable containers where the outline */
|
||||||
/* is usually not helping anyone. */
|
/* is usually not helping anyone. */
|
||||||
*:focus:not(.focus-visible) {
|
*:focus:not(:focus-visible) {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,7 +585,7 @@ legend {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
||||||
&:not(.focus-visible) {
|
&:not(:focus-visible) {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ limitations under the License.
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.focus-visible) {
|
&:not(:focus-visible) {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ limitations under the License.
|
||||||
--active-color: $slider-background-color;
|
--active-color: $slider-background-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus:not(.focus-visible) {
|
&:focus:not(:focus-visible) {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ limitations under the License.
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.focus-visible {
|
&:focus-visible {
|
||||||
& + label .mx_Checkbox_background {
|
& + label .mx_Checkbox_background {
|
||||||
@mixin unreal-focus;
|
@mixin unreal-focus;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.focus-visible {
|
&:focus-visible {
|
||||||
& + div {
|
& + div {
|
||||||
@mixin unreal-focus;
|
@mixin unreal-focus;
|
||||||
}
|
}
|
||||||
|
|
|
@ -224,7 +224,7 @@ $left-gutter: 64px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.focus-visible:focus-within,
|
&:focus-visible:focus-within,
|
||||||
&.mx_EventTile_actionBarFocused,
|
&.mx_EventTile_actionBarFocused,
|
||||||
&.mx_EventTile_isEditing,
|
&.mx_EventTile_isEditing,
|
||||||
&.mx_EventTile_selected {
|
&.mx_EventTile_selected {
|
||||||
|
@ -870,7 +870,7 @@ $left-gutter: 64px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
|
||||||
.mx_EventTile:hover &,
|
.mx_EventTile:hover &,
|
||||||
.mx_EventTile.focus-visible:focus-within & {
|
.mx_EventTile:focus-visible:focus-within & {
|
||||||
border: 1px solid $tertiary-content;
|
border: 1px solid $tertiary-content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -993,7 +993,7 @@ $left-gutter: 64px;
|
||||||
.mx_EventTile:hover .mx_MessageActionBar,
|
.mx_EventTile:hover .mx_MessageActionBar,
|
||||||
.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar,
|
.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar,
|
||||||
[data-whatinput="keyboard"] .mx_EventTile:focus-within .mx_MessageActionBar,
|
[data-whatinput="keyboard"] .mx_EventTile:focus-within .mx_MessageActionBar,
|
||||||
.mx_EventTile.focus-visible:focus-within .mx_MessageActionBar {
|
.mx_EventTile:focus-visible:focus-within .mx_MessageActionBar {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1002,7 +1002,7 @@ $left-gutter: 64px;
|
||||||
/* animation for when it's shown which means duplicating the style definition in */
|
/* animation for when it's shown which means duplicating the style definition in */
|
||||||
/* multiple places. */
|
/* multiple places. */
|
||||||
.mx_EventTile:not(:hover):not(.mx_EventTile_actionBarFocused):not([data-whatinput="keyboard"] :focus-within) {
|
.mx_EventTile:not(:hover):not(.mx_EventTile_actionBarFocused):not([data-whatinput="keyboard"] :focus-within) {
|
||||||
&:not(.focus-visible:focus-within) .mx_MessageActionBar .mx_Indicator {
|
&:not(:focus-visible:focus-within) .mx_MessageActionBar .mx_Indicator {
|
||||||
animation: none;
|
animation: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ limitations under the License.
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .mx_LinkPreviewGroup_hide img,
|
&:hover .mx_LinkPreviewGroup_hide img,
|
||||||
.mx_LinkPreviewGroup_hide.focus-visible:focus img {
|
.mx_LinkPreviewGroup_hide:focus-visible:focus img {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ limitations under the License.
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -8px;
|
top: -8px;
|
||||||
left: 10.5px;
|
left: 11px;
|
||||||
width: 4px;
|
width: 4px;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
|
|
@ -21,6 +21,7 @@ limitations under the License.
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
> .mx_StyledRadioButton {
|
> .mx_StyledRadioButton {
|
||||||
|
align-items: center;
|
||||||
padding: $font-16px;
|
padding: $font-16px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|
101
src/Lifecycle.ts
101
src/Lifecycle.ts
|
@ -501,11 +501,41 @@ export interface IStoredSession {
|
||||||
isUrl: string;
|
isUrl: string;
|
||||||
hasAccessToken: boolean;
|
hasAccessToken: boolean;
|
||||||
accessToken: string | IEncryptedPayload;
|
accessToken: string | IEncryptedPayload;
|
||||||
|
hasRefreshToken: boolean;
|
||||||
|
refreshToken?: string | IEncryptedPayload;
|
||||||
userId: string;
|
userId: string;
|
||||||
deviceId: string;
|
deviceId: string;
|
||||||
isGuest: boolean;
|
isGuest: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a token, as stored by `persistCredentials`
|
||||||
|
* Attempts to migrate token from localStorage to idb
|
||||||
|
* @param storageKey key used to store the token, eg ACCESS_TOKEN_STORAGE_KEY
|
||||||
|
* @returns Promise that resolves to token or undefined
|
||||||
|
*/
|
||||||
|
async function getStoredToken(storageKey: string): Promise<string | undefined> {
|
||||||
|
let token: string | undefined;
|
||||||
|
try {
|
||||||
|
token = await StorageManager.idbLoad("account", storageKey);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`StorageManager.idbLoad failed for account:${storageKey}`, e);
|
||||||
|
}
|
||||||
|
if (!token) {
|
||||||
|
token = localStorage.getItem(storageKey) ?? undefined;
|
||||||
|
if (token) {
|
||||||
|
try {
|
||||||
|
// try to migrate access token to IndexedDB if we can
|
||||||
|
await StorageManager.idbSave("account", storageKey, token);
|
||||||
|
localStorage.removeItem(storageKey);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`migration of token ${storageKey} to IndexedDB failed`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about the stored session from the browser's storage. The session
|
* Retrieves information about the stored session from the browser's storage. The session
|
||||||
* may not be valid, as it is not tested for consistency here.
|
* may not be valid, as it is not tested for consistency here.
|
||||||
|
@ -514,27 +544,14 @@ export interface IStoredSession {
|
||||||
export async function getStoredSessionVars(): Promise<Partial<IStoredSession>> {
|
export async function getStoredSessionVars(): Promise<Partial<IStoredSession>> {
|
||||||
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY) ?? undefined;
|
const hsUrl = localStorage.getItem(HOMESERVER_URL_KEY) ?? undefined;
|
||||||
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY) ?? undefined;
|
const isUrl = localStorage.getItem(ID_SERVER_URL_KEY) ?? undefined;
|
||||||
let accessToken: string | undefined;
|
|
||||||
try {
|
const accessToken = await getStoredToken(ACCESS_TOKEN_STORAGE_KEY);
|
||||||
accessToken = await StorageManager.idbLoad("account", ACCESS_TOKEN_STORAGE_KEY);
|
const refreshToken = await getStoredToken(REFRESH_TOKEN_STORAGE_KEY);
|
||||||
} catch (e) {
|
|
||||||
logger.error("StorageManager.idbLoad failed for account:mx_access_token", e);
|
|
||||||
}
|
|
||||||
if (!accessToken) {
|
|
||||||
accessToken = localStorage.getItem(ACCESS_TOKEN_STORAGE_KEY) ?? undefined;
|
|
||||||
if (accessToken) {
|
|
||||||
try {
|
|
||||||
// try to migrate access token to IndexedDB if we can
|
|
||||||
await StorageManager.idbSave("account", ACCESS_TOKEN_STORAGE_KEY, accessToken);
|
|
||||||
localStorage.removeItem(ACCESS_TOKEN_STORAGE_KEY);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("migration of access token to IndexedDB failed", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if we pre-date storing "mx_has_access_token", but we retrieved an access
|
// if we pre-date storing "mx_has_access_token", but we retrieved an access
|
||||||
// token, then we should say we have an access token
|
// token, then we should say we have an access token
|
||||||
const hasAccessToken = localStorage.getItem(HAS_ACCESS_TOKEN_STORAGE_KEY) === "true" || !!accessToken;
|
const hasAccessToken = localStorage.getItem(HAS_ACCESS_TOKEN_STORAGE_KEY) === "true" || !!accessToken;
|
||||||
|
const hasRefreshToken = localStorage.getItem(HAS_REFRESH_TOKEN_STORAGE_KEY) === "true" || !!refreshToken;
|
||||||
const userId = localStorage.getItem("mx_user_id") ?? undefined;
|
const userId = localStorage.getItem("mx_user_id") ?? undefined;
|
||||||
const deviceId = localStorage.getItem("mx_device_id") ?? undefined;
|
const deviceId = localStorage.getItem("mx_device_id") ?? undefined;
|
||||||
|
|
||||||
|
@ -546,7 +563,7 @@ export async function getStoredSessionVars(): Promise<Partial<IStoredSession>> {
|
||||||
isGuest = localStorage.getItem("matrix-is-guest") === "true";
|
isGuest = localStorage.getItem("matrix-is-guest") === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
return { hsUrl, isUrl, hasAccessToken, accessToken, userId, deviceId, isGuest };
|
return { hsUrl, isUrl, hasAccessToken, accessToken, refreshToken, hasRefreshToken, userId, deviceId, isGuest };
|
||||||
}
|
}
|
||||||
|
|
||||||
// The pickle key is a string of unspecified length and format. For AES, we
|
// The pickle key is a string of unspecified length and format. For AES, we
|
||||||
|
@ -585,6 +602,36 @@ async function abortLogin(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isEncryptedPayload = (token?: IEncryptedPayload | string | undefined): token is IEncryptedPayload => {
|
||||||
|
return !!token && typeof token !== "string";
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Try to decrypt a token retrieved from storage
|
||||||
|
* Where token is not encrypted (plain text) returns the plain text token
|
||||||
|
* Where token is encrypted, attempts decryption. Returns successfully decrypted token, else undefined.
|
||||||
|
* @param pickleKey pickle key used during encryption of token, or undefined
|
||||||
|
* @param token
|
||||||
|
* @param tokenIv initialization vector used during encryption of token eg ACCESS_TOKEN_IV
|
||||||
|
* @returns the decrypted token, or the plain text token. Returns undefined when token cannot be decrypted
|
||||||
|
*/
|
||||||
|
async function tryDecryptToken(
|
||||||
|
pickleKey: string | undefined,
|
||||||
|
token: IEncryptedPayload | string | undefined,
|
||||||
|
tokenIv: string,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
if (pickleKey && isEncryptedPayload(token)) {
|
||||||
|
const encrKey = await pickleKeyToAesKey(pickleKey);
|
||||||
|
const decryptedToken = await decryptAES(token, encrKey, tokenIv);
|
||||||
|
encrKey.fill(0);
|
||||||
|
return decryptedToken;
|
||||||
|
}
|
||||||
|
// if the token wasn't encrypted (plain string) just return it back
|
||||||
|
if (typeof token === "string") {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
// otherwise return undefined
|
||||||
|
}
|
||||||
|
|
||||||
// returns a promise which resolves to true if a session is found in
|
// returns a promise which resolves to true if a session is found in
|
||||||
// localstorage
|
// localstorage
|
||||||
//
|
//
|
||||||
|
@ -602,7 +649,8 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { hsUrl, isUrl, hasAccessToken, accessToken, userId, deviceId, isGuest } = await getStoredSessionVars();
|
const { hsUrl, isUrl, hasAccessToken, accessToken, refreshToken, userId, deviceId, isGuest } =
|
||||||
|
await getStoredSessionVars();
|
||||||
|
|
||||||
if (hasAccessToken && !accessToken) {
|
if (hasAccessToken && !accessToken) {
|
||||||
await abortLogin();
|
await abortLogin();
|
||||||
|
@ -614,18 +662,14 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let decryptedAccessToken = accessToken;
|
const pickleKey = (await PlatformPeg.get()?.getPickleKey(userId, deviceId ?? "")) ?? undefined;
|
||||||
const pickleKey = await PlatformPeg.get()?.getPickleKey(userId, deviceId ?? "");
|
|
||||||
if (pickleKey) {
|
if (pickleKey) {
|
||||||
logger.log("Got pickle key");
|
logger.log("Got pickle key");
|
||||||
if (typeof accessToken !== "string") {
|
|
||||||
const encrKey = await pickleKeyToAesKey(pickleKey);
|
|
||||||
decryptedAccessToken = await decryptAES(accessToken, encrKey, ACCESS_TOKEN_IV);
|
|
||||||
encrKey.fill(0);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
logger.log("No pickle key available");
|
logger.log("No pickle key available");
|
||||||
}
|
}
|
||||||
|
const decryptedAccessToken = await tryDecryptToken(pickleKey, accessToken, ACCESS_TOKEN_IV);
|
||||||
|
const decryptedRefreshToken = await tryDecryptToken(pickleKey, refreshToken, REFRESH_TOKEN_IV);
|
||||||
|
|
||||||
const freshLogin = sessionStorage.getItem("mx_fresh_login") === "true";
|
const freshLogin = sessionStorage.getItem("mx_fresh_login") === "true";
|
||||||
sessionStorage.removeItem("mx_fresh_login");
|
sessionStorage.removeItem("mx_fresh_login");
|
||||||
|
@ -635,7 +679,8 @@ export async function restoreFromLocalStorage(opts?: { ignoreGuest?: boolean }):
|
||||||
{
|
{
|
||||||
userId: userId,
|
userId: userId,
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
accessToken: decryptedAccessToken as string,
|
accessToken: decryptedAccessToken!,
|
||||||
|
refreshToken: decryptedRefreshToken,
|
||||||
homeserverUrl: hsUrl,
|
homeserverUrl: hsUrl,
|
||||||
identityServerUrl: isUrl,
|
identityServerUrl: isUrl,
|
||||||
guest: isGuest,
|
guest: isGuest,
|
||||||
|
|
|
@ -130,7 +130,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
||||||
private renderPhaseDone(): JSX.Element {
|
private renderPhaseDone(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{_t("Your keys are being backed up (the first backup could take a few minutes).")}</p>
|
<p>{_t("settings|key_backup|backup_in_progress")}</p>
|
||||||
<DialogButtons primaryButton={_t("action|ok")} onPrimaryButtonClick={this.onDone} hasCancel={false} />
|
<DialogButtons primaryButton={_t("action|ok")} onPrimaryButtonClick={this.onDone} hasCancel={false} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -139,11 +139,11 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
||||||
private titleForPhase(phase: Phase): string {
|
private titleForPhase(phase: Phase): string {
|
||||||
switch (phase) {
|
switch (phase) {
|
||||||
case Phase.BackingUp:
|
case Phase.BackingUp:
|
||||||
return _t("Starting backup…");
|
return _t("settings|key_backup|backup_starting");
|
||||||
case Phase.Done:
|
case Phase.Done:
|
||||||
return _t("Success!");
|
return _t("settings|key_backup|backup_success");
|
||||||
default:
|
default:
|
||||||
return _t("Create key backup");
|
return _t("settings|key_backup|create_title");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ export default class CreateKeyBackupDialog extends React.PureComponent<IProps, I
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
content = (
|
content = (
|
||||||
<div>
|
<div>
|
||||||
<p>{_t("Unable to create key backup")}</p>
|
<p>{_t("settings|key_backup|cannot_create_backup")}</p>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("action|retry")}
|
primaryButton={_t("action|retry")}
|
||||||
onPrimaryButtonClick={this.createBackup}
|
onPrimaryButtonClick={this.createBackup}
|
||||||
|
|
|
@ -541,13 +541,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
>
|
>
|
||||||
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
||||||
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_secureBackup" />
|
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_secureBackup" />
|
||||||
{_t("Generate a Security Key")}
|
{_t("settings|key_backup|setup_secure_backup|generate_security_key_title")}
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{_t(
|
|
||||||
"We'll generate a Security Key for you to store somewhere safe, like a password manager or a safe.",
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>{_t("settings|key_backup|setup_secure_backup|generate_security_key_description")}</div>
|
||||||
</StyledRadioButton>
|
</StyledRadioButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -564,11 +560,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
>
|
>
|
||||||
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
<div className="mx_CreateSecretStorageDialog_optionTitle">
|
||||||
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_securePhrase" />
|
<span className="mx_CreateSecretStorageDialog_optionIcon mx_CreateSecretStorageDialog_optionIcon_securePhrase" />
|
||||||
{_t("Enter a Security Phrase")}
|
{_t("settings|key_backup|setup_secure_backup|enter_phrase_title")}
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{_t("Use a secret phrase only you know, and optionally save a Security Key to use for backup.")}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div>{_t("settings|key_backup|setup_secure_backup|use_phrase_only_you_know")}</div>
|
||||||
</StyledRadioButton>
|
</StyledRadioButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -583,9 +577,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
return (
|
return (
|
||||||
<form onSubmit={this.onChooseKeyPassphraseFormSubmit}>
|
<form onSubmit={this.onChooseKeyPassphraseFormSubmit}>
|
||||||
<p className="mx_CreateSecretStorageDialog_centeredBody">
|
<p className="mx_CreateSecretStorageDialog_centeredBody">
|
||||||
{_t(
|
{_t("settings|key_backup|setup_secure_backup|description")}
|
||||||
"Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.",
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
<div className="mx_CreateSecretStorageDialog_primaryContainer" role="radiogroup">
|
<div className="mx_CreateSecretStorageDialog_primaryContainer" role="radiogroup">
|
||||||
{optionKey}
|
{optionKey}
|
||||||
|
@ -607,7 +599,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
if (this.state.canUploadKeysWithPasswordOnly) {
|
if (this.state.canUploadKeysWithPasswordOnly) {
|
||||||
authPrompt = (
|
authPrompt = (
|
||||||
<div>
|
<div>
|
||||||
<div>{_t("Enter your account password to confirm the upgrade:")}</div>
|
<div>{_t("settings|key_backup|setup_secure_backup|requires_password_confirmation")}</div>
|
||||||
<div>
|
<div>
|
||||||
<Field
|
<Field
|
||||||
id="mx_CreateSecretStorageDialog_password"
|
id="mx_CreateSecretStorageDialog_password"
|
||||||
|
@ -624,21 +616,17 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
} else if (!this.state.backupTrustInfo?.trusted) {
|
} else if (!this.state.backupTrustInfo?.trusted) {
|
||||||
authPrompt = (
|
authPrompt = (
|
||||||
<div>
|
<div>
|
||||||
<div>{_t("Restore your key backup to upgrade your encryption")}</div>
|
<div>{_t("settings|key_backup|setup_secure_backup|requires_key_restore")}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
nextCaption = _t("action|restore");
|
nextCaption = _t("action|restore");
|
||||||
} else {
|
} else {
|
||||||
authPrompt = <p>{_t("You'll need to authenticate with the server to confirm the upgrade.")}</p>;
|
authPrompt = <p>{_t("settings|key_backup|setup_secure_backup|requires_server_authentication")}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={this.onMigrateFormSubmit}>
|
<form onSubmit={this.onMigrateFormSubmit}>
|
||||||
<p>
|
<p>{_t("settings|key_backup|setup_secure_backup|session_upgrade_description")}</p>
|
||||||
{_t(
|
|
||||||
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div>{authPrompt}</div>
|
<div>{authPrompt}</div>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={nextCaption}
|
primaryButton={nextCaption}
|
||||||
|
@ -657,11 +645,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
private renderPhasePassPhrase(): JSX.Element {
|
private renderPhasePassPhrase(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<form onSubmit={this.onPassPhraseNextClick}>
|
<form onSubmit={this.onPassPhraseNextClick}>
|
||||||
<p>
|
<p>{_t("settings|key_backup|setup_secure_backup|enter_phrase_description")}</p>
|
||||||
{_t(
|
|
||||||
"Enter a Security Phrase only you know, as it's used to safeguard your data. To be secure, you shouldn't re-use your account password.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||||
<PassphraseField
|
<PassphraseField
|
||||||
|
@ -672,10 +656,10 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
onValidate={this.onPassPhraseValidate}
|
onValidate={this.onPassPhraseValidate}
|
||||||
fieldRef={this.passphraseField}
|
fieldRef={this.passphraseField}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
label={_td("Enter a Security Phrase")}
|
label={_td("settings|key_backup|setup_secure_backup|enter_phrase_title")}
|
||||||
labelEnterPassword={_td("Enter a Security Phrase")}
|
labelEnterPassword={_td("settings|key_backup|setup_secure_backup|enter_phrase_title")}
|
||||||
labelStrongPassword={_td("Great! This Security Phrase looks strong enough.")}
|
labelStrongPassword={_td("settings|key_backup|setup_secure_backup|phrase_strong_enough")}
|
||||||
labelAllowedButUnsafe={_td("Great! This Security Phrase looks strong enough.")}
|
labelAllowedButUnsafe={_td("settings|key_backup|setup_secure_backup|phrase_strong_enough")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -697,8 +681,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
let matchText;
|
let matchText;
|
||||||
let changeText;
|
let changeText;
|
||||||
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
if (this.state.passPhraseConfirm === this.state.passPhrase) {
|
||||||
matchText = _t("That matches!");
|
matchText = _t("settings|key_backup|setup_secure_backup|pass_phrase_match_success");
|
||||||
changeText = _t("Use a different passphrase?");
|
changeText = _t("settings|key_backup|setup_secure_backup|use_different_passphrase");
|
||||||
} else if (!this.state.passPhrase.startsWith(this.state.passPhraseConfirm)) {
|
} else if (!this.state.passPhrase.startsWith(this.state.passPhraseConfirm)) {
|
||||||
// only tell them they're wrong if they've actually gone wrong.
|
// only tell them they're wrong if they've actually gone wrong.
|
||||||
// Security conscious readers will note that if you left element-web unattended
|
// Security conscious readers will note that if you left element-web unattended
|
||||||
|
@ -707,8 +691,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
// just opening the browser's developer tools and reading it.
|
// just opening the browser's developer tools and reading it.
|
||||||
// Note that not having typed anything at all will not hit this clause and
|
// Note that not having typed anything at all will not hit this clause and
|
||||||
// fall through so empty box === no hint.
|
// fall through so empty box === no hint.
|
||||||
matchText = _t("That doesn't match.");
|
matchText = _t("settings|key_backup|setup_secure_backup|pass_phrase_match_failed");
|
||||||
changeText = _t("Go back to set it again.");
|
changeText = _t("settings|key_backup|setup_secure_backup|set_phrase_again");
|
||||||
}
|
}
|
||||||
|
|
||||||
let passPhraseMatch: JSX.Element | undefined;
|
let passPhraseMatch: JSX.Element | undefined;
|
||||||
|
@ -724,14 +708,14 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<form onSubmit={this.onPassPhraseConfirmNextClick}>
|
<form onSubmit={this.onPassPhraseConfirmNextClick}>
|
||||||
<p>{_t("Enter your Security Phrase a second time to confirm it.")}</p>
|
<p>{_t("settings|key_backup|setup_secure_backup|enter_phrase_to_confirm")}</p>
|
||||||
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
<div className="mx_CreateSecretStorageDialog_passPhraseContainer">
|
||||||
<Field
|
<Field
|
||||||
type="password"
|
type="password"
|
||||||
onChange={this.onPassPhraseConfirmChange}
|
onChange={this.onPassPhraseConfirmChange}
|
||||||
value={this.state.passPhraseConfirm}
|
value={this.state.passPhraseConfirm}
|
||||||
className="mx_CreateSecretStorageDialog_passPhraseField"
|
className="mx_CreateSecretStorageDialog_passPhraseField"
|
||||||
label={_t("Confirm your Security Phrase")}
|
label={_t("settings|key_backup|setup_secure_backup|confirm_security_phrase")}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
/>
|
/>
|
||||||
|
@ -772,11 +756,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>{_t("settings|key_backup|setup_secure_backup|security_key_safety_reminder")}</p>
|
||||||
{_t(
|
|
||||||
"Store your Security Key somewhere safe, like a password manager or a safe, as it's used to safeguard your encrypted data.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div className="mx_CreateSecretStorageDialog_primaryContainer mx_CreateSecretStorageDialog_recoveryKeyPrimarycontainer">
|
<div className="mx_CreateSecretStorageDialog_primaryContainer mx_CreateSecretStorageDialog_recoveryKeyPrimarycontainer">
|
||||||
<div className="mx_CreateSecretStorageDialog_recoveryKeyContainer">
|
<div className="mx_CreateSecretStorageDialog_recoveryKeyContainer">
|
||||||
<div className="mx_CreateSecretStorageDialog_recoveryKey">
|
<div className="mx_CreateSecretStorageDialog_recoveryKey">
|
||||||
|
@ -792,7 +772,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
{_t("action|download")}
|
{_t("action|download")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<span>
|
<span>
|
||||||
{_t("%(downloadButton)s or %(copyButton)s", {
|
{_t("settings|key_backup|setup_secure_backup|download_or_copy", {
|
||||||
downloadButton: "",
|
downloadButton: "",
|
||||||
copyButton: "",
|
copyButton: "",
|
||||||
})}
|
})}
|
||||||
|
@ -824,7 +804,9 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
private renderStoredPhase(): JSX.Element {
|
private renderStoredPhase(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className="mx_Dialog_content">{_t("Your keys are now being backed up from this device.")}</p>
|
<p className="mx_Dialog_content">
|
||||||
|
{_t("settings|key_backup|setup_secure_backup|backup_setup_success_description")}
|
||||||
|
</p>
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("action|done")}
|
primaryButton={_t("action|done")}
|
||||||
onPrimaryButtonClick={() => this.props.onFinished(true)}
|
onPrimaryButtonClick={() => this.props.onFinished(true)}
|
||||||
|
@ -837,7 +819,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
private renderPhaseLoadError(): JSX.Element {
|
private renderPhaseLoadError(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{_t("Unable to query secret storage status")}</p>
|
<p>{_t("settings|key_backup|setup_secure_backup|secret_storage_query_failure")}</p>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("action|retry")}
|
primaryButton={_t("action|retry")}
|
||||||
|
@ -853,10 +835,8 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
private renderPhaseSkipConfirm(): JSX.Element {
|
private renderPhaseSkipConfirm(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>{_t("settings|key_backup|setup_secure_backup|cancel_warning")}</p>
|
||||||
{_t("If you cancel now, you may lose encrypted messages & data if you lose access to your logins.")}
|
<p>{_t("settings|key_backup|setup_secure_backup|settings_reminder")}</p>
|
||||||
</p>
|
|
||||||
<p>{_t("You can also set up Secure Backup & manage your keys in Settings.")}</p>
|
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("action|go_back")}
|
primaryButton={_t("action|go_back")}
|
||||||
onPrimaryButtonClick={this.onGoBackClick}
|
onPrimaryButtonClick={this.onGoBackClick}
|
||||||
|
@ -875,19 +855,19 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
case Phase.ChooseKeyPassphrase:
|
case Phase.ChooseKeyPassphrase:
|
||||||
return _t("encryption|set_up_toast_title");
|
return _t("encryption|set_up_toast_title");
|
||||||
case Phase.Migrate:
|
case Phase.Migrate:
|
||||||
return _t("Upgrade your encryption");
|
return _t("settings|key_backup|setup_secure_backup|title_upgrade_encryption");
|
||||||
case Phase.Passphrase:
|
case Phase.Passphrase:
|
||||||
return _t("Set a Security Phrase");
|
return _t("settings|key_backup|setup_secure_backup|title_set_phrase");
|
||||||
case Phase.PassphraseConfirm:
|
case Phase.PassphraseConfirm:
|
||||||
return _t("Confirm Security Phrase");
|
return _t("settings|key_backup|setup_secure_backup|title_confirm_phrase");
|
||||||
case Phase.ConfirmSkip:
|
case Phase.ConfirmSkip:
|
||||||
return _t("Are you sure?");
|
return _t("Are you sure?");
|
||||||
case Phase.ShowKey:
|
case Phase.ShowKey:
|
||||||
return _t("Save your Security Key");
|
return _t("settings|key_backup|setup_secure_backup|title_save_key");
|
||||||
case Phase.Storing:
|
case Phase.Storing:
|
||||||
return _t("encryption|bootstrap_title");
|
return _t("encryption|bootstrap_title");
|
||||||
case Phase.Stored:
|
case Phase.Stored:
|
||||||
return _t("Secure Backup successful");
|
return _t("settings|key_backup|setup_secure_backup|backup_setup_success_title");
|
||||||
default:
|
default:
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -912,7 +892,7 @@ export default class CreateSecretStorageDialog extends React.PureComponent<IProp
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
content = (
|
content = (
|
||||||
<div>
|
<div>
|
||||||
<p>{_t("Unable to set up secret storage")}</p>
|
<p>{_t("settings|key_backup|setup_secure_backup|unable_to_setup")}</p>
|
||||||
<div className="mx_Dialog_buttons">
|
<div className="mx_Dialog_buttons">
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("action|retry")}
|
primaryButton={_t("action|retry")}
|
||||||
|
|
|
@ -158,29 +158,21 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
className="mx_exportE2eKeysDialog"
|
className="mx_exportE2eKeysDialog"
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={_t("Export room keys")}
|
title={_t("settings|key_export_import|export_title")}
|
||||||
>
|
>
|
||||||
<form onSubmit={this.onPassphraseFormSubmit}>
|
<form onSubmit={this.onPassphraseFormSubmit}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<p>
|
<p>{_t("settings|key_export_import|export_description_1")}</p>
|
||||||
{_t(
|
<p>{_t("settings|key_export_import|export_description_2")}</p>
|
||||||
"This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{_t(
|
|
||||||
"The exported file will allow anyone who can read it to decrypt any encrypted messages that you can see, so you should be careful to keep it secure. To help with this, you should enter a unique passphrase below, which will only be used to encrypt the exported data. It will only be possible to import the data by using the same passphrase.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div className="error">{this.state.errStr}</div>
|
<div className="error">{this.state.errStr}</div>
|
||||||
<div className="mx_E2eKeysDialog_inputTable">
|
<div className="mx_E2eKeysDialog_inputTable">
|
||||||
<div className="mx_E2eKeysDialog_inputRow">
|
<div className="mx_E2eKeysDialog_inputRow">
|
||||||
<PassphraseField
|
<PassphraseField
|
||||||
minScore={3}
|
minScore={3}
|
||||||
label={_td("Enter passphrase")}
|
label={_td("settings|key_export_import|enter_passphrase")}
|
||||||
labelEnterPassword={_td("Enter passphrase")}
|
labelEnterPassword={_td("settings|key_export_import|enter_passphrase")}
|
||||||
labelStrongPassword={_td("Great! This passphrase looks strong enough")}
|
labelStrongPassword={_td("settings|key_export_import|phrase_strong_enough")}
|
||||||
labelAllowedButUnsafe={_td("Great! This passphrase looks strong enough")}
|
labelAllowedButUnsafe={_td("settings|key_export_import|phrase_strong_enough")}
|
||||||
value={this.state.passphrase1}
|
value={this.state.passphrase1}
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
this.onPassphraseChange(e, "passphrase1")
|
this.onPassphraseChange(e, "passphrase1")
|
||||||
|
@ -196,9 +188,9 @@ export default class ExportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
<div className="mx_E2eKeysDialog_inputRow">
|
<div className="mx_E2eKeysDialog_inputRow">
|
||||||
<PassphraseConfirmField
|
<PassphraseConfirmField
|
||||||
password={this.state.passphrase1}
|
password={this.state.passphrase1}
|
||||||
label={_td("Confirm passphrase")}
|
label={_td("settings|key_export_import|confirm_passphrase")}
|
||||||
labelRequired={_td("Passphrase must not be empty")}
|
labelRequired={_td("settings|key_export_import|phrase_cannot_be_empty")}
|
||||||
labelInvalid={_td("Passphrases must match")}
|
labelInvalid={_td("settings|key_export_import|phrase_must_match")}
|
||||||
value={this.state.passphrase2}
|
value={this.state.passphrase2}
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
this.onPassphraseChange(e, "passphrase2")
|
this.onPassphraseChange(e, "passphrase2")
|
||||||
|
|
|
@ -140,25 +140,19 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
className="mx_importE2eKeysDialog"
|
className="mx_importE2eKeysDialog"
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
title={_t("Import room keys")}
|
title={_t("settings|key_export_import|import_title")}
|
||||||
>
|
>
|
||||||
<form onSubmit={this.onFormSubmit}>
|
<form onSubmit={this.onFormSubmit}>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
<p>
|
<p>{_t("settings|key_export_import|import_description_1")}</p>
|
||||||
{_t(
|
<p>{_t("settings|key_export_import|import_description_2")}</p>
|
||||||
"This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{_t(
|
|
||||||
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div className="error">{this.state.errStr}</div>
|
<div className="error">{this.state.errStr}</div>
|
||||||
<div className="mx_E2eKeysDialog_inputTable">
|
<div className="mx_E2eKeysDialog_inputTable">
|
||||||
<div className="mx_E2eKeysDialog_inputRow">
|
<div className="mx_E2eKeysDialog_inputRow">
|
||||||
<div className="mx_E2eKeysDialog_inputLabel">
|
<div className="mx_E2eKeysDialog_inputLabel">
|
||||||
<label htmlFor="importFile">{_t("File to import")}</label>
|
<label htmlFor="importFile">
|
||||||
|
{_t("settings|key_export_import|file_to_import")}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_E2eKeysDialog_inputCell">
|
<div className="mx_E2eKeysDialog_inputCell">
|
||||||
<input
|
<input
|
||||||
|
@ -173,7 +167,7 @@ export default class ImportE2eKeysDialog extends React.Component<IProps, IState>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_E2eKeysDialog_inputRow">
|
<div className="mx_E2eKeysDialog_inputRow">
|
||||||
<Field
|
<Field
|
||||||
label={_t("Enter passphrase")}
|
label={_t("settings|key_export_import|enter_passphrase")}
|
||||||
value={this.state.passphrase}
|
value={this.state.passphrase}
|
||||||
onChange={this.onPassphraseChange}
|
onChange={this.onPassphraseChange}
|
||||||
size={64}
|
size={64}
|
||||||
|
|
|
@ -55,29 +55,27 @@ export default class NewRecoveryMethodDialog extends React.PureComponent<IProps>
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("New Recovery Method")}</span>;
|
const title = (
|
||||||
|
<span className="mx_KeyBackupFailedDialog_title">
|
||||||
const newMethodDetected = <p>{_t("A new Security Phrase and key for Secure Messages have been detected.")}</p>;
|
{_t("encryption|new_recovery_method_detected|title")}
|
||||||
|
</span>
|
||||||
const hackWarning = (
|
|
||||||
<p className="warning">
|
|
||||||
{_t(
|
|
||||||
"If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const newMethodDetected = <p>{_t("encryption|new_recovery_method_detected|description_1")}</p>;
|
||||||
|
|
||||||
|
const hackWarning = <p className="warning">{_t("encryption|new_recovery_method_detected|warning")}</p>;
|
||||||
|
|
||||||
let content: JSX.Element | undefined;
|
let content: JSX.Element | undefined;
|
||||||
if (MatrixClientPeg.safeGet().getKeyBackupEnabled()) {
|
if (MatrixClientPeg.safeGet().getKeyBackupEnabled()) {
|
||||||
content = (
|
content = (
|
||||||
<div>
|
<div>
|
||||||
{newMethodDetected}
|
{newMethodDetected}
|
||||||
<p>{_t("This session is encrypting history using the new recovery method.")}</p>
|
<p>{_t("encryption|new_recovery_method_detected|description_2")}</p>
|
||||||
{hackWarning}
|
{hackWarning}
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("action|ok")}
|
primaryButton={_t("action|ok")}
|
||||||
onPrimaryButtonClick={this.onOkClick}
|
onPrimaryButtonClick={this.onOkClick}
|
||||||
cancelButton={_t("Go to Settings")}
|
cancelButton={_t("common|go_to_settings")}
|
||||||
onCancel={this.onGoToSettingsClick}
|
onCancel={this.onGoToSettingsClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,9 +86,9 @@ export default class NewRecoveryMethodDialog extends React.PureComponent<IProps>
|
||||||
{newMethodDetected}
|
{newMethodDetected}
|
||||||
{hackWarning}
|
{hackWarning}
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("Set up Secure Messages")}
|
primaryButton={_t("common|setup_secure_messages")}
|
||||||
onPrimaryButtonClick={this.onSetupClick}
|
onPrimaryButtonClick={this.onSetupClick}
|
||||||
cancelButton={_t("Go to Settings")}
|
cancelButton={_t("common|go_to_settings")}
|
||||||
onCancel={this.onGoToSettingsClick}
|
onCancel={this.onGoToSettingsClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -46,30 +46,20 @@ export default class RecoveryMethodRemovedDialog extends React.PureComponent<IPr
|
||||||
};
|
};
|
||||||
|
|
||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const title = <span className="mx_KeyBackupFailedDialog_title">{_t("Recovery Method Removed")}</span>;
|
const title = (
|
||||||
|
<span className="mx_KeyBackupFailedDialog_title">{_t("encryption|recovery_method_removed|title")}</span>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseDialog className="mx_KeyBackupFailedDialog" onFinished={this.props.onFinished} title={title}>
|
<BaseDialog className="mx_KeyBackupFailedDialog" onFinished={this.props.onFinished} title={title}>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>{_t("encryption|recovery_method_removed|description_1")}</p>
|
||||||
{_t(
|
<p>{_t("encryption|recovery_method_removed|description_2")}</p>
|
||||||
"This session has detected that your Security Phrase and key for Secure Messages have been removed.",
|
<p className="warning">{_t("encryption|recovery_method_removed|warning")}</p>
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{_t(
|
|
||||||
"If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p className="warning">
|
|
||||||
{_t(
|
|
||||||
"If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<DialogButtons
|
<DialogButtons
|
||||||
primaryButton={_t("Set up Secure Messages")}
|
primaryButton={_t("common|setup_secure_messages")}
|
||||||
onPrimaryButtonClick={this.onSetupClick}
|
onPrimaryButtonClick={this.onSetupClick}
|
||||||
cancelButton={_t("Go to Settings")}
|
cancelButton={_t("common|go_to_settings")}
|
||||||
onCancel={this.onGoToSettingsClick}
|
onCancel={this.onGoToSettingsClick}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -134,7 +134,7 @@ export default class RoomProvider extends AutocompleteProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getName(): string {
|
public getName(): string {
|
||||||
return _t("Rooms");
|
return _t("common|rooms");
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
public renderCompletions(completions: React.ReactNode[]): React.ReactNode {
|
||||||
|
|
|
@ -345,7 +345,7 @@ export default class LeftPanel extends React.Component<IProps, IState> {
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_LeftPanel_exploreButton"
|
className="mx_LeftPanel_exploreButton"
|
||||||
onClick={this.onExplore}
|
onClick={this.onExplore}
|
||||||
title={_t("Explore rooms")}
|
title={_t("action|explore_rooms")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,6 @@ import { CryptoEvent } from "matrix-js-sdk/src/crypto";
|
||||||
import { DecryptionError } from "matrix-js-sdk/src/crypto/algorithms";
|
import { DecryptionError } from "matrix-js-sdk/src/crypto/algorithms";
|
||||||
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
import { IKeyBackupInfo } from "matrix-js-sdk/src/crypto/keybackup";
|
||||||
|
|
||||||
// focus-visible is a Polyfill for the :focus-visible CSS pseudo-attribute used by various components
|
|
||||||
import "focus-visible";
|
|
||||||
// what-input helps improve keyboard accessibility
|
// what-input helps improve keyboard accessibility
|
||||||
import "what-input";
|
import "what-input";
|
||||||
|
|
||||||
|
|
|
@ -384,7 +384,7 @@ export const showRoom = (cli: MatrixClient, hierarchy: RoomHierarchy, roomId: st
|
||||||
oob_data: {
|
oob_data: {
|
||||||
avatarUrl: room?.avatar_url,
|
avatarUrl: room?.avatar_url,
|
||||||
// XXX: This logic is duplicated from the JS SDK which would normally decide what the name is.
|
// 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,
|
roomType,
|
||||||
} as IOOBData,
|
} as IOOBData,
|
||||||
metricsTrigger: "RoomDirectory",
|
metricsTrigger: "RoomDirectory",
|
||||||
|
|
|
@ -124,7 +124,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
||||||
{canCreateRoom && (
|
{canCreateRoom && (
|
||||||
<>
|
<>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("New room")}
|
label={_t("action|new_room")}
|
||||||
iconClassName="mx_RoomList_iconNewRoom"
|
iconClassName="mx_RoomList_iconNewRoom"
|
||||||
onClick={async (e): Promise<void> => {
|
onClick={async (e): Promise<void> => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -139,7 +139,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
||||||
/>
|
/>
|
||||||
{videoRoomsEnabled && (
|
{videoRoomsEnabled && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("New video room")}
|
label={_t("action|new_video_room")}
|
||||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
iconClassName="mx_RoomList_iconNewVideoRoom"
|
||||||
onClick={async (e): Promise<void> => {
|
onClick={async (e): Promise<void> => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -164,7 +164,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Add existing room")}
|
label={_t("action|add_existing_room")}
|
||||||
iconClassName="mx_RoomList_iconAddExistingRoom"
|
iconClassName="mx_RoomList_iconAddExistingRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -175,7 +175,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
|
||||||
/>
|
/>
|
||||||
{canCreateSpace && (
|
{canCreateSpace && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Add space")}
|
label={_t("room_list|add_space_label")}
|
||||||
iconClassName="mx_RoomList_iconPlus"
|
iconClassName="mx_RoomList_iconPlus"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
|
@ -29,7 +29,7 @@ export function SessionLockStolenView(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<SplashPage className="mx_SessionLockStolenView">
|
<SplashPage className="mx_SessionLockStolenView">
|
||||||
<h1>{_t("common|error")}</h1>
|
<h1>{_t("common|error")}</h1>
|
||||||
<h2>{_t("%(brand)s has been opened in another tab.", { brand })}</h2>
|
<h2>{_t("error_app_open_in_another_tab", { brand })}</h2>
|
||||||
</SplashPage>
|
</SplashPage>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,15 +159,11 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
||||||
if (lostKeys) {
|
if (lostKeys) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>{_t("encryption|verification|no_key_or_device")}</p>
|
||||||
{_t(
|
|
||||||
"It looks like you don't have a Security Key or any other devices you can verify against. This device will not be able to access old encrypted messages. In order to verify your identity on this device, you'll need to reset your verification keys.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mx_CompleteSecurity_actionRow">
|
<div className="mx_CompleteSecurity_actionRow">
|
||||||
<AccessibleButton kind="primary" onClick={this.onResetConfirmClick}>
|
<AccessibleButton kind="primary" onClick={this.onResetConfirmClick}>
|
||||||
{_t("Proceed with reset")}
|
{_t("encryption|verification|reset_proceed_prompt")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -176,9 +172,9 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
||||||
const store = SetupEncryptionStore.sharedInstance();
|
const store = SetupEncryptionStore.sharedInstance();
|
||||||
let recoveryKeyPrompt;
|
let recoveryKeyPrompt;
|
||||||
if (store.keyInfo && keyHasPassphrase(store.keyInfo)) {
|
if (store.keyInfo && keyHasPassphrase(store.keyInfo)) {
|
||||||
recoveryKeyPrompt = _t("Verify with Security Key or Phrase");
|
recoveryKeyPrompt = _t("encryption|verification|verify_using_key_or_phrase");
|
||||||
} else if (store.keyInfo) {
|
} else if (store.keyInfo) {
|
||||||
recoveryKeyPrompt = _t("Verify with Security Key");
|
recoveryKeyPrompt = _t("encryption|verification|verify_using_key");
|
||||||
}
|
}
|
||||||
|
|
||||||
let useRecoveryKeyButton;
|
let useRecoveryKeyButton;
|
||||||
|
@ -194,16 +190,14 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
||||||
if (store.hasDevicesToVerifyAgainst) {
|
if (store.hasDevicesToVerifyAgainst) {
|
||||||
verifyButton = (
|
verifyButton = (
|
||||||
<AccessibleButton kind="primary" onClick={this.onVerifyClick}>
|
<AccessibleButton kind="primary" onClick={this.onVerifyClick}>
|
||||||
{_t("Verify with another device")}
|
{_t("encryption|verification|verify_using_device")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>{_t("encryption|verification|verification_description")}</p>
|
||||||
{_t("Verify your identity to access encrypted messages and prove your identity to others.")}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mx_CompleteSecurity_actionRow">
|
<div className="mx_CompleteSecurity_actionRow">
|
||||||
{verifyButton}
|
{verifyButton}
|
||||||
|
@ -228,15 +222,9 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
||||||
} else if (phase === Phase.Done) {
|
} else if (phase === Phase.Done) {
|
||||||
let message: JSX.Element;
|
let message: JSX.Element;
|
||||||
if (this.state.backupInfo) {
|
if (this.state.backupInfo) {
|
||||||
message = (
|
message = <p>{_t("encryption|verification|verification_success_with_backup")}</p>;
|
||||||
<p>
|
|
||||||
{_t(
|
|
||||||
"Your new device is now verified. It has access to your encrypted messages, and other users will see it as trusted.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
message = <p>{_t("Your new device is now verified. Other users will see it as trusted.")}</p>;
|
message = <p>{_t("encryption|verification|verification_success_without_backup")}</p>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -252,14 +240,10 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
||||||
} else if (phase === Phase.ConfirmSkip) {
|
} else if (phase === Phase.ConfirmSkip) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>{_t("encryption|verification|verification_skip_warning")}</p>
|
||||||
{_t(
|
|
||||||
"Without verifying, you won't have access to all your messages and may appear as untrusted to others.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div className="mx_CompleteSecurity_actionRow">
|
<div className="mx_CompleteSecurity_actionRow">
|
||||||
<AccessibleButton kind="danger_outline" onClick={this.onSkipConfirmClick}>
|
<AccessibleButton kind="danger_outline" onClick={this.onSkipConfirmClick}>
|
||||||
{_t("I'll verify later")}
|
{_t("encryption|verification|verify_later")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<AccessibleButton kind="primary" onClick={this.onSkipBackClick}>
|
<AccessibleButton kind="primary" onClick={this.onSkipBackClick}>
|
||||||
{_t("action|go_back")}
|
{_t("action|go_back")}
|
||||||
|
@ -270,20 +254,12 @@ export default class SetupEncryptionBody extends React.Component<IProps, IState>
|
||||||
} else if (phase === Phase.ConfirmReset) {
|
} else if (phase === Phase.ConfirmReset) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>{_t("encryption|verification|verify_reset_warning_1")}</p>
|
||||||
{_t(
|
<p>{_t("encryption|verification|verify_reset_warning_2")}</p>
|
||||||
"Resetting your verification keys cannot be undone. After resetting, you won't have access to old encrypted messages, and any friends who have previously verified you will see security warnings until you re-verify with them.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{_t(
|
|
||||||
"Please only proceed if you're sure you've lost all of your other devices and your Security Key.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="mx_CompleteSecurity_actionRow">
|
<div className="mx_CompleteSecurity_actionRow">
|
||||||
<AccessibleButton kind="danger_outline" onClick={this.onResetConfirmClick}>
|
<AccessibleButton kind="danger_outline" onClick={this.onResetConfirmClick}>
|
||||||
{_t("Proceed with reset")}
|
{_t("encryption|verification|reset_proceed_prompt")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<AccessibleButton kind="primary" onClick={this.onResetBackClick}>
|
<AccessibleButton kind="primary" onClick={this.onResetBackClick}>
|
||||||
{_t("action|go_back")}
|
{_t("action|go_back")}
|
||||||
|
|
|
@ -155,7 +155,7 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
try {
|
try {
|
||||||
credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
|
credentials = await sendLoginRequest(hsUrl, isUrl, loginType, loginParams);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let errorText = _t("Failed to re-authenticate due to a homeserver problem");
|
let errorText = _t("auth|failed_soft_logout_homeserver");
|
||||||
if (
|
if (
|
||||||
e instanceof MatrixError &&
|
e instanceof MatrixError &&
|
||||||
e.errcode === "M_FORBIDDEN" &&
|
e.errcode === "M_FORBIDDEN" &&
|
||||||
|
@ -311,12 +311,8 @@ export default class SoftLogout extends React.Component<IProps, IState> {
|
||||||
<h2>{_t("action|sign_in")}</h2>
|
<h2>{_t("action|sign_in")}</h2>
|
||||||
<div>{this.renderSignInSection()}</div>
|
<div>{this.renderSignInSection()}</div>
|
||||||
|
|
||||||
<h2>{_t("Clear personal data")}</h2>
|
<h2>{_t("auth|soft_logout_subheading")}</h2>
|
||||||
<p>
|
<p>{_t("auth|soft_logout_warning")}</p>
|
||||||
{_t(
|
|
||||||
"Warning: your personal data (including encryption keys) is still stored in this session. Clear it if you're finished using this session, or want to sign in to another account.",
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div>
|
<div>
|
||||||
<AccessibleButton onClick={this.onClearAll} kind="danger">
|
<AccessibleButton onClick={this.onClearAll} kind="danger">
|
||||||
{_t("Clear all data")}
|
{_t("Clear all data")}
|
||||||
|
|
|
@ -46,7 +46,7 @@ export const EnterEmail: React.FC<EnterEmailProps> = ({
|
||||||
onLoginClick,
|
onLoginClick,
|
||||||
onSubmitForm,
|
onSubmitForm,
|
||||||
}) => {
|
}) => {
|
||||||
const submitButtonChild = loading ? <Spinner w={16} h={16} /> : _t("Send email");
|
const submitButtonChild = loading ? <Spinner w={16} h={16} /> : _t("auth|forgot_password_send_email");
|
||||||
|
|
||||||
const emailFieldRef = useRef<Field>(null);
|
const emailFieldRef = useRef<Field>(null);
|
||||||
|
|
||||||
|
|
|
@ -153,9 +153,11 @@ export default class LoginWithQR extends React.Component<IProps, IState> {
|
||||||
private generateCode = async (): Promise<void> => {
|
private generateCode = async (): Promise<void> => {
|
||||||
let rendezvous: MSC3906Rendezvous;
|
let rendezvous: MSC3906Rendezvous;
|
||||||
try {
|
try {
|
||||||
|
const fallbackRzServer = this.props.client.getClientWellKnown()?.["io.element.rendezvous"]?.server;
|
||||||
const transport = new MSC3886SimpleHttpRendezvousTransport<MSC3903ECDHPayload>({
|
const transport = new MSC3886SimpleHttpRendezvousTransport<MSC3903ECDHPayload>({
|
||||||
onFailure: this.onFailure,
|
onFailure: this.onFailure,
|
||||||
client: this.props.client,
|
client: this.props.client,
|
||||||
|
fallbackRzServer,
|
||||||
});
|
});
|
||||||
|
|
||||||
const channel = new MSC3903ECDHv2RendezvousChannel<MSC3906RendezvousPayload>(
|
const channel = new MSC3903ECDHv2RendezvousChannel<MSC3906RendezvousPayload>(
|
||||||
|
|
|
@ -164,7 +164,7 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
<IconizedContextMenuCheckbox
|
<IconizedContextMenuCheckbox
|
||||||
onClick={(e) => onTagRoom(e, DefaultTagID.LowPriority)}
|
onClick={(e) => onTagRoom(e, DefaultTagID.LowPriority)}
|
||||||
active={isLowPriority}
|
active={isLowPriority}
|
||||||
label={_t("Low priority")}
|
label={_t("common|low_priority")}
|
||||||
iconClassName="mx_RoomTile_iconArrowDown"
|
iconClassName="mx_RoomTile_iconArrowDown"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -174,11 +174,11 @@ const RoomContextMenu: React.FC<IProps> = ({ room, onFinished, ...props }) => {
|
||||||
let iconClassName: string | undefined;
|
let iconClassName: string | undefined;
|
||||||
switch (echoChamber.notificationVolume) {
|
switch (echoChamber.notificationVolume) {
|
||||||
case RoomNotifState.AllMessages:
|
case RoomNotifState.AllMessages:
|
||||||
notificationLabel = _t("Default");
|
notificationLabel = _t("notifications|default");
|
||||||
iconClassName = "mx_RoomTile_iconNotificationsDefault";
|
iconClassName = "mx_RoomTile_iconNotificationsDefault";
|
||||||
break;
|
break;
|
||||||
case RoomNotifState.AllMessagesLoud:
|
case RoomNotifState.AllMessagesLoud:
|
||||||
notificationLabel = _t("All messages");
|
notificationLabel = _t("notifications|all_messages");
|
||||||
iconClassName = "mx_RoomTile_iconNotificationsAllMessages";
|
iconClassName = "mx_RoomTile_iconNotificationsAllMessages";
|
||||||
break;
|
break;
|
||||||
case RoomNotifState.MentionsOnly:
|
case RoomNotifState.MentionsOnly:
|
||||||
|
|
|
@ -61,7 +61,7 @@ export const RoomNotificationContextMenu: React.FC<IProps> = ({ room, onFinished
|
||||||
|
|
||||||
const allMessagesOption: JSX.Element = (
|
const allMessagesOption: JSX.Element = (
|
||||||
<IconizedContextMenuRadio
|
<IconizedContextMenuRadio
|
||||||
label={_t("All messages")}
|
label={_t("notifications|all_messages")}
|
||||||
active={notificationState === RoomNotifState.AllMessagesLoud}
|
active={notificationState === RoomNotifState.AllMessagesLoud}
|
||||||
iconClassName="mx_RoomNotificationContextMenu_iconBellDot"
|
iconClassName="mx_RoomNotificationContextMenu_iconBellDot"
|
||||||
onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessagesLoud))}
|
onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessagesLoud))}
|
||||||
|
|
|
@ -187,7 +187,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
data-testid="new-video-room-option"
|
data-testid="new-video-room-option"
|
||||||
iconClassName="mx_SpacePanel_iconPlus"
|
iconClassName="mx_SpacePanel_iconPlus"
|
||||||
label={_t("Video room")}
|
label={_t("common|video_room")}
|
||||||
onClick={onNewVideoRoomClick}
|
onClick={onNewVideoRoomClick}
|
||||||
>
|
>
|
||||||
<BetaPill />
|
<BetaPill />
|
||||||
|
|
|
@ -387,7 +387,7 @@ const defaultRendererFactory =
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const defaultRoomsRenderer = defaultRendererFactory(_td("Rooms"));
|
export const defaultRoomsRenderer = defaultRendererFactory(_td("common|rooms"));
|
||||||
export const defaultSpacesRenderer = defaultRendererFactory(_td("common|spaces"));
|
export const defaultSpacesRenderer = defaultRendererFactory(_td("common|spaces"));
|
||||||
export const defaultDmsRenderer = defaultRendererFactory(_td("Direct Messages"));
|
export const defaultDmsRenderer = defaultRendererFactory(_td("Direct Messages"));
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,7 @@ export default class ConfirmUserActionDialog extends React.Component<IProps, ISt
|
||||||
onChange={this.onReasonChange}
|
onChange={this.onReasonChange}
|
||||||
value={this.state.reason}
|
value={this.state.reason}
|
||||||
className="mx_ConfirmUserActionDialog_reasonField"
|
className="mx_ConfirmUserActionDialog_reasonField"
|
||||||
label={_t("Reason")}
|
label={_t("room_settings|permissions|ban_reason")}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -353,9 +353,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
microcopy = _t("create_room|encryption_forced");
|
microcopy = _t("create_room|encryption_forced");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
microcopy = _t(
|
microcopy = _t("settings|security|e2ee_default_disabled_warning");
|
||||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
e2eeSection = (
|
e2eeSection = (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
@ -420,7 +418,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
|
||||||
labelKnock={
|
labelKnock={
|
||||||
this.askToJoinEnabled ? _t("room_settings|security|join_rule_knock") : undefined
|
this.askToJoinEnabled ? _t("room_settings|security|join_rule_knock") : undefined
|
||||||
}
|
}
|
||||||
labelPublic={_t("Public room")}
|
labelPublic={_t("common|public_room")}
|
||||||
labelRestricted={
|
labelRestricted={
|
||||||
this.supportsRestricted ? _t("create_room|join_rule_restricted") : undefined
|
this.supportsRestricted ? _t("create_room|join_rule_restricted") : undefined
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,7 +163,7 @@ const CreateSubspaceDialog: React.FC<IProps> = ({ space, onAddExistingSpaceClick
|
||||||
<JoinRuleDropdown
|
<JoinRuleDropdown
|
||||||
label={_t("Space visibility")}
|
label={_t("Space visibility")}
|
||||||
labelInvite={_t("Private space (invite only)")}
|
labelInvite={_t("Private space (invite only)")}
|
||||||
labelPublic={_t("Public space")}
|
labelPublic={_t("common|public_space")}
|
||||||
labelRestricted={_t("create_room|join_rule_restricted")}
|
labelRestricted={_t("create_room|join_rule_restricted")}
|
||||||
width={478}
|
width={478}
|
||||||
value={joinRule}
|
value={joinRule}
|
||||||
|
|
|
@ -214,7 +214,7 @@ export default class DeactivateAccountDialog extends React.Component<IProps, ISt
|
||||||
className="mx_DeactivateAccountDialog"
|
className="mx_DeactivateAccountDialog"
|
||||||
onFinished={this.props.onFinished}
|
onFinished={this.props.onFinished}
|
||||||
titleClass="danger"
|
titleClass="danger"
|
||||||
title={_t("Deactivate Account")}
|
title={_t("settings|general|deactivate_section")}
|
||||||
screenName="DeactivateAccount"
|
screenName="DeactivateAccount"
|
||||||
>
|
>
|
||||||
<div className="mx_Dialog_content">
|
<div className="mx_Dialog_content">
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default class IntegrationsDisabledDialog extends React.Component<IProps>
|
||||||
<div className="mx_IntegrationsDisabledDialog_content">
|
<div className="mx_IntegrationsDisabledDialog_content">
|
||||||
<p>
|
<p>
|
||||||
{_t("Enable '%(manageIntegrations)s' in Settings to do this.", {
|
{_t("Enable '%(manageIntegrations)s' in Settings to do this.", {
|
||||||
manageIntegrations: _t("Manage integrations"),
|
manageIntegrations: _t("integration_manager|manage_title"),
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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
|
// 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.
|
// 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 body = this.state.authError ? null : this.props.body;
|
||||||
let continueText: string | undefined;
|
let continueText: string | undefined;
|
||||||
let continueKind: string | undefined;
|
let continueKind: string | undefined;
|
||||||
|
|
|
@ -417,7 +417,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||||
<Field
|
<Field
|
||||||
className="mx_ReportEventDialog_reason"
|
className="mx_ReportEventDialog_reason"
|
||||||
element="textarea"
|
element="textarea"
|
||||||
label={_t("Reason")}
|
label={_t("room_settings|permissions|ban_reason")}
|
||||||
rows={5}
|
rows={5}
|
||||||
onChange={this.onReasonChange}
|
onChange={this.onReasonChange}
|
||||||
value={this.state.reason}
|
value={this.state.reason}
|
||||||
|
@ -456,7 +456,7 @@ export default class ReportEventDialog extends React.Component<IProps, IState> {
|
||||||
<Field
|
<Field
|
||||||
className="mx_ReportEventDialog_reason"
|
className="mx_ReportEventDialog_reason"
|
||||||
element="textarea"
|
element="textarea"
|
||||||
label={_t("Reason")}
|
label={_t("room_settings|permissions|ban_reason")}
|
||||||
rows={5}
|
rows={5}
|
||||||
onChange={this.onReasonChange}
|
onChange={this.onReasonChange}
|
||||||
value={this.state.reason}
|
value={this.state.reason}
|
||||||
|
|
|
@ -154,7 +154,7 @@ class RoomSettingsDialog extends React.Component<IProps, IState> {
|
||||||
tabs.push(
|
tabs.push(
|
||||||
new Tab(
|
new Tab(
|
||||||
RoomSettingsTab.Voip,
|
RoomSettingsTab.Voip,
|
||||||
_td("Voice & Video"),
|
_td("settings|voip|title"),
|
||||||
"mx_RoomSettingsDialog_voiceIcon",
|
"mx_RoomSettingsDialog_voiceIcon",
|
||||||
<VoipRoomSettingsTab room={this.state.room} />,
|
<VoipRoomSettingsTab room={this.state.room} />,
|
||||||
),
|
),
|
||||||
|
@ -197,7 +197,7 @@ class RoomSettingsDialog extends React.Component<IProps, IState> {
|
||||||
tabs.push(
|
tabs.push(
|
||||||
new Tab(
|
new Tab(
|
||||||
RoomSettingsTab.Bridges,
|
RoomSettingsTab.Bridges,
|
||||||
_td("Bridges"),
|
_td("room_settings|bridges|title"),
|
||||||
"mx_RoomSettingsDialog_bridgesIcon",
|
"mx_RoomSettingsDialog_bridgesIcon",
|
||||||
<BridgeSettingsTab room={this.state.room} />,
|
<BridgeSettingsTab room={this.state.room} />,
|
||||||
"RoomSettingsBridges",
|
"RoomSettingsBridges",
|
||||||
|
|
|
@ -67,8 +67,8 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||||
const emailAddress = this.state.emailAddress;
|
const emailAddress = this.state.emailAddress;
|
||||||
if (!Email.looksValid(emailAddress)) {
|
if (!Email.looksValid(emailAddress)) {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Invalid Email Address"),
|
title: _t("settings|general|error_invalid_email"),
|
||||||
description: _t("This doesn't appear to be a valid email address"),
|
description: _t("settings|general|error_invalid_email_detail"),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ export default class SetEmailDialog extends React.Component<IProps, IState> {
|
||||||
this.setState({ emailBusy: false });
|
this.setState({ emailBusy: false });
|
||||||
logger.error("Unable to add email address " + emailAddress + " " + err);
|
logger.error("Unable to add email address " + emailAddress + " " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to add email address"),
|
title: _t("settings|general|error_add_email"),
|
||||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
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") {
|
if (underlyingError instanceof MatrixError && underlyingError.errcode === "M_THREEPID_AUTH_FAILED") {
|
||||||
const message =
|
const message =
|
||||||
_t("Unable to verify email address.") +
|
_t("settings|general|error_email_verification") +
|
||||||
" " +
|
" " +
|
||||||
_t(
|
_t(
|
||||||
"Please check your email and click on the link it contains. Once this is done, click continue.",
|
"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 {
|
} else {
|
||||||
logger.error("Unable to verify email address: " + err);
|
logger.error("Unable to verify email address: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to verify email address."),
|
title: _t("settings|general|error_email_verification"),
|
||||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,7 +133,7 @@ export default class UserSettingsDialog extends React.Component<IProps, IState>
|
||||||
tabs.push(
|
tabs.push(
|
||||||
new Tab(
|
new Tab(
|
||||||
UserTab.Voice,
|
UserTab.Voice,
|
||||||
_td("Voice & Video"),
|
_td("settings|voip|title"),
|
||||||
"mx_UserSettingsDialog_voiceIcon",
|
"mx_UserSettingsDialog_voiceIcon",
|
||||||
<VoiceUserSettingsTab />,
|
<VoiceUserSettingsTab />,
|
||||||
"UserSettingsVoiceVideo",
|
"UserSettingsVoiceVideo",
|
||||||
|
|
|
@ -33,7 +33,9 @@ interface Props {
|
||||||
|
|
||||||
export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsId }: Props): JSX.Element {
|
export function PublicRoomResultDetails({ room, labelId, descriptionId, detailsId }: Props): JSX.Element {
|
||||||
let name =
|
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) {
|
if (name.length > MAX_NAME_LENGTH) {
|
||||||
name = `${name.substring(0, 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;
|
const target = ev.target as HTMLElement;
|
||||||
setGeneralMenuPosition(target.getBoundingClientRect());
|
setGeneralMenuPosition(target.getBoundingClientRect());
|
||||||
}}
|
}}
|
||||||
title={room.isSpaceRoom() ? _t("space|context_menu|options") : _t("Room options")}
|
title={room.isSpaceRoom() ? _t("space|context_menu|options") : _t("room|context_menu|title")}
|
||||||
isExpanded={generalMenuPosition !== null}
|
isExpanded={generalMenuPosition !== null}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -788,7 +788,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
|
||||||
role="group"
|
role="group"
|
||||||
aria-labelledby="mx_SpotlightDialog_section_rooms"
|
aria-labelledby="mx_SpotlightDialog_section_rooms"
|
||||||
>
|
>
|
||||||
<h4 id="mx_SpotlightDialog_section_rooms">{_t("Rooms")}</h4>
|
<h4 id="mx_SpotlightDialog_section_rooms">{_t("common|rooms")}</h4>
|
||||||
<div>{results[Section.Rooms].slice(0, SECTION_LIMIT).map(resultMapper)}</div>
|
<div>{results[Section.Rooms].slice(0, SECTION_LIMIT).map(resultMapper)}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -409,7 +409,7 @@ export const UserOptionsSection: React.FC<{
|
||||||
kind="link"
|
kind="link"
|
||||||
className={classNames("mx_UserInfo_field", { mx_UserInfo_destructive: !isIgnored })}
|
className={classNames("mx_UserInfo_field", { mx_UserInfo_destructive: !isIgnored })}
|
||||||
>
|
>
|
||||||
{isIgnored ? _t("Unignore") : _t("action|ignore")}
|
{isIgnored ? _t("action|unignore") : _t("action|ignore")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -212,9 +212,12 @@ const VideoCallButton: FC<VideoCallButtonProps> = ({ room, busy, setBusy, behavi
|
||||||
menu = (
|
menu = (
|
||||||
<IconizedContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu}>
|
<IconizedContextMenu {...aboveLeftOf(buttonRect)} onFinished={closeMenu}>
|
||||||
<IconizedContextMenuOptionList>
|
<IconizedContextMenuOptionList>
|
||||||
<IconizedContextMenuOption label={_t("Video call (Jitsi)")} onClick={onJitsiClick} />
|
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Video call (%(brand)s)", { brand })}
|
label={_t("room|header|video_call_button_jitsi")}
|
||||||
|
onClick={onJitsiClick}
|
||||||
|
/>
|
||||||
|
<IconizedContextMenuOption
|
||||||
|
label={_t("room|header|video_call_button_ec", { brand })}
|
||||||
onClick={onElementClick}
|
onClick={onElementClick}
|
||||||
/>
|
/>
|
||||||
</IconizedContextMenuOptionList>
|
</IconizedContextMenuOptionList>
|
||||||
|
@ -412,13 +415,13 @@ const CallLayoutSelector: FC<CallLayoutSelectorProps> = ({ call }) => {
|
||||||
<IconizedContextMenuOptionList>
|
<IconizedContextMenuOptionList>
|
||||||
<IconizedContextMenuRadio
|
<IconizedContextMenuRadio
|
||||||
iconClassName="mx_LegacyRoomHeader_freedomIcon"
|
iconClassName="mx_LegacyRoomHeader_freedomIcon"
|
||||||
label={_t("Freedom")}
|
label={_t("room|header|video_call_ec_layout_freedom")}
|
||||||
active={layout === Layout.Tile}
|
active={layout === Layout.Tile}
|
||||||
onClick={onFreedomClick}
|
onClick={onFreedomClick}
|
||||||
/>
|
/>
|
||||||
<IconizedContextMenuRadio
|
<IconizedContextMenuRadio
|
||||||
iconClassName="mx_LegacyRoomHeader_spotlightIcon"
|
iconClassName="mx_LegacyRoomHeader_spotlightIcon"
|
||||||
label={_t("Spotlight")}
|
label={_t("room|header|video_call_ec_layout_spotlight")}
|
||||||
active={layout === Layout.Spotlight}
|
active={layout === Layout.Spotlight}
|
||||||
onClick={onSpotlightClick}
|
onClick={onSpotlightClick}
|
||||||
/>
|
/>
|
||||||
|
@ -436,7 +439,7 @@ const CallLayoutSelector: FC<CallLayoutSelectorProps> = ({ call }) => {
|
||||||
"mx_LegacyRoomHeader_layoutButton--spotlight": layout === Layout.Spotlight,
|
"mx_LegacyRoomHeader_layoutButton--spotlight": layout === Layout.Spotlight,
|
||||||
})}
|
})}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
title={_t("Change layout")}
|
title={_t("room|header|video_call_ec_change_layout")}
|
||||||
alignment={Alignment.Bottom}
|
alignment={Alignment.Bottom}
|
||||||
key="layout"
|
key="layout"
|
||||||
/>
|
/>
|
||||||
|
@ -589,7 +592,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_forgetButton"
|
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_forgetButton"
|
||||||
onClick={this.props.onForgetClick}
|
onClick={this.props.onForgetClick}
|
||||||
title={_t("Forget room")}
|
title={_t("room|header|forget_room_button")}
|
||||||
alignment={Alignment.Bottom}
|
alignment={Alignment.Bottom}
|
||||||
key="forget"
|
key="forget"
|
||||||
/>,
|
/>,
|
||||||
|
@ -603,7 +606,11 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
mx_LegacyRoomHeader_appsButton_highlight: this.props.appsShown,
|
mx_LegacyRoomHeader_appsButton_highlight: this.props.appsShown,
|
||||||
})}
|
})}
|
||||||
onClick={this.props.onAppsClick}
|
onClick={this.props.onAppsClick}
|
||||||
title={this.props.appsShown ? _t("Hide Widgets") : _t("Show Widgets")}
|
title={
|
||||||
|
this.props.appsShown
|
||||||
|
? _t("room|header|hide_widgets_button")
|
||||||
|
: _t("room|header|show_widgets_button")
|
||||||
|
}
|
||||||
aria-checked={this.props.appsShown}
|
aria-checked={this.props.appsShown}
|
||||||
alignment={Alignment.Bottom}
|
alignment={Alignment.Bottom}
|
||||||
key="apps"
|
key="apps"
|
||||||
|
@ -643,7 +650,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_closeButton"
|
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_closeButton"
|
||||||
onClick={this.onHideCallClick}
|
onClick={this.onHideCallClick}
|
||||||
title={_t("Close call")}
|
title={_t("room|header|close_call_button")}
|
||||||
key="close"
|
key="close"
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
@ -652,7 +659,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_minimiseButton"
|
className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_minimiseButton"
|
||||||
onClick={this.onHideCallClick}
|
onClick={this.onHideCallClick}
|
||||||
title={_t("View chat timeline")}
|
title={_t("room|header|video_room_view_chat_button")}
|
||||||
alignment={Alignment.Bottom}
|
alignment={Alignment.Bottom}
|
||||||
key="minimise"
|
key="minimise"
|
||||||
/>,
|
/>,
|
||||||
|
@ -718,7 +725,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
className="mx_LegacyRoomHeader_name"
|
className="mx_LegacyRoomHeader_name"
|
||||||
onClick={this.onContextMenuOpenClick}
|
onClick={this.onContextMenuOpenClick}
|
||||||
isExpanded={!!this.state.contextMenuPosition}
|
isExpanded={!!this.state.contextMenuPosition}
|
||||||
title={_t("Room options")}
|
title={_t("room|context_menu|title")}
|
||||||
alignment={Alignment.Bottom}
|
alignment={Alignment.Bottom}
|
||||||
>
|
>
|
||||||
{roomName}
|
{roomName}
|
||||||
|
@ -762,7 +769,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const buttons = this.props.showButtons ? this.renderButtons(isVideoRoom) : null;
|
const buttons = this.props.showButtons ? this.renderButtons(isVideoRoom) : null;
|
||||||
|
|
||||||
let oobName = _t("Unnamed room");
|
let oobName = _t("common|unnamed_room");
|
||||||
if (this.props.oobData && this.props.oobData.name) {
|
if (this.props.oobData && this.props.oobData.name) {
|
||||||
oobName = 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}>
|
<AccessibleButton onClick={toggleExpanded}>
|
||||||
{expanded
|
{expanded
|
||||||
? _t("action|collapse")
|
? _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>
|
</AccessibleButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ const LinkPreviewGroup: React.FC<IProps> = ({ links, mxEvent, onCancelClick, onH
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_LinkPreviewGroup_hide"
|
className="mx_LinkPreviewGroup_hide"
|
||||||
onClick={onCancelClick}
|
onClick={onCancelClick}
|
||||||
aria-label={_t("Close preview")}
|
aria-label={_t("timeline|url_preview|close")}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className="mx_filterFlipColor"
|
className="mx_filterFlipColor"
|
||||||
|
|
|
@ -354,9 +354,9 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||||
let inviteButton: JSX.Element | undefined;
|
let inviteButton: JSX.Element | undefined;
|
||||||
|
|
||||||
if (room?.getMyMembership() === "join" && shouldShowComponent(UIComponent.InviteUsers)) {
|
if (room?.getMyMembership() === "join" && shouldShowComponent(UIComponent.InviteUsers)) {
|
||||||
let inviteButtonText = _t("Invite to this room");
|
let inviteButtonText = _t("room|invite_this_room");
|
||||||
if (room.isSpaceRoom()) {
|
if (room.isSpaceRoom()) {
|
||||||
inviteButtonText = _t("Invite to this space");
|
inviteButtonText = _t("space|invite_this_space");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.canInvite) {
|
if (this.state.canInvite) {
|
||||||
|
@ -371,7 +371,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||||
className="mx_MemberList_invite"
|
className="mx_MemberList_invite"
|
||||||
onClick={null}
|
onClick={null}
|
||||||
disabled
|
disabled
|
||||||
tooltip={_t("You do not have permission to invite users")}
|
tooltip={_t("member_list|invite_button_no_perms_tooltip")}
|
||||||
>
|
>
|
||||||
<span>{inviteButtonText}</span>
|
<span>{inviteButtonText}</span>
|
||||||
</AccessibleTooltipButton>
|
</AccessibleTooltipButton>
|
||||||
|
@ -382,7 +382,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||||
let invitedHeader;
|
let invitedHeader;
|
||||||
let invitedSection;
|
let invitedSection;
|
||||||
if (this.getChildCountInvited() > 0) {
|
if (this.getChildCountInvited() > 0) {
|
||||||
invitedHeader = <h2>{_t("Invited")}</h2>;
|
invitedHeader = <h2>{_t("member_list|invited_list_heading")}</h2>;
|
||||||
invitedSection = (
|
invitedSection = (
|
||||||
<TruncatedList
|
<TruncatedList
|
||||||
className="mx_MemberList_section mx_MemberList_invited"
|
className="mx_MemberList_section mx_MemberList_invited"
|
||||||
|
@ -397,7 +397,7 @@ export default class MemberList extends React.Component<IProps, IState> {
|
||||||
const footer = (
|
const footer = (
|
||||||
<SearchBox
|
<SearchBox
|
||||||
className="mx_MemberList_query mx_textinput_icon mx_textinput_search"
|
className="mx_MemberList_query mx_textinput_icon mx_textinput_search"
|
||||||
placeholder={_t("Filter room members")}
|
placeholder={_t("member_list|filter_placeholder")}
|
||||||
onSearch={this.onSearchQueryChanged}
|
onSearch={this.onSearchQueryChanged}
|
||||||
initialValue={this.props.searchQuery}
|
initialValue={this.props.searchQuery}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -173,7 +173,7 @@ export default class MemberTile extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private getPowerLabel(): string {
|
private getPowerLabel(): string {
|
||||||
return _t("%(userName)s (power %(powerLevelNumber)s)", {
|
return _t("member_list|power_label", {
|
||||||
userName: UserIdentifierCustomisations.getDisplayUserIdentifier(this.props.member.userId, {
|
userName: UserIdentifierCustomisations.getDisplayUserIdentifier(this.props.member.userId, {
|
||||||
roomId: this.props.member.roomId,
|
roomId: this.props.member.roomId,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -535,7 +535,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||||
className="mx_MessageComposer_roomReplaced_link"
|
className="mx_MessageComposer_roomReplaced_link"
|
||||||
onClick={this.onTombstoneClick}
|
onClick={this.onTombstoneClick}
|
||||||
>
|
>
|
||||||
{_t("The conversation continues here.")}
|
{_t("composer|room_upgraded_link")}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
""
|
""
|
||||||
|
@ -551,7 +551,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||||
src={require("../../../../res/img/room_replaced.svg").default}
|
src={require("../../../../res/img/room_replaced.svg").default}
|
||||||
/>
|
/>
|
||||||
<span className="mx_MessageComposer_roomReplaced_header">
|
<span className="mx_MessageComposer_roomReplaced_header">
|
||||||
{_t("This room has been replaced and is no longer active.")}
|
{_t("composer|room_upgraded_notice")}
|
||||||
</span>
|
</span>
|
||||||
<br />
|
<br />
|
||||||
{continuesLink}
|
{continuesLink}
|
||||||
|
@ -561,7 +561,7 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||||
} else {
|
} else {
|
||||||
controls.push(
|
controls.push(
|
||||||
<div key="controls_error" className="mx_MessageComposer_noperm_error">
|
<div key="controls_error" className="mx_MessageComposer_noperm_error">
|
||||||
{_t("You do not have permission to post to this room")}
|
{_t("composer|no_perms_notice")}
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -649,7 +649,9 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
||||||
<SendButton
|
<SendButton
|
||||||
key="controls_send"
|
key="controls_send"
|
||||||
onClick={this.sendMessage}
|
onClick={this.sendMessage}
|
||||||
title={this.state.haveRecording ? _t("Send voice message") : undefined}
|
title={
|
||||||
|
this.state.haveRecording ? _t("composer|send_button_voice_message") : undefined
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -258,7 +258,7 @@ function showStickersButton(props: IProps): ReactElement | null {
|
||||||
className="mx_MessageComposer_button"
|
className="mx_MessageComposer_button"
|
||||||
iconClassName="mx_MessageComposer_stickers"
|
iconClassName="mx_MessageComposer_stickers"
|
||||||
onClick={() => props.setStickerPickerOpen(!props.isStickerPickerOpen)}
|
onClick={() => props.setStickerPickerOpen(!props.isStickerPickerOpen)}
|
||||||
title={props.isStickerPickerOpen ? _t("Hide stickers") : _t("common|sticker")}
|
title={props.isStickerPickerOpen ? _t("composer|close_sticker_picker") : _t("common|sticker")}
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
@ -283,7 +283,7 @@ function voiceRecordingButton(props: IProps, narrow: boolean): ReactElement | nu
|
||||||
className="mx_MessageComposer_button"
|
className="mx_MessageComposer_button"
|
||||||
iconClassName="mx_MessageComposer_voiceMessage"
|
iconClassName="mx_MessageComposer_voiceMessage"
|
||||||
onClick={props.onRecordStartEndClick}
|
onClick={props.onRecordStartEndClick}
|
||||||
title={_t("Voice Message")}
|
title={_t("composer|voice_message_button")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -309,8 +309,8 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
|
||||||
);
|
);
|
||||||
if (!canSend) {
|
if (!canSend) {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Permission Required"),
|
title: _t("composer|poll_button_no_perms_title"),
|
||||||
description: _t("You do not have permission to start polls in this room."),
|
description: _t("composer|poll_button_no_perms_description"),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const threadId =
|
const threadId =
|
||||||
|
@ -338,7 +338,7 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
|
||||||
className="mx_MessageComposer_button"
|
className="mx_MessageComposer_button"
|
||||||
iconClassName="mx_MessageComposer_poll"
|
iconClassName="mx_MessageComposer_poll"
|
||||||
onClick={this.onCreateClick}
|
onClick={this.onCreateClick}
|
||||||
title={_t("Poll")}
|
title={_t("composer|poll_button")}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -364,7 +364,7 @@ interface WysiwygToggleButtonProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ComposerModeButton({ isRichTextEnabled, onClick }: WysiwygToggleButtonProps): JSX.Element {
|
function ComposerModeButton({ isRichTextEnabled, onClick }: WysiwygToggleButtonProps): JSX.Element {
|
||||||
const title = isRichTextEnabled ? _t("Hide formatting") : _t("Show formatting");
|
const title = isRichTextEnabled ? _t("composer|mode_plain") : _t("composer|mode_rich_text");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CollapsibleButton
|
<CollapsibleButton
|
||||||
|
|
|
@ -52,7 +52,7 @@ export default class MessageComposerFormatBar extends React.PureComponent<IProps
|
||||||
mx_MessageComposerFormatBar_shown: this.state.visible,
|
mx_MessageComposerFormatBar_shown: this.state.visible,
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<Toolbar className={classes} ref={this.formatBarRef} aria-label={_t("Formatting")}>
|
<Toolbar className={classes} ref={this.formatBarRef} aria-label={_t("composer|formatting_toolbar_label")}>
|
||||||
<FormatButton
|
<FormatButton
|
||||||
label={_t("composer|format_bold")}
|
label={_t("composer|format_bold")}
|
||||||
onClick={() => this.props.onAction(Formatting.Bold)}
|
onClick={() => this.props.onAction(Formatting.Bold)}
|
||||||
|
@ -61,7 +61,7 @@ export default class MessageComposerFormatBar extends React.PureComponent<IProps
|
||||||
visible={this.state.visible}
|
visible={this.state.visible}
|
||||||
/>
|
/>
|
||||||
<FormatButton
|
<FormatButton
|
||||||
label={_t("Italics")}
|
label={_t("composer|format_italics")}
|
||||||
onClick={() => this.props.onAction(Formatting.Italics)}
|
onClick={() => this.props.onAction(Formatting.Italics)}
|
||||||
icon="Italic"
|
icon="Italic"
|
||||||
shortcut={this.props.shortcuts.italics}
|
shortcut={this.props.shortcuts.italics}
|
||||||
|
@ -88,7 +88,7 @@ export default class MessageComposerFormatBar extends React.PureComponent<IProps
|
||||||
visible={this.state.visible}
|
visible={this.state.visible}
|
||||||
/>
|
/>
|
||||||
<FormatButton
|
<FormatButton
|
||||||
label={_t("Insert link")}
|
label={_t("composer|format_insert_link")}
|
||||||
onClick={() => this.props.onAction(Formatting.InsertLink)}
|
onClick={() => this.props.onAction(Formatting.InsertLink)}
|
||||||
icon="InsertLink"
|
icon="InsertLink"
|
||||||
shortcut={this.props.shortcuts.insert_link}
|
shortcut={this.props.shortcuts.insert_link}
|
||||||
|
|
|
@ -215,7 +215,7 @@ const NewRoomIntro: React.FC = () => {
|
||||||
defaultDispatcher.dispatch({ action: "view_invite", roomId });
|
defaultDispatcher.dispatch({ action: "view_invite", roomId });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_t("Invite to this room")}
|
{_t("room|invite_this_room")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -122,7 +122,7 @@ export default class NotificationBadge extends React.PureComponent<XOR<IProps, I
|
||||||
let label: string | undefined;
|
let label: string | undefined;
|
||||||
let tooltip: JSX.Element | undefined;
|
let tooltip: JSX.Element | undefined;
|
||||||
if (showUnsentTooltip && this.state.showTooltip && notification.color === NotificationColor.Unsent) {
|
if (showUnsentTooltip && this.state.showTooltip && notification.color === NotificationColor.Unsent) {
|
||||||
label = _t("Message didn't send. Click for info.");
|
label = _t("notifications|message_didnt_send");
|
||||||
tooltip = <Tooltip className="mx_NotificationBadge_tooltip" label={label} />;
|
tooltip = <Tooltip className="mx_NotificationBadge_tooltip" label={label} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,9 @@ export function ReadReceiptGroup({
|
||||||
const [{ showTooltip, hideTooltip }, tooltip] = useTooltip({
|
const [{ showTooltip, hideTooltip }, tooltip] = useTooltip({
|
||||||
label: (
|
label: (
|
||||||
<>
|
<>
|
||||||
<div className="mx_Tooltip_title">{_t("Seen by %(count)s people", { count: readReceipts.length })}</div>
|
<div className="mx_Tooltip_title">
|
||||||
|
{_t("timeline|read_receipt_title", { count: readReceipts.length })}
|
||||||
|
</div>
|
||||||
<div className="mx_Tooltip_sub">{tooltipText}</div>
|
<div className="mx_Tooltip_sub">{tooltipText}</div>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
@ -176,7 +178,7 @@ export function ReadReceiptGroup({
|
||||||
<ContextMenu menuClassName="mx_ReadReceiptGroup_popup" onFinished={closeMenu} {...aboveLeftOf(buttonRect)}>
|
<ContextMenu menuClassName="mx_ReadReceiptGroup_popup" onFinished={closeMenu} {...aboveLeftOf(buttonRect)}>
|
||||||
<AutoHideScrollbar>
|
<AutoHideScrollbar>
|
||||||
<SectionHeader className="mx_ReadReceiptGroup_title">
|
<SectionHeader className="mx_ReadReceiptGroup_title">
|
||||||
{_t("Seen by %(count)s people", { count: readReceipts.length })}
|
{_t("timeline|read_receipt_title", { count: readReceipts.length })}
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
{readReceipts.map((receipt) => (
|
{readReceipts.map((receipt) => (
|
||||||
<ReadReceiptPerson
|
<ReadReceiptPerson
|
||||||
|
@ -193,7 +195,7 @@ export function ReadReceiptGroup({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_EventTile_msgOption">
|
<div className="mx_EventTile_msgOption">
|
||||||
<div className="mx_ReadReceiptGroup" role="group" aria-label={_t("Read receipts")}>
|
<div className="mx_ReadReceiptGroup" role="group" aria-label={_t("timeline|read_receipts_label")}>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_ReadReceiptGroup_button"
|
className="mx_ReadReceiptGroup_button"
|
||||||
inputRef={button}
|
inputRef={button}
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default class ReplyPreview extends React.Component<IProps> {
|
||||||
<div className="mx_ReplyPreview">
|
<div className="mx_ReplyPreview">
|
||||||
<div className="mx_ReplyPreview_section">
|
<div className="mx_ReplyPreview_section">
|
||||||
<div className="mx_ReplyPreview_header">
|
<div className="mx_ReplyPreview_header">
|
||||||
<span>{_t("Replying")}</span>
|
<span>{_t("composer|replying_title")}</span>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_ReplyPreview_header_cancel"
|
className="mx_ReplyPreview_header_cancel"
|
||||||
onClick={() => cancelQuoting(this.context.timelineRenderingType)}
|
onClick={() => cancelQuoting(this.context.timelineRenderingType)}
|
||||||
|
|
|
@ -50,7 +50,7 @@ const RoomBreadcrumbTile: React.FC<{ room: Room; onClick: (ev: ButtonEvent) => v
|
||||||
<AccessibleTooltipButton
|
<AccessibleTooltipButton
|
||||||
className="mx_RoomBreadcrumbs_crumb"
|
className="mx_RoomBreadcrumbs_crumb"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
aria-label={_t("Room %(name)s", { name: room.name })}
|
aria-label={_t("a11y|room_name", { name: room.name })}
|
||||||
title={room.name}
|
title={room.name}
|
||||||
tooltipClassName="mx_RoomBreadcrumbs_Tooltip"
|
tooltipClassName="mx_RoomBreadcrumbs_Tooltip"
|
||||||
onFocus={onFocus}
|
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!
|
// NOTE: The CSSTransition timeout MUST match the timeout in our CSS!
|
||||||
return (
|
return (
|
||||||
<CSSTransition appear={true} in={this.state.doAnimation} timeout={640} classNames="mx_RoomBreadcrumbs">
|
<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)}
|
{tiles.slice(this.state.skipFirst ? 1 : 0)}
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</CSSTransition>
|
</CSSTransition>
|
||||||
|
@ -131,7 +131,7 @@ export default class RoomBreadcrumbs extends React.PureComponent<IProps, IState>
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomBreadcrumbs">
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,12 +131,12 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
||||||
{roomName}
|
{roomName}
|
||||||
|
|
||||||
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
|
{!isDirectMessage && roomState.getJoinRule() === JoinRule.Public && (
|
||||||
<Tooltip label={_t("Public room")}>
|
<Tooltip label={_t("common|public_room")}>
|
||||||
<PublicIcon
|
<PublicIcon
|
||||||
width="16px"
|
width="16px"
|
||||||
height="16px"
|
height="16px"
|
||||||
className="text-secondary"
|
className="text-secondary"
|
||||||
aria-label={_t("Public room")}
|
aria-label={_t("common|public_room")}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
@ -153,12 +153,12 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isDirectMessage && e2eStatus === E2EStatus.Warning && (
|
{isDirectMessage && e2eStatus === E2EStatus.Warning && (
|
||||||
<Tooltip label={_t("Untrusted")}>
|
<Tooltip label={_t("room|header_untrusted_label")}>
|
||||||
<ErrorIcon
|
<ErrorIcon
|
||||||
width="16px"
|
width="16px"
|
||||||
height="16px"
|
height="16px"
|
||||||
className="mx_Untrusted"
|
className="mx_Untrusted"
|
||||||
aria-label={_t("Untrusted")}
|
aria-label={_t("room|header_untrusted_label")}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -51,13 +51,13 @@ const RoomInfoLine: FC<IProps> = ({ room }) => {
|
||||||
let roomType: string;
|
let roomType: string;
|
||||||
if (isVideoRoom) {
|
if (isVideoRoom) {
|
||||||
iconClass = "mx_RoomInfoLine_video";
|
iconClass = "mx_RoomInfoLine_video";
|
||||||
roomType = _t("Video room");
|
roomType = _t("common|video_room");
|
||||||
} else if (joinRule === JoinRule.Public) {
|
} else if (joinRule === JoinRule.Public) {
|
||||||
iconClass = "mx_RoomInfoLine_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 {
|
} else {
|
||||||
iconClass = "mx_RoomInfoLine_private";
|
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;
|
let members: JSX.Element | undefined;
|
||||||
|
|
|
@ -81,7 +81,6 @@ interface IState {
|
||||||
|
|
||||||
export const TAG_ORDER: TagID[] = [
|
export const TAG_ORDER: TagID[] = [
|
||||||
DefaultTagID.Invite,
|
DefaultTagID.Invite,
|
||||||
DefaultTagID.SavedItems,
|
|
||||||
DefaultTagID.Favourite,
|
DefaultTagID.Favourite,
|
||||||
DefaultTagID.DM,
|
DefaultTagID.DM,
|
||||||
DefaultTagID.Untagged,
|
DefaultTagID.Untagged,
|
||||||
|
@ -132,7 +131,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
|
||||||
<IconizedContextMenuOptionList first>
|
<IconizedContextMenuOptionList first>
|
||||||
{showCreateRooms && (
|
{showCreateRooms && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Start new chat")}
|
label={_t("action|start_new_chat")}
|
||||||
iconClassName="mx_RoomList_iconStartChat"
|
iconClassName="mx_RoomList_iconStartChat"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -148,7 +147,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
|
||||||
)}
|
)}
|
||||||
{showInviteUsers && (
|
{showInviteUsers && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Invite to space")}
|
label={_t("action|invite_to_space")}
|
||||||
iconClassName="mx_RoomList_iconInvite"
|
iconClassName="mx_RoomList_iconInvite"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -172,8 +171,8 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
|
||||||
onClick={openMenu}
|
onClick={openMenu}
|
||||||
className="mx_RoomSublist_auxButton"
|
className="mx_RoomSublist_auxButton"
|
||||||
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
||||||
aria-label={_t("Add people")}
|
aria-label={_t("action|add_people")}
|
||||||
title={_t("Add people")}
|
title={_t("action|add_people")}
|
||||||
isExpanded={menuDisplayed}
|
isExpanded={menuDisplayed}
|
||||||
inputRef={handle}
|
inputRef={handle}
|
||||||
/>
|
/>
|
||||||
|
@ -222,7 +221,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||||
contextMenuContent = (
|
contextMenuContent = (
|
||||||
<IconizedContextMenuOptionList first>
|
<IconizedContextMenuOptionList first>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Explore rooms")}
|
label={_t("action|explore_rooms")}
|
||||||
iconClassName="mx_RoomList_iconExplore"
|
iconClassName="mx_RoomList_iconExplore"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -239,7 +238,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||||
{showCreateRoom ? (
|
{showCreateRoom ? (
|
||||||
<>
|
<>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("New room")}
|
label={_t("action|new_room")}
|
||||||
iconClassName="mx_RoomList_iconNewRoom"
|
iconClassName="mx_RoomList_iconNewRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -253,7 +252,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||||
/>
|
/>
|
||||||
{videoRoomsEnabled && (
|
{videoRoomsEnabled && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("New video room")}
|
label={_t("action|new_video_room")}
|
||||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
iconClassName="mx_RoomList_iconNewVideoRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -271,7 +270,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||||
</IconizedContextMenuOption>
|
</IconizedContextMenuOption>
|
||||||
)}
|
)}
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Add existing room")}
|
label={_t("action|add_existing_room")}
|
||||||
iconClassName="mx_RoomList_iconAddExistingRoom"
|
iconClassName="mx_RoomList_iconAddExistingRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -292,7 +291,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||||
{showCreateRoom && (
|
{showCreateRoom && (
|
||||||
<>
|
<>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("New room")}
|
label={_t("action|new_room")}
|
||||||
iconClassName="mx_RoomList_iconNewRoom"
|
iconClassName="mx_RoomList_iconNewRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -304,7 +303,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||||
/>
|
/>
|
||||||
{videoRoomsEnabled && (
|
{videoRoomsEnabled && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("New video room")}
|
label={_t("action|new_video_room")}
|
||||||
iconClassName="mx_RoomList_iconNewVideoRoom"
|
iconClassName="mx_RoomList_iconNewVideoRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -325,7 +324,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||||
)}
|
)}
|
||||||
{showExploreRooms ? (
|
{showExploreRooms ? (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Explore public rooms")}
|
label={_t("action|explore_public_rooms")}
|
||||||
iconClassName="mx_RoomList_iconExplore"
|
iconClassName="mx_RoomList_iconExplore"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -357,8 +356,8 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
|
||||||
onClick={openMenu}
|
onClick={openMenu}
|
||||||
className="mx_RoomSublist_auxButton"
|
className="mx_RoomSublist_auxButton"
|
||||||
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
tooltipClassName="mx_RoomSublist_addRoomTooltip"
|
||||||
aria-label={_t("Add room")}
|
aria-label={_t("room_list|add_room_label")}
|
||||||
title={_t("Add room")}
|
title={_t("room_list|add_room_label")}
|
||||||
isExpanded={menuDisplayed}
|
isExpanded={menuDisplayed}
|
||||||
inputRef={handle}
|
inputRef={handle}
|
||||||
/>
|
/>
|
||||||
|
@ -382,11 +381,6 @@ const TAG_AESTHETICS: TagAestheticsMap = {
|
||||||
isInvite: false,
|
isInvite: false,
|
||||||
defaultHidden: false,
|
defaultHidden: false,
|
||||||
},
|
},
|
||||||
[DefaultTagID.SavedItems]: {
|
|
||||||
sectionLabel: _td("Saved Items"),
|
|
||||||
isInvite: false,
|
|
||||||
defaultHidden: false,
|
|
||||||
},
|
|
||||||
[DefaultTagID.DM]: {
|
[DefaultTagID.DM]: {
|
||||||
sectionLabel: _td("common|people"),
|
sectionLabel: _td("common|people"),
|
||||||
isInvite: false,
|
isInvite: false,
|
||||||
|
@ -394,13 +388,13 @@ const TAG_AESTHETICS: TagAestheticsMap = {
|
||||||
AuxButtonComponent: DmAuxButton,
|
AuxButtonComponent: DmAuxButton,
|
||||||
},
|
},
|
||||||
[DefaultTagID.Untagged]: {
|
[DefaultTagID.Untagged]: {
|
||||||
sectionLabel: _td("Rooms"),
|
sectionLabel: _td("common|rooms"),
|
||||||
isInvite: false,
|
isInvite: false,
|
||||||
defaultHidden: false,
|
defaultHidden: false,
|
||||||
AuxButtonComponent: UntaggedAuxButton,
|
AuxButtonComponent: UntaggedAuxButton,
|
||||||
},
|
},
|
||||||
[DefaultTagID.LowPriority]: {
|
[DefaultTagID.LowPriority]: {
|
||||||
sectionLabel: _td("Low priority"),
|
sectionLabel: _td("common|low_priority"),
|
||||||
isInvite: false,
|
isInvite: false,
|
||||||
defaultHidden: false,
|
defaultHidden: false,
|
||||||
},
|
},
|
||||||
|
@ -412,13 +406,13 @@ const TAG_AESTHETICS: TagAestheticsMap = {
|
||||||
|
|
||||||
// TODO: Replace with archived view: https://github.com/vector-im/element-web/issues/14038
|
// TODO: Replace with archived view: https://github.com/vector-im/element-web/issues/14038
|
||||||
[DefaultTagID.Archived]: {
|
[DefaultTagID.Archived]: {
|
||||||
sectionLabel: _td("Historical"),
|
sectionLabel: _td("common|historical"),
|
||||||
isInvite: false,
|
isInvite: false,
|
||||||
defaultHidden: true,
|
defaultHidden: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
[DefaultTagID.Suggested]: {
|
[DefaultTagID.Suggested]: {
|
||||||
sectionLabel: _td("Suggested Rooms"),
|
sectionLabel: _td("room_list|suggested_rooms_heading"),
|
||||||
isInvite: false,
|
isInvite: false,
|
||||||
defaultHidden: false,
|
defaultHidden: false,
|
||||||
},
|
},
|
||||||
|
@ -654,7 +648,7 @@ export default class RoomList extends React.PureComponent<IProps, IState> {
|
||||||
onKeyDown={onKeyDownHandler}
|
onKeyDown={onKeyDownHandler}
|
||||||
className="mx_RoomList"
|
className="mx_RoomList"
|
||||||
role="tree"
|
role="tree"
|
||||||
aria-label={_t("Rooms")}
|
aria-label={_t("common|rooms")}
|
||||||
ref={this.treeRef}
|
ref={this.treeRef}
|
||||||
>
|
>
|
||||||
{sublists}
|
{sublists}
|
||||||
|
|
|
@ -203,7 +203,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
<>
|
<>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_RoomListHeader_iconNewRoom"
|
iconClassName="mx_RoomListHeader_iconNewRoom"
|
||||||
label={_t("New room")}
|
label={_t("action|new_room")}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -215,7 +215,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
{videoRoomsEnabled && (
|
{videoRoomsEnabled && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
|
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
|
||||||
label={_t("New video room")}
|
label={_t("action|new_video_room")}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -243,7 +243,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
{inviteOption}
|
{inviteOption}
|
||||||
{newRoomOptions}
|
{newRoomOptions}
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Explore rooms")}
|
label={_t("action|explore_rooms")}
|
||||||
iconClassName="mx_RoomListHeader_iconExplore"
|
iconClassName="mx_RoomListHeader_iconExplore"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -258,7 +258,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Add existing room")}
|
label={_t("action|add_existing_room")}
|
||||||
iconClassName="mx_RoomListHeader_iconPlus"
|
iconClassName="mx_RoomListHeader_iconPlus"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -271,7 +271,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
/>
|
/>
|
||||||
{canCreateSpaces && (
|
{canCreateSpaces && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Add space")}
|
label={_t("room_list|add_space_label")}
|
||||||
iconClassName="mx_RoomListHeader_iconPlus"
|
iconClassName="mx_RoomListHeader_iconPlus"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -296,7 +296,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
newRoomOpts = (
|
newRoomOpts = (
|
||||||
<>
|
<>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Start new chat")}
|
label={_t("action|start_new_chat")}
|
||||||
iconClassName="mx_RoomListHeader_iconStartChat"
|
iconClassName="mx_RoomListHeader_iconStartChat"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -307,7 +307,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("New room")}
|
label={_t("action|new_room")}
|
||||||
iconClassName="mx_RoomListHeader_iconNewRoom"
|
iconClassName="mx_RoomListHeader_iconNewRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -319,7 +319,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
/>
|
/>
|
||||||
{videoRoomsEnabled && (
|
{videoRoomsEnabled && (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("New video room")}
|
label={_t("action|new_video_room")}
|
||||||
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
|
iconClassName="mx_RoomListHeader_iconNewVideoRoom"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -340,7 +340,7 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
if (canExploreRooms) {
|
if (canExploreRooms) {
|
||||||
joinRoomOpt = (
|
joinRoomOpt = (
|
||||||
<IconizedContextMenuOption
|
<IconizedContextMenuOption
|
||||||
label={_t("Join public room")}
|
label={_t("room_list|join_public_room_label")}
|
||||||
iconClassName="mx_RoomListHeader_iconExplore"
|
iconClassName="mx_RoomListHeader_iconExplore"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -379,9 +379,9 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
.map(([type, keys]) => {
|
.map(([type, keys]) => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PendingActionType.JoinRoom:
|
case PendingActionType.JoinRoom:
|
||||||
return _t("Currently joining %(count)s rooms", { count: keys.size });
|
return _t("room_list|joining_rooms_status", { count: keys.size });
|
||||||
case PendingActionType.BulkRedact:
|
case PendingActionType.BulkRedact:
|
||||||
return _t("Currently removing messages in %(count)s rooms", { count: keys.size });
|
return _t("room_list|redacting_messages_status", { count: keys.size });
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
@ -400,11 +400,11 @@ const RoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
|
||||||
contextMenuButton = (
|
contextMenuButton = (
|
||||||
<ContextMenuButton
|
<ContextMenuButton
|
||||||
{...commonProps}
|
{...commonProps}
|
||||||
label={_t("%(spaceName)s menu", { spaceName: spaceName ?? activeSpace.name })}
|
label={_t("room_list|space_menu_label", { spaceName: spaceName ?? activeSpace.name })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
contextMenuButton = <ContextMenuTooltipButton {...commonProps} title={_t("Home options")} />;
|
contextMenuButton = <ContextMenuTooltipButton {...commonProps} title={_t("room_list|home_menu_label")} />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
identityAccessToken!,
|
identityAccessToken!,
|
||||||
);
|
);
|
||||||
if (!("mxid" in result)) {
|
if (!("mxid" in result)) {
|
||||||
throw new UserFriendlyError("Unable to find user by email");
|
throw new UserFriendlyError("room|error_3pid_invite_email_lookup");
|
||||||
}
|
}
|
||||||
this.setState({ invitedEmailMxid: result.mxid });
|
this.setState({ invitedEmailMxid: result.mxid });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -329,9 +329,9 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
switch (messageCase) {
|
switch (messageCase) {
|
||||||
case MessageCase.Joining: {
|
case MessageCase.Joining: {
|
||||||
if (this.props.oobData?.roomType || isSpace) {
|
if (this.props.oobData?.roomType || isSpace) {
|
||||||
title = isSpace ? _t("Joining space…") : _t("Joining room…");
|
title = isSpace ? _t("room|joining_space") : _t("room|joining_room");
|
||||||
} else {
|
} else {
|
||||||
title = _t("Joining…");
|
title = _t("room|joining");
|
||||||
}
|
}
|
||||||
|
|
||||||
showSpinner = true;
|
showSpinner = true;
|
||||||
|
@ -343,7 +343,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MessageCase.Rejecting: {
|
case MessageCase.Rejecting: {
|
||||||
title = _t("Rejecting invite…");
|
title = _t("room|rejecting");
|
||||||
showSpinner = true;
|
showSpinner = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -353,15 +353,15 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
ModuleRunner.instance.invoke(RoomViewLifecycle.PreviewRoomNotLoggedIn, opts, this.props.roomId);
|
ModuleRunner.instance.invoke(RoomViewLifecycle.PreviewRoomNotLoggedIn, opts, this.props.roomId);
|
||||||
}
|
}
|
||||||
if (opts.canJoin) {
|
if (opts.canJoin) {
|
||||||
title = _t("Join the room to participate");
|
title = _t("room|join_title");
|
||||||
primaryActionLabel = _t("action|join");
|
primaryActionLabel = _t("action|join");
|
||||||
primaryActionHandler = () => {
|
primaryActionHandler = () => {
|
||||||
ModuleRunner.instance.invoke(RoomViewLifecycle.JoinFromRoomPreview, this.props.roomId);
|
ModuleRunner.instance.invoke(RoomViewLifecycle.JoinFromRoomPreview, this.props.roomId);
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
title = _t("Join the conversation with an account");
|
title = _t("room|join_title_account");
|
||||||
if (SettingsStore.getValue(UIFeature.Registration)) {
|
if (SettingsStore.getValue(UIFeature.Registration)) {
|
||||||
primaryActionLabel = _t("Sign Up");
|
primaryActionLabel = _t("room|join_button_account");
|
||||||
primaryActionHandler = this.onRegisterClick;
|
primaryActionHandler = this.onRegisterClick;
|
||||||
}
|
}
|
||||||
secondaryActionLabel = _t("action|sign_in");
|
secondaryActionLabel = _t("action|sign_in");
|
||||||
|
@ -371,7 +371,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
footer = (
|
footer = (
|
||||||
<div>
|
<div>
|
||||||
<Spinner w={20} h={20} />
|
<Spinner w={20} h={20} />
|
||||||
{_t("Loading preview")}
|
{_t("room|loading_preview")}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -380,16 +380,16 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
case MessageCase.Kicked: {
|
case MessageCase.Kicked: {
|
||||||
const { memberName, reason } = this.getKickOrBanInfo();
|
const { memberName, reason } = this.getKickOrBanInfo();
|
||||||
if (roomName) {
|
if (roomName) {
|
||||||
title = _t("You were removed from %(roomName)s by %(memberName)s", { memberName, roomName });
|
title = _t("room|kicked_from_room_by", { memberName, roomName });
|
||||||
} else {
|
} else {
|
||||||
title = _t("You were removed by %(memberName)s", { memberName });
|
title = _t("room|kicked_by", { memberName });
|
||||||
}
|
}
|
||||||
subTitle = reason ? _t("Reason: %(reason)s", { reason }) : undefined;
|
subTitle = reason ? _t("room|kick_reason", { reason }) : undefined;
|
||||||
|
|
||||||
if (isSpace) {
|
if (isSpace) {
|
||||||
primaryActionLabel = _t("Forget this space");
|
primaryActionLabel = _t("room|forget_space");
|
||||||
} else {
|
} else {
|
||||||
primaryActionLabel = _t("Forget this room");
|
primaryActionLabel = _t("room|forget_room");
|
||||||
}
|
}
|
||||||
primaryActionHandler = this.props.onForgetClick;
|
primaryActionHandler = this.props.onForgetClick;
|
||||||
|
|
||||||
|
@ -397,22 +397,20 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
secondaryActionLabel = primaryActionLabel;
|
secondaryActionLabel = primaryActionLabel;
|
||||||
secondaryActionHandler = primaryActionHandler;
|
secondaryActionHandler = primaryActionHandler;
|
||||||
|
|
||||||
primaryActionLabel = _t("Re-join");
|
primaryActionLabel = _t("room|rejoin_button");
|
||||||
primaryActionHandler = this.props.onJoinClick;
|
primaryActionHandler = this.props.onJoinClick;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MessageCase.RequestDenied: {
|
case MessageCase.RequestDenied: {
|
||||||
title = _t("You have been denied access");
|
title = _t("room|knock_denied_title");
|
||||||
|
|
||||||
subTitle = _t(
|
subTitle = _t("room|knock_denied_subtitle");
|
||||||
"As you have been denied access, you cannot rejoin unless you are invited by the admin or moderator of the group.",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isSpace) {
|
if (isSpace) {
|
||||||
primaryActionLabel = _t("Forget this space");
|
primaryActionLabel = _t("room|forget_space");
|
||||||
} else {
|
} else {
|
||||||
primaryActionLabel = _t("Forget this room");
|
primaryActionLabel = _t("room|forget_room");
|
||||||
}
|
}
|
||||||
primaryActionHandler = this.props.onForgetClick;
|
primaryActionHandler = this.props.onForgetClick;
|
||||||
break;
|
break;
|
||||||
|
@ -420,44 +418,43 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
case MessageCase.Banned: {
|
case MessageCase.Banned: {
|
||||||
const { memberName, reason } = this.getKickOrBanInfo();
|
const { memberName, reason } = this.getKickOrBanInfo();
|
||||||
if (roomName) {
|
if (roomName) {
|
||||||
title = _t("You were banned from %(roomName)s by %(memberName)s", { memberName, roomName });
|
title = _t("room|banned_from_room_by", { memberName, roomName });
|
||||||
} else {
|
} else {
|
||||||
title = _t("You were banned by %(memberName)s", { memberName });
|
title = _t("room|banned_by", { memberName });
|
||||||
}
|
}
|
||||||
subTitle = reason ? _t("Reason: %(reason)s", { reason }) : undefined;
|
subTitle = reason ? _t("room|kick_reason", { reason }) : undefined;
|
||||||
if (isSpace) {
|
if (isSpace) {
|
||||||
primaryActionLabel = _t("Forget this space");
|
primaryActionLabel = _t("room|forget_space");
|
||||||
} else {
|
} else {
|
||||||
primaryActionLabel = _t("Forget this room");
|
primaryActionLabel = _t("room|forget_room");
|
||||||
}
|
}
|
||||||
primaryActionHandler = this.props.onForgetClick;
|
primaryActionHandler = this.props.onForgetClick;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MessageCase.OtherThreePIDError: {
|
case MessageCase.OtherThreePIDError: {
|
||||||
if (roomName) {
|
if (roomName) {
|
||||||
title = _t("Something went wrong with your invite to %(roomName)s", { roomName });
|
title = _t("room|3pid_invite_error_title_room", { roomName });
|
||||||
} else {
|
} else {
|
||||||
title = _t("Something went wrong with your invite.");
|
title = _t("room|3pid_invite_error_title");
|
||||||
}
|
}
|
||||||
const joinRule = this.joinRule();
|
const joinRule = this.joinRule();
|
||||||
const errCodeMessage = _t(
|
const errCodeMessage = _t("room|3pid_invite_error_description", {
|
||||||
"An error (%(errcode)s) was returned while trying to validate your invite. You could try to pass this information on to the person who invited you.",
|
errcode: this.state.threePidFetchError?.errcode || _t("unknown error code"),
|
||||||
{ errcode: this.state.threePidFetchError?.errcode || _t("unknown error code") },
|
});
|
||||||
);
|
|
||||||
switch (joinRule) {
|
switch (joinRule) {
|
||||||
case "invite":
|
case "invite":
|
||||||
subTitle = [_t("You can only join it with a working invite."), errCodeMessage];
|
subTitle = [_t("room|3pid_invite_error_invite_subtitle"), errCodeMessage];
|
||||||
primaryActionLabel = _t("Try to join anyway");
|
primaryActionLabel = _t("room|3pid_invite_error_invite_action");
|
||||||
primaryActionHandler = this.props.onJoinClick;
|
primaryActionHandler = this.props.onJoinClick;
|
||||||
break;
|
break;
|
||||||
case "public":
|
case "public":
|
||||||
subTitle = _t("You can still join here.");
|
subTitle = _t("room|3pid_invite_error_public_subtitle");
|
||||||
primaryActionLabel = _t("Join the discussion");
|
primaryActionLabel = _t("room|join_the_discussion");
|
||||||
primaryActionHandler = this.props.onJoinClick;
|
primaryActionHandler = this.props.onJoinClick;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
subTitle = errCodeMessage;
|
subTitle = errCodeMessage;
|
||||||
primaryActionLabel = _t("Try to join anyway");
|
primaryActionLabel = _t("room|3pid_invite_error_invite_action");
|
||||||
primaryActionHandler = this.props.onJoinClick;
|
primaryActionHandler = this.props.onJoinClick;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -465,56 +462,50 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
case MessageCase.InvitedEmailNotFoundInAccount: {
|
case MessageCase.InvitedEmailNotFoundInAccount: {
|
||||||
if (roomName) {
|
if (roomName) {
|
||||||
title = _t(
|
title = _t("room|3pid_invite_email_not_found_account_room", {
|
||||||
"This invite to %(roomName)s was sent to %(email)s which is not associated with your account",
|
roomName,
|
||||||
{
|
email: this.props.invitedEmail,
|
||||||
roomName,
|
});
|
||||||
email: this.props.invitedEmail,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
title = _t("This invite was sent to %(email)s which is not associated with your account", {
|
title = _t("room|3pid_invite_email_not_found_account", {
|
||||||
email: this.props.invitedEmail,
|
email: this.props.invitedEmail,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
subTitle = _t(
|
subTitle = _t("room|link_email_to_receive_3pid_invite", { brand });
|
||||||
"Link this email with your account in Settings to receive invites directly in %(brand)s.",
|
primaryActionLabel = _t("room|join_the_discussion");
|
||||||
{ brand },
|
|
||||||
);
|
|
||||||
primaryActionLabel = _t("Join the discussion");
|
|
||||||
primaryActionHandler = this.props.onJoinClick;
|
primaryActionHandler = this.props.onJoinClick;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MessageCase.InvitedEmailNoIdentityServer: {
|
case MessageCase.InvitedEmailNoIdentityServer: {
|
||||||
if (roomName) {
|
if (roomName) {
|
||||||
title = _t("This invite to %(roomName)s was sent to %(email)s", {
|
title = _t("room|invite_sent_to_email_room", {
|
||||||
roomName,
|
roomName,
|
||||||
email: this.props.invitedEmail,
|
email: this.props.invitedEmail,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
title = _t("This invite was sent to %(email)s", { email: this.props.invitedEmail });
|
title = _t("room|invite_sent_to_email", { email: this.props.invitedEmail });
|
||||||
}
|
}
|
||||||
|
|
||||||
subTitle = _t("Use an identity server in Settings to receive invites directly in %(brand)s.", {
|
subTitle = _t("room|3pid_invite_no_is_subtitle", {
|
||||||
brand,
|
brand,
|
||||||
});
|
});
|
||||||
primaryActionLabel = _t("Join the discussion");
|
primaryActionLabel = _t("room|join_the_discussion");
|
||||||
primaryActionHandler = this.props.onJoinClick;
|
primaryActionHandler = this.props.onJoinClick;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MessageCase.InvitedEmailMismatch: {
|
case MessageCase.InvitedEmailMismatch: {
|
||||||
if (roomName) {
|
if (roomName) {
|
||||||
title = _t("This invite to %(roomName)s was sent to %(email)s", {
|
title = _t("room|invite_sent_to_email_room", {
|
||||||
roomName,
|
roomName,
|
||||||
email: this.props.invitedEmail,
|
email: this.props.invitedEmail,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
title = _t("This invite was sent to %(email)s", { email: this.props.invitedEmail });
|
title = _t("room|invite_sent_to_email", { email: this.props.invitedEmail });
|
||||||
}
|
}
|
||||||
|
|
||||||
subTitle = _t("Share this email in Settings to receive invites directly in %(brand)s.", { brand });
|
subTitle = _t("room|invite_email_mismatch_suggestion", { brand });
|
||||||
primaryActionLabel = _t("Join the discussion");
|
primaryActionLabel = _t("room|join_the_discussion");
|
||||||
primaryActionHandler = this.props.onJoinClick;
|
primaryActionHandler = this.props.onJoinClick;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -536,14 +527,14 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
const isDM = this.isDMInvite();
|
const isDM = this.isDMInvite();
|
||||||
if (isDM) {
|
if (isDM) {
|
||||||
title = _t("Do you want to chat with %(user)s?", {
|
title = _t("room|dm_invite_title", {
|
||||||
user: inviteMember?.name ?? this.props.inviterName,
|
user: inviteMember?.name ?? this.props.inviterName,
|
||||||
});
|
});
|
||||||
subTitle = [avatar, _t("<userName/> wants to chat", {}, { userName: () => inviterElement })];
|
subTitle = [avatar, _t("room|dm_invite_subtitle", {}, { userName: () => inviterElement })];
|
||||||
primaryActionLabel = _t("Start chatting");
|
primaryActionLabel = _t("room|dm_invite_action");
|
||||||
} else {
|
} else {
|
||||||
title = _t("Do you want to join %(roomName)s?", { roomName });
|
title = _t("room|invite_title", { roomName });
|
||||||
subTitle = [avatar, _t("<userName/> invited you", {}, { userName: () => inviterElement })];
|
subTitle = [avatar, _t("room|invite_subtitle", {}, { userName: () => inviterElement })];
|
||||||
primaryActionLabel = _t("action|accept");
|
primaryActionLabel = _t("action|accept");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,7 +558,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
if (this.props.onRejectAndIgnoreClick) {
|
if (this.props.onRejectAndIgnoreClick) {
|
||||||
extraComponents.push(
|
extraComponents.push(
|
||||||
<AccessibleButton kind="secondary" onClick={this.props.onRejectAndIgnoreClick} key="ignore">
|
<AccessibleButton kind="secondary" onClick={this.props.onRejectAndIgnoreClick} key="ignore">
|
||||||
{_t("Reject & Ignore user")}
|
{_t("room|invite_reject_ignore")}
|
||||||
</AccessibleButton>,
|
</AccessibleButton>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -575,35 +566,35 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
case MessageCase.ViewingRoom: {
|
case MessageCase.ViewingRoom: {
|
||||||
if (this.props.canPreview) {
|
if (this.props.canPreview) {
|
||||||
title = _t("You're previewing %(roomName)s. Want to join it?", { roomName });
|
title = _t("room|peek_join_prompt", { roomName });
|
||||||
} else if (roomName) {
|
} else if (roomName) {
|
||||||
title = _t("%(roomName)s can't be previewed. Do you want to join it?", { roomName });
|
title = _t("room|no_peek_join_prompt", { roomName });
|
||||||
} else {
|
} else {
|
||||||
title = _t("There's no preview, would you like to join?");
|
title = _t("room|no_peek_no_name_join_prompt");
|
||||||
}
|
}
|
||||||
primaryActionLabel = _t("Join the discussion");
|
primaryActionLabel = _t("room|join_the_discussion");
|
||||||
primaryActionHandler = this.props.onJoinClick;
|
primaryActionHandler = this.props.onJoinClick;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MessageCase.RoomNotFound: {
|
case MessageCase.RoomNotFound: {
|
||||||
if (roomName) {
|
if (roomName) {
|
||||||
title = _t("%(roomName)s does not exist.", { roomName });
|
title = _t("room|not_found_title_name", { roomName });
|
||||||
} else {
|
} else {
|
||||||
title = _t("This room or space does not exist.");
|
title = _t("room|not_found_title");
|
||||||
}
|
}
|
||||||
subTitle = _t("Are you sure you're at the right place?");
|
subTitle = _t("room|not_found_subtitle");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MessageCase.OtherError: {
|
case MessageCase.OtherError: {
|
||||||
if (roomName) {
|
if (roomName) {
|
||||||
title = _t("%(roomName)s is not accessible at this time.", { roomName });
|
title = _t("room|inaccessible_name", { roomName });
|
||||||
} else {
|
} else {
|
||||||
title = _t("This room or space is not accessible at this time.");
|
title = _t("room|inaccessible");
|
||||||
}
|
}
|
||||||
subTitle = [
|
subTitle = [
|
||||||
_t("Try again later, or ask a room or space admin to check if you have access."),
|
_t("room|inaccessible_subtitle_1"),
|
||||||
_t(
|
_t(
|
||||||
"%(errcode)s was returned while trying to access the room or space. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.",
|
"room|inaccessible_subtitle_2",
|
||||||
{ errcode: String(this.props.error?.errcode) },
|
{ errcode: String(this.props.error?.errcode) },
|
||||||
{
|
{
|
||||||
issueLink: (label) => (
|
issueLink: (label) => (
|
||||||
|
@ -622,18 +613,13 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
case MessageCase.PromptAskToJoin: {
|
case MessageCase.PromptAskToJoin: {
|
||||||
if (roomName) {
|
if (roomName) {
|
||||||
title = _t("Ask to join %(roomName)s?", { roomName });
|
title = _t("room|knock_prompt_name", { roomName });
|
||||||
} else {
|
} else {
|
||||||
title = _t("Ask to join?");
|
title = _t("room|knock_prompt");
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatar = <RoomAvatar room={this.props.room} oobData={this.props.oobData} />;
|
const avatar = <RoomAvatar room={this.props.room} oobData={this.props.oobData} />;
|
||||||
subTitle = [
|
subTitle = [avatar, _t("room|knock_subtitle")];
|
||||||
avatar,
|
|
||||||
_t(
|
|
||||||
"You need to be granted access to this room in order to view or participate in the conversation. You can send a request to join below.",
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
reasonElement = (
|
reasonElement = (
|
||||||
<Field
|
<Field
|
||||||
|
@ -641,7 +627,7 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
className="mx_RoomPreviewBar_fullWidth"
|
className="mx_RoomPreviewBar_fullWidth"
|
||||||
element="textarea"
|
element="textarea"
|
||||||
onChange={this.onChangeReason}
|
onChange={this.onChangeReason}
|
||||||
placeholder={_t("Message (optional)")}
|
placeholder={_t("room|knock_message_field_placeholder")}
|
||||||
type="text"
|
type="text"
|
||||||
value={this.state.reason ?? ""}
|
value={this.state.reason ?? ""}
|
||||||
/>
|
/>
|
||||||
|
@ -649,22 +635,22 @@ export default class RoomPreviewBar extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
primaryActionHandler = () =>
|
primaryActionHandler = () =>
|
||||||
this.props.onSubmitAskToJoin && this.props.onSubmitAskToJoin(this.state.reason);
|
this.props.onSubmitAskToJoin && this.props.onSubmitAskToJoin(this.state.reason);
|
||||||
primaryActionLabel = _t("Request access");
|
primaryActionLabel = _t("room|knock_send_action");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MessageCase.Knocked: {
|
case MessageCase.Knocked: {
|
||||||
title = _t("Request to join sent");
|
title = _t("room|knock_sent");
|
||||||
|
|
||||||
subTitle = [
|
subTitle = [
|
||||||
<>
|
<>
|
||||||
<AskToJoinIcon className="mx_Icon mx_Icon_16 mx_RoomPreviewBar_icon" />
|
<AskToJoinIcon className="mx_Icon mx_Icon_16 mx_RoomPreviewBar_icon" />
|
||||||
{_t("Your request to join is pending.")}
|
{_t("room|knock_sent_subtitle")}
|
||||||
</>,
|
</>,
|
||||||
];
|
];
|
||||||
|
|
||||||
secondaryActionHandler = this.props.onCancelAskToJoin;
|
secondaryActionHandler = this.props.onCancelAskToJoin;
|
||||||
secondaryActionLabel = _t("Cancel request");
|
secondaryActionLabel = _t("room|knock_cancel_action");
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,14 +173,14 @@ const RoomPreviewCard: FC<IProps> = ({ room, onJoinButtonClicked, onRejectButton
|
||||||
|
|
||||||
let notice: string | null = null;
|
let notice: string | null = null;
|
||||||
if (cannotJoin) {
|
if (cannotJoin) {
|
||||||
notice = _t("To view %(roomName)s, you need an invite", {
|
notice = _t("room|join_failed_needs_invite", {
|
||||||
roomName: room.name,
|
roomName: room.name,
|
||||||
});
|
});
|
||||||
} else if (isVideoRoom && !videoRoomsEnabled) {
|
} else if (isVideoRoom && !videoRoomsEnabled) {
|
||||||
notice =
|
notice =
|
||||||
myMembership === "join"
|
myMembership === "join"
|
||||||
? _t("To view, please enable video rooms in Labs first")
|
? _t("room|view_failed_enable_video_rooms")
|
||||||
: _t("To join, please enable video rooms in Labs first");
|
: _t("room|join_failed_enable_video_rooms");
|
||||||
|
|
||||||
joinButtons = (
|
joinButtons = (
|
||||||
<AccessibleButton kind="primary" onClick={viewLabs}>
|
<AccessibleButton kind="primary" onClick={viewLabs}>
|
||||||
|
|
|
@ -559,7 +559,7 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderMenu(): ReactNode {
|
private renderMenu(): ReactNode {
|
||||||
if (this.props.tagId === DefaultTagID.Suggested || this.props.tagId === DefaultTagID.SavedItems) return null; // not sortable
|
if (this.props.tagId === DefaultTagID.Suggested) return null; // not sortable
|
||||||
|
|
||||||
let contextMenu: JSX.Element | undefined;
|
let contextMenu: JSX.Element | undefined;
|
||||||
if (this.state.contextMenuPosition) {
|
if (this.state.contextMenuPosition) {
|
||||||
|
|
|
@ -342,7 +342,7 @@ export class RoomTile extends React.PureComponent<ClassProps, State> {
|
||||||
<ContextMenuTooltipButton
|
<ContextMenuTooltipButton
|
||||||
className="mx_RoomTile_menuButton"
|
className="mx_RoomTile_menuButton"
|
||||||
onClick={this.onGeneralMenuOpenClick}
|
onClick={this.onGeneralMenuOpenClick}
|
||||||
title={_t("Room options")}
|
title={_t("room|context_menu|title")}
|
||||||
isExpanded={!!this.state.generalMenuPosition}
|
isExpanded={!!this.state.generalMenuPosition}
|
||||||
/>
|
/>
|
||||||
{this.state.generalMenuPosition && (
|
{this.state.generalMenuPosition && (
|
||||||
|
|
|
@ -36,7 +36,7 @@ export const RoomTileCallSummary: FC<Props> = ({ call }) => {
|
||||||
active = false;
|
active = false;
|
||||||
break;
|
break;
|
||||||
case ConnectionState.Connecting:
|
case ConnectionState.Connecting:
|
||||||
text = _t("Joining…");
|
text = _t("room|joining");
|
||||||
active = true;
|
active = true;
|
||||||
break;
|
break;
|
||||||
case ConnectionState.Connected:
|
case ConnectionState.Connected:
|
||||||
|
|
|
@ -311,7 +311,11 @@ export default class SecureBackupPanel extends React.PureComponent<{}, IState> {
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{_t("settings|security|key_backup_active_version")}</th>
|
<th scope="row">{_t("settings|security|key_backup_active_version")}</th>
|
||||||
<td>{this.state.activeBackupVersion === null ? _t("None") : this.state.activeBackupVersion}</td>
|
<td>
|
||||||
|
{this.state.activeBackupVersion === null
|
||||||
|
? _t("settings|security|key_backup_active_version_none")
|
||||||
|
: this.state.activeBackupVersion}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -47,7 +47,7 @@ const REACHABILITY_TIMEOUT = 10000; // ms
|
||||||
async function checkIdentityServerUrl(u: string): Promise<string | null> {
|
async function checkIdentityServerUrl(u: string): Promise<string | null> {
|
||||||
const parsedUrl = parseUrl(u);
|
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
|
// 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
|
// 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) {
|
if (response.ok) {
|
||||||
return null;
|
return null;
|
||||||
} else if (response.status < 200 || response.status >= 300) {
|
} 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 {
|
} else {
|
||||||
return _t("Could not connect to identity server");
|
return _t("identity_server|error_connection");
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} 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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<InlineSpinner />
|
<InlineSpinner />
|
||||||
{_t("Checking server")}
|
{_t("identity_server|checking")}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.state.error) {
|
} else if (this.state.error) {
|
||||||
|
@ -191,9 +191,9 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||||
// 3PIDs that would be left behind.
|
// 3PIDs that would be left behind.
|
||||||
if (save && currentClientIdServer && fullUrl !== currentClientIdServer) {
|
if (save && currentClientIdServer && fullUrl !== currentClientIdServer) {
|
||||||
const [confirmed] = await this.showServerChangeWarning({
|
const [confirmed] = await this.showServerChangeWarning({
|
||||||
title: _t("Change identity server"),
|
title: _t("identity_server|change"),
|
||||||
unboundMessage: _t(
|
unboundMessage: _t(
|
||||||
"Disconnect from the identity server <current /> and connect to <new /> instead?",
|
"identity_server|change_prompt",
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
current: (sub) => <b>{abbreviateUrl(currentClientIdServer)}</b>,
|
current: (sub) => <b>{abbreviateUrl(currentClientIdServer)}</b>,
|
||||||
|
@ -210,7 +210,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(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({
|
this.setState({
|
||||||
|
@ -226,9 +226,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||||
title: _t("terms|identity_server_no_terms_title"),
|
title: _t("terms|identity_server_no_terms_title"),
|
||||||
description: (
|
description: (
|
||||||
<div>
|
<div>
|
||||||
<span className="warning">
|
<span className="warning">{_t("identity_server|no_terms")}</span>
|
||||||
{_t("The identity server you have chosen does not have any terms of service.")}
|
|
||||||
</span>
|
|
||||||
<span> {_t("terms|identity_server_no_terms_description_2")}</span>
|
<span> {_t("terms|identity_server_no_terms_description_2")}</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
@ -241,9 +239,9 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||||
this.setState({ disconnectBusy: true });
|
this.setState({ disconnectBusy: true });
|
||||||
try {
|
try {
|
||||||
const [confirmed] = await this.showServerChangeWarning({
|
const [confirmed] = await this.showServerChangeWarning({
|
||||||
title: _t("Disconnect identity server"),
|
title: _t("identity_server|disconnect"),
|
||||||
unboundMessage: _t(
|
unboundMessage: _t(
|
||||||
"Disconnect from the identity server <idserver />?",
|
"identity_server|disconnect_server",
|
||||||
{},
|
{},
|
||||||
{ idserver: (sub) => <b>{abbreviateUrl(this.state.currentClientIdServer)}</b> },
|
{ idserver: (sub) => <b>{abbreviateUrl(this.state.currentClientIdServer)}</b> },
|
||||||
),
|
),
|
||||||
|
@ -294,54 +292,34 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||||
if (!currentServerReachable) {
|
if (!currentServerReachable) {
|
||||||
message = (
|
message = (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>{_t("identity_server|disconnect_offline_warning", {}, messageElements)}</p>
|
||||||
{_t(
|
<p>{_t("identity_server|suggestions")}</p>
|
||||||
"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>
|
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>{_t("identity_server|suggestions_1")}</li>
|
||||||
<li>
|
<li>
|
||||||
{_t(
|
{_t(
|
||||||
"check your browser plugins for anything that might block the identity server (such as Privacy Badger)",
|
"identity_server|suggestions_2",
|
||||||
)}
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{_t(
|
|
||||||
"contact the administrators of identity server <idserver />",
|
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
idserver: messageElements.idserver,
|
idserver: messageElements.idserver,
|
||||||
},
|
},
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
<li>{_t("wait and try again later")}</li>
|
<li>{_t("identity_server|suggestions_3")}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
danger = true;
|
danger = true;
|
||||||
button = _t("Disconnect anyway");
|
button = _t("identity_server|disconnect_anyway");
|
||||||
} else if (boundThreepids.length) {
|
} else if (boundThreepids.length) {
|
||||||
message = (
|
message = (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>{_t("identity_server|disconnect_personal_data_warning_1", {}, messageElements)}</p>
|
||||||
{_t(
|
<p>{_t("identity_server|disconnect_personal_data_warning_2")}</p>
|
||||||
"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>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
danger = true;
|
danger = true;
|
||||||
button = _t("Disconnect anyway");
|
button = _t("identity_server|disconnect_anyway");
|
||||||
} else {
|
} else {
|
||||||
message = unboundMessage;
|
message = unboundMessage;
|
||||||
}
|
}
|
||||||
|
@ -382,37 +360,31 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||||
let sectionTitle;
|
let sectionTitle;
|
||||||
let bodyText;
|
let bodyText;
|
||||||
if (idServerUrl) {
|
if (idServerUrl) {
|
||||||
sectionTitle = _t("Identity server (%(server)s)", { server: abbreviateUrl(idServerUrl) });
|
sectionTitle = _t("identity_server|url", { server: abbreviateUrl(idServerUrl) });
|
||||||
bodyText = _t(
|
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> },
|
{ server: (sub) => <b>{abbreviateUrl(idServerUrl)}</b> },
|
||||||
);
|
);
|
||||||
if (this.props.missingTerms) {
|
if (this.props.missingTerms) {
|
||||||
bodyText = _t(
|
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> },
|
{ server: (sub) => <b>{abbreviateUrl(idServerUrl)}</b> },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sectionTitle = _t("common|identity_server");
|
sectionTitle = _t("common|identity_server");
|
||||||
bodyText = _t(
|
bodyText = _t("identity_server|description_disconnected");
|
||||||
"You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let discoSection;
|
let discoSection;
|
||||||
if (idServerUrl) {
|
if (idServerUrl) {
|
||||||
let discoButtonContent: React.ReactNode = _t("action|disconnect");
|
let discoButtonContent: React.ReactNode = _t("action|disconnect");
|
||||||
let discoBodyText = _t(
|
let discoBodyText = _t("identity_server|disconnect_warning");
|
||||||
"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.",
|
|
||||||
);
|
|
||||||
if (this.props.missingTerms) {
|
if (this.props.missingTerms) {
|
||||||
discoBodyText = _t(
|
discoBodyText = _t("identity_server|description_optional");
|
||||||
"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("identity_server|do_not_use");
|
||||||
);
|
|
||||||
discoButtonContent = _t("Do not use an identity server");
|
|
||||||
}
|
}
|
||||||
if (this.state.disconnectBusy) {
|
if (this.state.disconnectBusy) {
|
||||||
discoButtonContent = <InlineSpinner />;
|
discoButtonContent = <InlineSpinner />;
|
||||||
|
@ -431,7 +403,7 @@ export default class SetIdServer extends React.Component<IProps, IState> {
|
||||||
<SettingsFieldset legend={sectionTitle} description={bodyText}>
|
<SettingsFieldset legend={sectionTitle} description={bodyText}>
|
||||||
<form className="mx_SetIdServer" onSubmit={this.checkIdServer}>
|
<form className="mx_SetIdServer" onSubmit={this.checkIdServer}>
|
||||||
<Field
|
<Field
|
||||||
label={_t("Enter a new identity server")}
|
label={_t("identity_server|url_field_label")}
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
placeholder={this.state.defaultIdServer}
|
placeholder={this.state.defaultIdServer}
|
||||||
|
|
|
@ -63,12 +63,12 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
|
||||||
if (currentManager) {
|
if (currentManager) {
|
||||||
managerName = `(${currentManager.name})`;
|
managerName = `(${currentManager.name})`;
|
||||||
bodyText = _t(
|
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 },
|
{ serverName: currentManager.name },
|
||||||
{ b: (sub) => <b>{sub}</b> },
|
{ b: (sub) => <b>{sub}</b> },
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
bodyText = _t("Use an integration manager to manage bots, widgets, and sticker packs.");
|
bodyText = _t("integration_manager|use_im");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -79,7 +79,7 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
|
||||||
>
|
>
|
||||||
<div className="mx_SettingsFlag">
|
<div className="mx_SettingsFlag">
|
||||||
<div className="mx_SetIntegrationManager_heading_manager">
|
<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>
|
<Heading size="3">{managerName}</Heading>
|
||||||
</div>
|
</div>
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
|
@ -90,11 +90,7 @@ export default class SetIntegrationManager extends React.Component<IProps, IStat
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SettingsSubsectionText>{bodyText}</SettingsSubsectionText>
|
<SettingsSubsectionText>{bodyText}</SettingsSubsectionText>
|
||||||
<SettingsSubsectionText>
|
<SettingsSubsectionText>{_t("integration_manager|explainer")}</SettingsSubsectionText>
|
||||||
{_t(
|
|
||||||
"Integration managers receive configuration data, and can modify widgets, send room invites, and set power levels on your behalf.",
|
|
||||||
)}
|
|
||||||
</SettingsSubsectionText>
|
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ export class ExistingEmailAddress extends React.Component<IExistingEmailAddressP
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error("Unable to remove contact information: " + err);
|
logger.error("Unable to remove contact information: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
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"),
|
description: err && err.message ? err.message : _t("invite|failed_generic"),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -99,7 +99,7 @@ export class ExistingEmailAddress extends React.Component<IExistingEmailAddressP
|
||||||
return (
|
return (
|
||||||
<div className="mx_GeneralUserSettingsTab_section--discovery_existing">
|
<div className="mx_GeneralUserSettingsTab_section--discovery_existing">
|
||||||
<span className="mx_GeneralUserSettingsTab_section--discovery_existing_promptText">
|
<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>
|
</span>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={this.onActuallyRemove}
|
onClick={this.onActuallyRemove}
|
||||||
|
@ -182,8 +182,8 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||||
// TODO: Inline field validation
|
// TODO: Inline field validation
|
||||||
if (!Email.looksValid(email)) {
|
if (!Email.looksValid(email)) {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Invalid Email Address"),
|
title: _t("settings|general|error_invalid_email"),
|
||||||
description: _t("This doesn't appear to be a valid email address"),
|
description: _t("settings|general|error_invalid_email_detail"),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||||
logger.error("Unable to add email address " + email + " " + err);
|
logger.error("Unable to add email address " + email + " " + err);
|
||||||
this.setState({ verifying: false, continueDisabled: false, addTask: null });
|
this.setState({ verifying: false, continueDisabled: false, addTask: null });
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to add email address"),
|
title: _t("settings|general|error_add_email"),
|
||||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
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") {
|
if (underlyingError instanceof MatrixError && underlyingError.errcode === "M_THREEPID_AUTH_FAILED") {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Your email address hasn't been verified yet"),
|
title: _t("settings|general|email_not_verified"),
|
||||||
description: _t(
|
description: _t("settings|general|email_verification_instructions"),
|
||||||
"Click the link in the email you received to verify and then click continue again.",
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to verify email address."),
|
title: _t("settings|general|error_email_verification"),
|
||||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -273,11 +271,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||||
if (this.state.verifying) {
|
if (this.state.verifying) {
|
||||||
addButton = (
|
addButton = (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>{_t("settings|general|add_email_instructions")}</div>
|
||||||
{_t(
|
|
||||||
"We've sent you an email to verify your address. Please follow the instructions there and then click the button below.",
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={this.onContinueClick}
|
onClick={this.onContinueClick}
|
||||||
kind="primary"
|
kind="primary"
|
||||||
|
@ -295,7 +289,7 @@ export default class EmailAddresses extends React.Component<IProps, IState> {
|
||||||
<form onSubmit={this.onAddClick} autoComplete="off" noValidate={true}>
|
<form onSubmit={this.onAddClick} autoComplete="off" noValidate={true}>
|
||||||
<Field
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Email Address")}
|
label={_t("settings|general|email_address_label")}
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
disabled={this.props.disabled || this.state.verifying}
|
disabled={this.props.disabled || this.state.verifying}
|
||||||
value={this.state.newEmailAddress}
|
value={this.state.newEmailAddress}
|
||||||
|
|
|
@ -84,7 +84,7 @@ export class ExistingPhoneNumber extends React.Component<IExistingPhoneNumberPro
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error("Unable to remove contact information: " + err);
|
logger.error("Unable to remove contact information: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to remove contact information"),
|
title: _t("settings|general|error_remove_3pid"),
|
||||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -95,7 +95,7 @@ export class ExistingPhoneNumber extends React.Component<IExistingPhoneNumberPro
|
||||||
return (
|
return (
|
||||||
<div className="mx_GeneralUserSettingsTab_section--discovery_existing">
|
<div className="mx_GeneralUserSettingsTab_section--discovery_existing">
|
||||||
<span className="mx_GeneralUserSettingsTab_section--discovery_existing_promptText">
|
<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>
|
</span>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
onClick={this.onActuallyRemove}
|
onClick={this.onActuallyRemove}
|
||||||
|
@ -244,11 +244,11 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
|
||||||
|
|
||||||
if (underlyingError.errcode !== "M_THREEPID_AUTH_FAILED") {
|
if (underlyingError.errcode !== "M_THREEPID_AUTH_FAILED") {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to verify phone number."),
|
title: _t("settings|general|error_msisdn_verification"),
|
||||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||||
});
|
});
|
||||||
} else {
|
} 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 = (
|
addVerifySection = (
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
{_t(
|
{_t("settings|general|add_msisdn_instructions", { msisdn: msisdn })}
|
||||||
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.",
|
|
||||||
{ msisdn: msisdn },
|
|
||||||
)}
|
|
||||||
<br />
|
<br />
|
||||||
{this.state.verifyError}
|
{this.state.verifyError}
|
||||||
</div>
|
</div>
|
||||||
<form onSubmit={this.onContinueClick} autoComplete="off" noValidate={true}>
|
<form onSubmit={this.onContinueClick} autoComplete="off" noValidate={true}>
|
||||||
<Field
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Verification code")}
|
label={_t("settings|general|msisdn_verification_field_label")}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
disabled={this.props.disabled || this.state.continueDisabled}
|
disabled={this.props.disabled || this.state.continueDisabled}
|
||||||
value={this.state.newPhoneNumberCode}
|
value={this.state.newPhoneNumberCode}
|
||||||
|
@ -329,7 +326,7 @@ export default class PhoneNumbers extends React.Component<IProps, IState> {
|
||||||
<div className="mx_PhoneNumbers_input">
|
<div className="mx_PhoneNumbers_input">
|
||||||
<Field
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Phone Number")}
|
label={_t("settings|general|msisdn_label")}
|
||||||
autoComplete="tel-national"
|
autoComplete="tel-national"
|
||||||
disabled={this.props.disabled || this.state.verifying}
|
disabled={this.props.disabled || this.state.verifying}
|
||||||
prefixComponent={phoneCountry}
|
prefixComponent={phoneCountry}
|
||||||
|
|
|
@ -49,7 +49,7 @@ const DeviceNameEditor: React.FC<Props & { stopEditing: () => void }> = ({ devic
|
||||||
await saveDeviceName(deviceName);
|
await saveDeviceName(deviceName);
|
||||||
stopEditing();
|
stopEditing();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError(_t("Failed to set display name"));
|
setError(_t("settings|sessions|error_set_name"));
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
IServerVersions,
|
IServerVersions,
|
||||||
UNSTABLE_MSC3882_CAPABILITY,
|
UNSTABLE_MSC3882_CAPABILITY,
|
||||||
Capabilities,
|
Capabilities,
|
||||||
|
IClientWellKnown,
|
||||||
} from "matrix-js-sdk/src/matrix";
|
} from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
import { _t } from "../../../../languageHandler";
|
import { _t } from "../../../../languageHandler";
|
||||||
|
@ -30,6 +31,7 @@ interface IProps {
|
||||||
onShowQr: () => void;
|
onShowQr: () => void;
|
||||||
versions?: IServerVersions;
|
versions?: IServerVersions;
|
||||||
capabilities?: Capabilities;
|
capabilities?: Capabilities;
|
||||||
|
wellKnown?: IClientWellKnown;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class LoginWithQRSection extends React.Component<IProps> {
|
export default class LoginWithQRSection extends React.Component<IProps> {
|
||||||
|
@ -43,7 +45,9 @@ export default class LoginWithQRSection extends React.Component<IProps> {
|
||||||
const capability = UNSTABLE_MSC3882_CAPABILITY.findIn<IMSC3882GetLoginTokenCapability>(this.props.capabilities);
|
const capability = UNSTABLE_MSC3882_CAPABILITY.findIn<IMSC3882GetLoginTokenCapability>(this.props.capabilities);
|
||||||
const msc3882Supported =
|
const msc3882Supported =
|
||||||
!!this.props.versions?.unstable_features?.["org.matrix.msc3882"] || !!capability?.enabled;
|
!!this.props.versions?.unstable_features?.["org.matrix.msc3882"] || !!capability?.enabled;
|
||||||
const msc3886Supported = !!this.props.versions?.unstable_features?.["org.matrix.msc3886"];
|
const msc3886Supported =
|
||||||
|
!!this.props.versions?.unstable_features?.["org.matrix.msc3886"] ||
|
||||||
|
this.props.wellKnown?.["io.element.rendezvous"]?.server;
|
||||||
const offerShowQr = msc3882Supported && msc3886Supported;
|
const offerShowQr = msc3882Supported && msc3886Supported;
|
||||||
|
|
||||||
// don't show anything if no method is available
|
// don't show anything if no method is available
|
||||||
|
|
|
@ -71,7 +71,7 @@ export const deleteDevicesWithInteractiveAuth = async (
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
Modal.createDialog(InteractiveAuthDialog, {
|
Modal.createDialog(InteractiveAuthDialog, {
|
||||||
title: _t("Authentication"),
|
title: _t("common|authentication"),
|
||||||
matrixClient: matrixClient,
|
matrixClient: matrixClient,
|
||||||
authData: error.data as IAuthData,
|
authData: error.data as IAuthData,
|
||||||
onFinished,
|
onFinished,
|
||||||
|
|
|
@ -194,8 +194,8 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
await matrixClient.setDeviceDetails(deviceId, { display_name: deviceName });
|
await matrixClient.setDeviceDetails(deviceId, { display_name: deviceName });
|
||||||
await refreshDevices();
|
await refreshDevices();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error setting session display name", error);
|
logger.error("Error setting device name", error);
|
||||||
throw new Error(_t("Failed to set display name"));
|
throw new Error(_t("settings|sessions|error_set_name"));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[matrixClient, devices, refreshDevices],
|
[matrixClient, devices, refreshDevices],
|
||||||
|
@ -217,7 +217,7 @@ export const useOwnDevices = (): DevicesState => {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error setting pusher state", 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 {
|
} finally {
|
||||||
await refreshDevices();
|
await refreshDevices();
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||||
this.changeBinding({
|
this.changeBinding({
|
||||||
bind: false,
|
bind: false,
|
||||||
label: "revoke",
|
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({
|
this.changeBinding({
|
||||||
bind: true,
|
bind: true,
|
||||||
label: "share",
|
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") {
|
if (underlyingError instanceof MatrixError && underlyingError.errcode === "M_THREEPID_AUTH_FAILED") {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Your email address hasn't been verified yet"),
|
title: _t("settings|general|email_not_verified"),
|
||||||
description: _t(
|
description: _t("settings|general|email_verification_instructions"),
|
||||||
"Click the link in the email you received to verify and then click continue again.",
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.error("Unable to verify email address: " + err);
|
logger.error("Unable to verify email address: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to verify email address."),
|
title: _t("settings|general|error_email_verification"),
|
||||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -178,7 +176,7 @@ export class EmailAddress extends React.Component<IEmailAddressProps, IEmailAddr
|
||||||
if (verifying) {
|
if (verifying) {
|
||||||
status = (
|
status = (
|
||||||
<span>
|
<span>
|
||||||
{_t("Verify the link in your inbox")}
|
{_t("settings|general|discovery_email_verification_instructions")}
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
className="mx_GeneralUserSettingsTab_section--discovery_existing_button"
|
className="mx_GeneralUserSettingsTab_section--discovery_existing_button"
|
||||||
kind="primary_sm"
|
kind="primary_sm"
|
||||||
|
@ -242,10 +240,8 @@ export default class EmailAddresses extends React.Component<IProps> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
heading={_t("Email addresses")}
|
heading={_t("settings|general|emails_heading")}
|
||||||
description={
|
description={(!hasEmails && _t("settings|general|discovery_email_empty")) || undefined}
|
||||||
(!hasEmails && _t("Discovery options will appear once you have added an email above.")) || undefined
|
|
||||||
}
|
|
||||||
stretchContent
|
stretchContent
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
|
|
|
@ -117,7 +117,7 @@ export class PhoneNumber extends React.Component<IPhoneNumberProps, IPhoneNumber
|
||||||
this.changeBinding({
|
this.changeBinding({
|
||||||
bind: false,
|
bind: false,
|
||||||
label: "revoke",
|
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({
|
this.changeBinding({
|
||||||
bind: true,
|
bind: true,
|
||||||
label: "share",
|
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 });
|
this.setState({ continueDisabled: false });
|
||||||
if (underlyingError instanceof MatrixError && underlyingError.errcode !== "M_THREEPID_AUTH_FAILED") {
|
if (underlyingError instanceof MatrixError && underlyingError.errcode !== "M_THREEPID_AUTH_FAILED") {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Unable to verify phone number."),
|
title: _t("settings|general|error_msisdn_verification"),
|
||||||
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
description: extractErrorMessageFromError(err, _t("invite|failed_generic")),
|
||||||
});
|
});
|
||||||
} else {
|
} 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 = (
|
status = (
|
||||||
<span className="mx_GeneralUserSettingsTab_section--discovery_existing_verification">
|
<span className="mx_GeneralUserSettingsTab_section--discovery_existing_verification">
|
||||||
<span>
|
<span>
|
||||||
{_t("Please enter verification code sent via text.")}
|
{_t("settings|general|msisdn_verification_instructions")}
|
||||||
<br />
|
<br />
|
||||||
{this.state.verifyError}
|
{this.state.verifyError}
|
||||||
</span>
|
</span>
|
||||||
<form onSubmit={this.onContinueClick} autoComplete="off" noValidate={true}>
|
<form onSubmit={this.onContinueClick} autoComplete="off" noValidate={true}>
|
||||||
<Field
|
<Field
|
||||||
type="text"
|
type="text"
|
||||||
label={_t("Verification code")}
|
label={_t("settings|general|msisdn_verification_field_label")}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
disabled={this.state.continueDisabled}
|
disabled={this.state.continueDisabled}
|
||||||
value={this.state.verificationCode}
|
value={this.state.verificationCode}
|
||||||
|
@ -247,13 +247,12 @@ export default class PhoneNumbers extends React.Component<IProps> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const description =
|
const description = (!content && _t("settings|general|discovery_msisdn_empty")) || undefined;
|
||||||
(!content && _t("Discovery options will appear once you have added a phone number above.")) || undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
data-testid="mx_DiscoveryPhoneNumbers"
|
data-testid="mx_DiscoveryPhoneNumbers"
|
||||||
heading={_t("Phone numbers")}
|
heading={_t("settings|general|msisdns_heading")}
|
||||||
description={description}
|
description={description}
|
||||||
stretchContent
|
stretchContent
|
||||||
>
|
>
|
||||||
|
|
|
@ -51,7 +51,7 @@ export function NotificationPusherSettings(): JSX.Element {
|
||||||
() => ({
|
() => ({
|
||||||
kind: "email",
|
kind: "email",
|
||||||
app_id: "m.email",
|
app_id: "m.email",
|
||||||
app_display_name: _t("Email Notifications"),
|
app_display_name: _t("notifications|email_pusher_app_display_name"),
|
||||||
lang: navigator.language,
|
lang: navigator.language,
|
||||||
data: {
|
data: {
|
||||||
brand: SdkConfig.get().brand,
|
brand: SdkConfig.get().brand,
|
||||||
|
@ -91,17 +91,16 @@ export function NotificationPusherSettings(): JSX.Element {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingsSubsection className="mx_NotificationPusherSettings" heading={_t("Email summary")}>
|
<SettingsSubsection
|
||||||
|
className="mx_NotificationPusherSettings"
|
||||||
|
heading={_t("settings|notifications|email_section")}
|
||||||
|
>
|
||||||
<SettingsSubsectionText className="mx_NotificationPusherSettings_description">
|
<SettingsSubsectionText className="mx_NotificationPusherSettings_description">
|
||||||
{_t("Receive an email summary of missed notifications")}
|
{_t("settings|notifications|email_description")}
|
||||||
</SettingsSubsectionText>
|
</SettingsSubsectionText>
|
||||||
<div className="mx_SettingsSubsection_description mx_NotificationPusherSettings_detail">
|
<div className="mx_SettingsSubsection_description mx_NotificationPusherSettings_detail">
|
||||||
<SettingsSubsectionText>
|
<SettingsSubsectionText>
|
||||||
{_t(
|
{_t("settings|notifications|email_select", {}, { button: generalTabButton })}
|
||||||
"Select which emails you want to send summaries to. Manage your emails in <button>General</button>.",
|
|
||||||
{},
|
|
||||||
{ button: generalTabButton },
|
|
||||||
)}
|
|
||||||
</SettingsSubsectionText>
|
</SettingsSubsectionText>
|
||||||
</div>
|
</div>
|
||||||
<SettingsIndent>
|
<SettingsIndent>
|
||||||
|
|
|
@ -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 {
|
function boldText(text: string): JSX.Element {
|
||||||
return <strong>{text}</strong>;
|
return <strong>{text}</strong>;
|
||||||
}
|
}
|
||||||
|
@ -101,6 +86,21 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
const [updatingUnread, setUpdatingUnread] = useState<boolean>(false);
|
const [updatingUnread, setUpdatingUnread] = useState<boolean>(false);
|
||||||
const hasUnreadNotifications = useHasUnreadNotifications();
|
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 (
|
return (
|
||||||
<div className="mx_NotificationSettings2">
|
<div className="mx_NotificationSettings2">
|
||||||
{hasPendingChanges && model !== null && (
|
{hasPendingChanges && model !== null && (
|
||||||
|
@ -110,7 +110,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
onAction={() => reconcile(model!)}
|
onAction={() => reconcile(model!)}
|
||||||
>
|
>
|
||||||
{_t(
|
{_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,
|
strong: boldText,
|
||||||
|
@ -140,7 +140,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
label={_t("Show message preview in desktop notification")}
|
label={_t("settings|notifications|desktop_notification_message_preview")}
|
||||||
value={desktopShowBody}
|
value={desktopShowBody}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
SettingsStore.setValue("notificationBodyEnabled", null, SettingLevel.DEVICE, value)
|
SettingsStore.setValue("notificationBodyEnabled", null, SettingLevel.DEVICE, value)
|
||||||
|
@ -155,8 +155,8 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
heading={_t("I want to be notified for (Default Setting)")}
|
heading={_t("settings|notifications|default_setting_section")}
|
||||||
description={_t("This setting will be applied by default to all your rooms.")}
|
description={_t("settings|notifications|default_setting_description")}
|
||||||
>
|
>
|
||||||
<StyledRadioGroup
|
<StyledRadioGroup
|
||||||
name="defaultNotificationLevel"
|
name="defaultNotificationLevel"
|
||||||
|
@ -182,8 +182,8 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
heading={_t("Play a sound for")}
|
heading={_t("settings|notifications|play_sound_for_section")}
|
||||||
description={_t("Applied by default to all rooms on all devices.")}
|
description={_t("settings|notifications|play_sound_for_description")}
|
||||||
>
|
>
|
||||||
<LabelledCheckbox
|
<LabelledCheckbox
|
||||||
label="People"
|
label="People"
|
||||||
|
@ -200,7 +200,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LabelledCheckbox
|
<LabelledCheckbox
|
||||||
label={_t("Mentions and Keywords")}
|
label={_t("settings|notifications|mentions_keywords")}
|
||||||
value={settings.sound.mentions !== undefined}
|
value={settings.sound.mentions !== undefined}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
@ -214,7 +214,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LabelledCheckbox
|
<LabelledCheckbox
|
||||||
label={_t("Audio and Video calls")}
|
label={_t("settings|notifications|voip")}
|
||||||
value={settings.sound.calls !== undefined}
|
value={settings.sound.calls !== undefined}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
@ -228,9 +228,9 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
<SettingsSubsection heading={_t("Other things we think you might be interested in:")}>
|
<SettingsSubsection heading={_t("settings|notifications|other_section")}>
|
||||||
<LabelledCheckbox
|
<LabelledCheckbox
|
||||||
label={_t("Invited to a room")}
|
label={_t("settings|notifications|invites")}
|
||||||
value={settings.activity.invite}
|
value={settings.activity.invite}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
@ -244,7 +244,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LabelledCheckbox
|
<LabelledCheckbox
|
||||||
label={_t("New room activity, upgrades and status messages occur")}
|
label={_t("settings|notifications|room_activity")}
|
||||||
value={settings.activity.status_event}
|
value={settings.activity.status_event}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
@ -258,7 +258,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LabelledCheckbox
|
<LabelledCheckbox
|
||||||
label={_t("Messages sent by bots")}
|
label={_t("settings|notifications|notices")}
|
||||||
value={settings.activity.bot_notices}
|
value={settings.activity.bot_notices}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
@ -273,9 +273,9 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
heading={_t("Mentions and Keywords")}
|
heading={_t("settings|notifications|mentions_keywords")}
|
||||||
description={_t(
|
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} />,
|
badge: <StatelessNotificationBadge symbol="1" count={1} color={NotificationColor.Grey} />,
|
||||||
|
@ -283,7 +283,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<LabelledCheckbox
|
<LabelledCheckbox
|
||||||
label={_t("Notify when someone mentions using @room")}
|
label={_t("settings|notifications|notify_at_room")}
|
||||||
value={settings.mentions.room}
|
value={settings.mentions.room}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
@ -297,7 +297,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LabelledCheckbox
|
<LabelledCheckbox
|
||||||
label={_t("Notify when someone mentions using @displayname or %(mxid)s", {
|
label={_t("settings|notifications|notify_mention", {
|
||||||
mxid: cli.getUserId()!,
|
mxid: cli.getUserId()!,
|
||||||
})}
|
})}
|
||||||
value={settings.mentions.user}
|
value={settings.mentions.user}
|
||||||
|
@ -313,8 +313,8 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<LabelledCheckbox
|
<LabelledCheckbox
|
||||||
label={_t("Notify when someone uses a keyword")}
|
label={_t("settings|notifications|notify_keyword")}
|
||||||
byline={_t("Enter keywords here, or use for spelling variations or nicknames")}
|
byline={_t("settings|notifications|keywords_prompt")}
|
||||||
value={settings.mentions.keywords}
|
value={settings.mentions.keywords}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
@ -348,7 +348,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
/>
|
/>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
<NotificationPusherSettings />
|
<NotificationPusherSettings />
|
||||||
<SettingsSubsection heading={_t("Quick Actions")}>
|
<SettingsSubsection heading={_t("settings|notifications|quick_actions_section")}>
|
||||||
{hasUnreadNotifications && (
|
{hasUnreadNotifications && (
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
kind="primary_outline"
|
kind="primary_outline"
|
||||||
|
@ -359,7 +359,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
setUpdatingUnread(false);
|
setUpdatingUnread(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_t("Mark all messages as read")}
|
{_t("settings|notifications|quick_actions_mark_all_read")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
)}
|
)}
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
@ -369,7 +369,7 @@ export default function NotificationSettings2(): JSX.Element {
|
||||||
reconcile(DefaultNotificationSettings);
|
reconcile(DefaultNotificationSettings);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_t("Reset to default settings")}
|
{_t("settings|notifications|quick_actions_reset")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
|
@ -156,7 +156,13 @@ export default class AdvancedRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
return (
|
return (
|
||||||
<SettingsTab>
|
<SettingsTab>
|
||||||
<SettingsSection heading={_t("common|advanced")}>
|
<SettingsSection heading={_t("common|advanced")}>
|
||||||
<SettingsSubsection heading={room.isSpaceRoom() ? _t("Space information") : _t("Room information")}>
|
<SettingsSubsection
|
||||||
|
heading={
|
||||||
|
room.isSpaceRoom()
|
||||||
|
? _t("room_settings|advanced|information_section_space")
|
||||||
|
: _t("room_settings|advanced|information_section_room")
|
||||||
|
}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<span>{_t("room_settings|advanced|room_id")}</span>
|
<span>{_t("room_settings|advanced|room_id")}</span>
|
||||||
<CopyableText getTextToCopy={() => this.props.room.roomId}>
|
<CopyableText getTextToCopy={() => this.props.room.roomId}>
|
||||||
|
|
|
@ -63,7 +63,7 @@ export default class BridgeSettingsTab extends React.Component<IProps> {
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
{_t(
|
{_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
|
// 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 = (
|
content = (
|
||||||
<p>
|
<p>
|
||||||
{_t(
|
{_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
|
// 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 (
|
return (
|
||||||
<SettingsTab>
|
<SettingsTab>
|
||||||
<SettingsSection heading={_t("Bridges")}>{content}</SettingsSection>
|
<SettingsSection heading={_t("room_settings|bridges|title")}>{content}</SettingsSection>
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
|
||||||
<RoomProfileSettings roomId={room.roomId} />
|
<RoomProfileSettings roomId={room.roomId} />
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<SettingsSection heading={_t("Room Addresses")}>
|
<SettingsSection heading={_t("room_settings|general|aliases_section")}>
|
||||||
<AliasSettings
|
<AliasSettings
|
||||||
roomId={room.roomId}
|
roomId={room.roomId}
|
||||||
canSetCanonicalAlias={canSetCanonical}
|
canSetCanonicalAlias={canSetCanonical}
|
||||||
|
@ -98,7 +98,7 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
|
||||||
/>
|
/>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<SettingsSection heading={_t("Other")}>
|
<SettingsSection heading={_t("room_settings|general|other_section")}>
|
||||||
{urlPreviewSettings}
|
{urlPreviewSettings}
|
||||||
{leaveSection}
|
{leaveSection}
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
|
@ -163,7 +163,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
currentUploadedFile = (
|
currentUploadedFile = (
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span>
|
||||||
{_t("Uploaded sound")}: <code>{this.state.uploadedFile.name}</code>
|
{_t("room_settings|notifications|uploaded_sound")}: <code>{this.state.uploadedFile.name}</code>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -181,10 +181,10 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
className: "mx_NotificationSettingsTab_defaultEntry",
|
className: "mx_NotificationSettingsTab_defaultEntry",
|
||||||
label: (
|
label: (
|
||||||
<>
|
<>
|
||||||
{_t("Default")}
|
{_t("notifications|default")}
|
||||||
<div className="mx_NotificationSettingsTab_microCopy">
|
<div className="mx_NotificationSettingsTab_microCopy">
|
||||||
{_t(
|
{_t(
|
||||||
"Get notifications as set up in your <a>settings</a>",
|
"room_settings|notifications|settings_link",
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
a: (sub) => (
|
a: (sub) => (
|
||||||
|
@ -206,9 +206,9 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
className: "mx_NotificationSettingsTab_allMessagesEntry",
|
className: "mx_NotificationSettingsTab_allMessagesEntry",
|
||||||
label: (
|
label: (
|
||||||
<>
|
<>
|
||||||
{_t("All messages")}
|
{_t("notifications|all_messages")}
|
||||||
<div className="mx_NotificationSettingsTab_microCopy">
|
<div className="mx_NotificationSettingsTab_microCopy">
|
||||||
{_t("Get notified for every message")}
|
{_t("notifications|all_messages_description")}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
@ -218,10 +218,10 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
className: "mx_NotificationSettingsTab_mentionsKeywordsEntry",
|
className: "mx_NotificationSettingsTab_mentionsKeywordsEntry",
|
||||||
label: (
|
label: (
|
||||||
<>
|
<>
|
||||||
{_t("@mentions & keywords")}
|
{_t("notifications|mentions_and_keywords")}
|
||||||
<div className="mx_NotificationSettingsTab_microCopy">
|
<div className="mx_NotificationSettingsTab_microCopy">
|
||||||
{_t(
|
{_t(
|
||||||
"Get notified only with mentions and keywords as set up in your <a>settings</a>",
|
"notifications|mentions_and_keywords_description",
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
a: (sub) => (
|
a: (sub) => (
|
||||||
|
@ -245,7 +245,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
<>
|
<>
|
||||||
{_t("common|off")}
|
{_t("common|off")}
|
||||||
<div className="mx_NotificationSettingsTab_microCopy">
|
<div className="mx_NotificationSettingsTab_microCopy">
|
||||||
{_t("You won't get any notifications")}
|
{_t("notifications|mute_description")}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
@ -256,11 +256,12 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SettingsSubsection heading={_t("Sounds")}>
|
<SettingsSubsection heading={_t("room_settings|notifications|sounds_section")}>
|
||||||
<div>
|
<div>
|
||||||
<div className="mx_SettingsTab_subsectionText">
|
<div className="mx_SettingsTab_subsectionText">
|
||||||
<span>
|
<span>
|
||||||
{_t("Notification sound")}: <code>{this.state.currentSound}</code>
|
{_t("room_settings|notifications|notification_sound")}:{" "}
|
||||||
|
<code>{this.state.currentSound}</code>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
@ -273,7 +274,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
<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">
|
<div className="mx_SettingsFlag">
|
||||||
<form autoComplete="off" noValidate={true}>
|
<form autoComplete="off" noValidate={true}>
|
||||||
<input
|
<input
|
||||||
|
@ -283,7 +284,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
onClick={chromeFileInputFix}
|
onClick={chromeFileInputFix}
|
||||||
onChange={this.onSoundUploadChanged}
|
onChange={this.onSoundUploadChanged}
|
||||||
accept="audio/*"
|
accept="audio/*"
|
||||||
aria-label={_t("Upload custom sound")}
|
aria-label={_t("room_settings|notifications|upload_sound_label")}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
@ -295,7 +296,7 @@ export default class NotificationsSettingsTab extends React.Component<IProps, IS
|
||||||
onClick={this.triggerUploader}
|
onClick={this.triggerUploader}
|
||||||
kind="primary"
|
kind="primary"
|
||||||
>
|
>
|
||||||
{_t("Browse")}
|
{_t("room_settings|notifications|browse_button")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
|
|
||||||
<AccessibleButton
|
<AccessibleButton
|
||||||
|
|
|
@ -52,7 +52,7 @@ const SeeMoreOrLess: VFC<{ roomMember: RoomMember }> = ({ roomMember }) => {
|
||||||
</p>
|
</p>
|
||||||
{shouldTruncate && (
|
{shouldTruncate && (
|
||||||
<AccessibleButton kind="link" onClick={() => setSeeMore(!seeMore)}>
|
<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>
|
</AccessibleButton>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -151,7 +151,7 @@ export const PeopleRoomSettingsTab: VFC<{ room: Room }> = ({ room }) => {
|
||||||
return (
|
return (
|
||||||
<SettingsTab>
|
<SettingsTab>
|
||||||
<SettingsSection heading={_t("common|people")}>
|
<SettingsSection heading={_t("common|people")}>
|
||||||
<SettingsFieldset legend={_t("Asking to join")}>
|
<SettingsFieldset legend={_t("room_settings|people|knock_section")}>
|
||||||
{knockMembers.length ? (
|
{knockMembers.length ? (
|
||||||
knockMembers.map((knockMember) => (
|
knockMembers.map((knockMember) => (
|
||||||
<Knock
|
<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>
|
</SettingsFieldset>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
|
@ -95,7 +95,7 @@ export class BannedUser extends React.Component<IBannedUserProps> {
|
||||||
logger.error("Failed to unban: " + err);
|
logger.error("Failed to unban: " + err);
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("common|error"),
|
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 (
|
return (
|
||||||
<li>
|
<li>
|
||||||
{unbanButton}
|
{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}
|
<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>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
@ -205,10 +207,8 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Error changing power level requirement"),
|
title: _t("room_settings|permissions|error_changing_pl_reqs_title"),
|
||||||
description: _t(
|
description: _t("room_settings|permissions|error_changing_pl_reqs_description"),
|
||||||
"An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.",
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -230,10 +230,8 @@ export default class RolesRoomSettingsTab extends React.Component<IProps> {
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
|
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Error changing power level"),
|
title: _t("room_settings|permissions|error_changing_pl_title"),
|
||||||
description: _t(
|
description: _t("room_settings|permissions|error_changing_pl_description"),
|
||||||
"An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.",
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -301,8 +301,8 @@ export default class SecurityRoomSettingsTab extends React.Component<IProps, ISt
|
||||||
|
|
||||||
private onJoinRuleChangeError = (error: Error): void => {
|
private onJoinRuleChangeError = (error: Error): void => {
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Failed to update the join rules"),
|
title: _t("room_settings|security|error_join_rule_change_title"),
|
||||||
description: error.message ?? _t("Unknown failure"),
|
description: error.message ?? _t("room_settings|security|error_join_rule_change_unknown"),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -81,14 +81,14 @@ const ElementCallSwitch: React.FC<ElementCallSwitchProps> = ({ room }) => {
|
||||||
return (
|
return (
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
data-testid="element-call-switch"
|
data-testid="element-call-switch"
|
||||||
label={_t("Enable %(brand)s as an additional calling option in this room", { brand })}
|
label={_t("room_settings|voip|enable_element_call_label", { brand })}
|
||||||
caption={_t("%(brand)s is end-to-end encrypted, but is currently limited to smaller numbers of users.", {
|
caption={_t("room_settings|voip|enable_element_call_caption", {
|
||||||
brand,
|
brand,
|
||||||
})}
|
})}
|
||||||
value={elementCallEnabled}
|
value={elementCallEnabled}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={!maySend}
|
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 }) => {
|
export const VoipRoomSettingsTab: React.FC<Props> = ({ room }) => {
|
||||||
return (
|
return (
|
||||||
<SettingsTab>
|
<SettingsTab>
|
||||||
<SettingsSection heading={_t("Voice & Video")}>
|
<SettingsSection heading={_t("settings|voip|title")}>
|
||||||
<SettingsSubsection heading={_t("Call type")}>
|
<SettingsSubsection heading={_t("room_settings|voip|call_type_section")}>
|
||||||
<ElementCallSwitch room={room} />
|
<ElementCallSwitch room={room} />
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
|
@ -282,16 +282,16 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
|
|
||||||
const errorMessage = extractErrorMessageFromError(
|
const errorMessage = extractErrorMessageFromError(
|
||||||
err,
|
err,
|
||||||
_t("Unknown password change error (%(stringifiedError)s)", {
|
_t("settings|general|error_password_change_unknown", {
|
||||||
stringifiedError: String(err),
|
stringifiedError: String(err),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
let errorMessageToDisplay = errorMessage;
|
let errorMessageToDisplay = errorMessage;
|
||||||
if (underlyingError instanceof HTTPError && underlyingError.httpStatus === 403) {
|
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) {
|
} else if (underlyingError instanceof HTTPError) {
|
||||||
errorMessageToDisplay = _t("%(errorMessage)s (HTTP status %(httpStatus)s)", {
|
errorMessageToDisplay = _t("settings|general|error_password_change_http", {
|
||||||
errorMessage,
|
errorMessage,
|
||||||
httpStatus: underlyingError.httpStatus,
|
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
|
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("Error changing password"),
|
title: _t("settings|general|error_password_change_title"),
|
||||||
description: errorMessageToDisplay,
|
description: errorMessageToDisplay,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private onPasswordChanged = (): void => {
|
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
|
// TODO: Figure out a design that doesn't involve replacing the current dialog
|
||||||
Modal.createDialog(ErrorDialog, {
|
Modal.createDialog(ErrorDialog, {
|
||||||
title: _t("common|success"),
|
title: _t("common|success"),
|
||||||
|
@ -346,7 +346,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
threepidSection = (
|
threepidSection = (
|
||||||
<>
|
<>
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
heading={_t("Email addresses")}
|
heading={_t("settings|general|emails_heading")}
|
||||||
stretchContent
|
stretchContent
|
||||||
data-testid="mx_AccountEmailAddresses"
|
data-testid="mx_AccountEmailAddresses"
|
||||||
>
|
>
|
||||||
|
@ -354,7 +354,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
|
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
heading={_t("Phone numbers")}
|
heading={_t("settings|general|msisdns_heading")}
|
||||||
stretchContent
|
stretchContent
|
||||||
data-testid="mx_AccountPhoneNumbers"
|
data-testid="mx_AccountPhoneNumbers"
|
||||||
>
|
>
|
||||||
|
@ -368,7 +368,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
if (this.state.canChangePassword) {
|
if (this.state.canChangePassword) {
|
||||||
passwordChangeSection = (
|
passwordChangeSection = (
|
||||||
<>
|
<>
|
||||||
<SettingsSubsectionText>{_t("Set a new account password…")}</SettingsSubsectionText>
|
<SettingsSubsectionText>{_t("settings|general|password_change_section")}</SettingsSubsectionText>
|
||||||
<ChangePassword
|
<ChangePassword
|
||||||
className="mx_GeneralUserSettingsTab_section--account_changePassword"
|
className="mx_GeneralUserSettingsTab_section--account_changePassword"
|
||||||
rowClassName=""
|
rowClassName=""
|
||||||
|
@ -388,7 +388,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
<>
|
<>
|
||||||
<SettingsSubsectionText data-testid="external-account-management-outer">
|
<SettingsSubsectionText data-testid="external-account-management-outer">
|
||||||
{_t(
|
{_t(
|
||||||
"Your account details are managed separately at <code>%(hostname)s</code>.",
|
"settings|general|external_account_management",
|
||||||
{ hostname },
|
{ hostname },
|
||||||
{ code: (sub) => <code>{sub}</code> },
|
{ code: (sub) => <code>{sub}</code> },
|
||||||
)}
|
)}
|
||||||
|
@ -457,10 +457,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
if (this.state.requiredPolicyInfo.hasTerms) {
|
if (this.state.requiredPolicyInfo.hasTerms) {
|
||||||
const intro = (
|
const intro = (
|
||||||
<SettingsSubsectionText>
|
<SettingsSubsectionText>
|
||||||
{_t(
|
{_t("settings|general|discovery_needs_terms", { serverName: this.state.idServerName })}
|
||||||
"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 },
|
|
||||||
)}
|
|
||||||
</SettingsSubsectionText>
|
</SettingsSubsectionText>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
|
@ -504,14 +501,14 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
private renderManagementSection(): JSX.Element {
|
private renderManagementSection(): JSX.Element {
|
||||||
// TODO: Improve warning text for account deactivation
|
// TODO: Improve warning text for account deactivation
|
||||||
return (
|
return (
|
||||||
<SettingsSection heading={_t("Deactivate account")}>
|
<SettingsSection heading={_t("settings|general|deactivate_section")}>
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
heading={_t("Account management")}
|
heading={_t("settings|general|account_management_section")}
|
||||||
data-testid="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">
|
<AccessibleButton onClick={this.onDeactivateClicked} kind="danger">
|
||||||
{_t("Deactivate Account")}
|
{_t("settings|general|deactivate_section")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
@ -549,7 +546,7 @@ export default class GeneralUserSettingsTab extends React.Component<IProps, ISta
|
||||||
const heading = (
|
const heading = (
|
||||||
<Heading size="2">
|
<Heading size="2">
|
||||||
{discoWarning}
|
{discoWarning}
|
||||||
{_t("Discovery")}
|
{_t("settings|general|discovery_section")}
|
||||||
</Heading>
|
</Heading>
|
||||||
);
|
);
|
||||||
discoverySection = (
|
discoverySection = (
|
||||||
|
|
|
@ -143,7 +143,7 @@ export default class MjolnirUserSettingsTab extends React.Component<{}, IState>
|
||||||
const name = room ? room.name : list.roomId;
|
const name = room ? room.name : list.roomId;
|
||||||
|
|
||||||
const renderRules = (rules: ListRule[]): JSX.Element => {
|
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[] = [];
|
const tiles: JSX.Element[] = [];
|
||||||
for (const rule of rules) {
|
for (const rule of rules) {
|
||||||
|
|
|
@ -63,7 +63,7 @@ export class IgnoredUser extends React.Component<IIgnoredUserProps> {
|
||||||
aria-describedby={id}
|
aria-describedby={id}
|
||||||
disabled={this.props.inProgress}
|
disabled={this.props.inProgress}
|
||||||
>
|
>
|
||||||
{_t("Unignore")}
|
{_t("action|unignore")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
<span id={id}>{this.props.userId}</span>
|
<span id={id}>{this.props.userId}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -225,7 +225,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||||
const { waitingUnignored, ignoredUserIds } = this.state;
|
const { waitingUnignored, ignoredUserIds } = this.state;
|
||||||
|
|
||||||
const userIds = !ignoredUserIds?.length
|
const userIds = !ignoredUserIds?.length
|
||||||
? _t("You have no ignored users.")
|
? _t("settings|security|ignore_users_empty")
|
||||||
: ignoredUserIds.map((u) => {
|
: ignoredUserIds.map((u) => {
|
||||||
return (
|
return (
|
||||||
<IgnoredUser
|
<IgnoredUser
|
||||||
|
@ -238,7 +238,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsSubsection heading={_t("Ignored users")}>
|
<SettingsSubsection heading={_t("settings|security|ignore_users_section")}>
|
||||||
<SettingsSubsectionText>{userIds}</SettingsSubsectionText>
|
<SettingsSubsectionText>{userIds}</SettingsSubsectionText>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
);
|
);
|
||||||
|
@ -301,9 +301,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||||
if (!privateShouldBeEncrypted(MatrixClientPeg.safeGet())) {
|
if (!privateShouldBeEncrypted(MatrixClientPeg.safeGet())) {
|
||||||
warning = (
|
warning = (
|
||||||
<div className="mx_SecurityUserSettingsTab_warning">
|
<div className="mx_SecurityUserSettingsTab_warning">
|
||||||
{_t(
|
{_t("settings|security|e2ee_default_disabled_warning")}
|
||||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.",
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -320,9 +318,7 @@ export default class SecurityUserSettingsTab extends React.Component<IProps, ISt
|
||||||
<SettingsSection heading={_t("common|privacy")}>
|
<SettingsSection heading={_t("common|privacy")}>
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
heading={_t("common|analytics")}
|
heading={_t("common|analytics")}
|
||||||
description={_t(
|
description={_t("settings|security|analytics_description")}
|
||||||
"Share anonymous data to help us identify issues. Nothing personal. No third parties.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<AccessibleButton kind="link" onClick={onClickAnalyticsLearnMore}>
|
<AccessibleButton kind="link" onClick={onClickAnalyticsLearnMore}>
|
||||||
{_t("action|learn_more")}
|
{_t("action|learn_more")}
|
||||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useCallback, useContext, useEffect, useRef, useState } from "react";
|
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
|
|
||||||
|
@ -180,6 +180,7 @@ const SessionManagerTab: React.FC = () => {
|
||||||
const currentUserMember = (userId && matrixClient?.getUser(userId)) || undefined;
|
const currentUserMember = (userId && matrixClient?.getUser(userId)) || undefined;
|
||||||
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
|
const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]);
|
||||||
const capabilities = useAsyncMemo(async () => matrixClient?.getCapabilities(), [matrixClient]);
|
const capabilities = useAsyncMemo(async () => matrixClient?.getCapabilities(), [matrixClient]);
|
||||||
|
const wellKnown = useMemo(() => matrixClient?.getClientWellKnown(), [matrixClient]);
|
||||||
|
|
||||||
const onDeviceExpandToggle = (deviceId: ExtendedDevice["device_id"]): void => {
|
const onDeviceExpandToggle = (deviceId: ExtendedDevice["device_id"]): void => {
|
||||||
if (expandedDeviceIds.includes(deviceId)) {
|
if (expandedDeviceIds.includes(deviceId)) {
|
||||||
|
@ -302,9 +303,7 @@ const SessionManagerTab: React.FC = () => {
|
||||||
disabled={!!signingOutDeviceIds.length}
|
disabled={!!signingOutDeviceIds.length}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
description={_t(
|
description={_t("settings|sessions|best_security_note")}
|
||||||
"For best security, verify your sessions and sign out from any session that you don't recognize or use anymore.",
|
|
||||||
)}
|
|
||||||
data-testid="other-sessions-section"
|
data-testid="other-sessions-section"
|
||||||
stretchContent
|
stretchContent
|
||||||
>
|
>
|
||||||
|
@ -331,7 +330,12 @@ const SessionManagerTab: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
)}
|
)}
|
||||||
<LoginWithQRSection onShowQr={onShowQrClicked} versions={clientVersions} capabilities={capabilities} />
|
<LoginWithQRSection
|
||||||
|
onShowQr={onShowQrClicked}
|
||||||
|
versions={clientVersions}
|
||||||
|
capabilities={capabilities}
|
||||||
|
wellKnown={wellKnown}
|
||||||
|
/>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
);
|
);
|
||||||
|
|
|
@ -67,9 +67,7 @@ const SidebarUserSettingsTab: React.FC = () => {
|
||||||
<SettingsSection heading={_t("settings|sidebar|title")}>
|
<SettingsSection heading={_t("settings|sidebar|title")}>
|
||||||
<SettingsSubsection
|
<SettingsSubsection
|
||||||
heading={_t("settings|sidebar|metaspaces_subsection")}
|
heading={_t("settings|sidebar|metaspaces_subsection")}
|
||||||
description={_t(
|
description={_t("settings|sidebar|spaces_explainer")}
|
||||||
"Spaces are ways to group rooms and people. Alongside the spaces you're in, you can use some pre-built ones too.",
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<StyledCheckbox
|
<StyledCheckbox
|
||||||
checked={!!homeEnabled}
|
checked={!!homeEnabled}
|
||||||
|
|
|
@ -159,29 +159,30 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
|
||||||
if (!this.state.mediaDevices) {
|
if (!this.state.mediaDevices) {
|
||||||
requestButton = (
|
requestButton = (
|
||||||
<div>
|
<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">
|
<AccessibleButton onClick={this.requestMediaPermissions} kind="primary">
|
||||||
{_t("Request media permissions")}
|
{_t("settings|voip|request_permissions")}
|
||||||
</AccessibleButton>
|
</AccessibleButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (this.state.mediaDevices) {
|
} else if (this.state.mediaDevices) {
|
||||||
speakerDropdown = this.renderDropdown(MediaDeviceKindEnum.AudioOutput, _t("Audio Output")) || (
|
speakerDropdown = this.renderDropdown(
|
||||||
<p>{_t("No Audio Outputs detected")}</p>
|
MediaDeviceKindEnum.AudioOutput,
|
||||||
);
|
_t("settings|voip|audio_output"),
|
||||||
|
) || <p>{_t("settings|voip|audio_output_empty")}</p>;
|
||||||
microphoneDropdown = this.renderDropdown(MediaDeviceKindEnum.AudioInput, _t("common|microphone")) || (
|
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")) || (
|
webcamDropdown = this.renderDropdown(MediaDeviceKindEnum.VideoInput, _t("common|camera")) || (
|
||||||
<p>{_t("No Webcams detected")}</p>
|
<p>{_t("settings|voip|video_input_empty")}</p>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsTab>
|
<SettingsTab>
|
||||||
<SettingsSection heading={_t("Voice & Video")}>
|
<SettingsSection heading={_t("settings|voip|title")}>
|
||||||
{requestButton}
|
{requestButton}
|
||||||
<SettingsSubsection heading={_t("Voice settings")} stretchContent>
|
<SettingsSubsection heading={_t("settings|voip|voice_section")} stretchContent>
|
||||||
{speakerDropdown}
|
{speakerDropdown}
|
||||||
{microphoneDropdown}
|
{microphoneDropdown}
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
|
@ -190,18 +191,18 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
|
||||||
await MediaDeviceHandler.setAudioAutoGainControl(v);
|
await MediaDeviceHandler.setAudioAutoGainControl(v);
|
||||||
this.setState({ audioAutoGainControl: MediaDeviceHandler.getAudioAutoGainControl() });
|
this.setState({ audioAutoGainControl: MediaDeviceHandler.getAudioAutoGainControl() });
|
||||||
}}
|
}}
|
||||||
label={_t("Automatically adjust the microphone volume")}
|
label={_t("settings|voip|voice_agc")}
|
||||||
data-testid="voice-auto-gain"
|
data-testid="voice-auto-gain"
|
||||||
/>
|
/>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
<SettingsSubsection heading={_t("Video settings")} stretchContent>
|
<SettingsSubsection heading={_t("settings|voip|video_section")} stretchContent>
|
||||||
{webcamDropdown}
|
{webcamDropdown}
|
||||||
<SettingsFlag name="VideoView.flipVideoHorizontally" level={SettingLevel.ACCOUNT} />
|
<SettingsFlag name="VideoView.flipVideoHorizontally" level={SettingLevel.ACCOUNT} />
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<SettingsSection heading={_t("common|advanced")}>
|
<SettingsSection heading={_t("common|advanced")}>
|
||||||
<SettingsSubsection heading={_t("Voice processing")}>
|
<SettingsSubsection heading={_t("settings|voip|voice_processing")}>
|
||||||
<LabelledToggleSwitch
|
<LabelledToggleSwitch
|
||||||
value={this.state.audioNoiseSuppression}
|
value={this.state.audioNoiseSuppression}
|
||||||
onChange={async (v): Promise<void> => {
|
onChange={async (v): Promise<void> => {
|
||||||
|
@ -221,7 +222,7 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
|
||||||
data-testid="voice-echo-cancellation"
|
data-testid="voice-echo-cancellation"
|
||||||
/>
|
/>
|
||||||
</SettingsSubsection>
|
</SettingsSubsection>
|
||||||
<SettingsSubsection heading={_t("Connection")}>
|
<SettingsSubsection heading={_t("settings|voip|connection_section")}>
|
||||||
<SettingsFlag
|
<SettingsFlag
|
||||||
name="webRtcAllowPeerToPeer"
|
name="webRtcAllowPeerToPeer"
|
||||||
level={SettingLevel.DEVICE}
|
level={SettingLevel.DEVICE}
|
||||||
|
|
|
@ -125,7 +125,7 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
|
||||||
let addressesSection: JSX.Element | undefined;
|
let addressesSection: JSX.Element | undefined;
|
||||||
if (space.getJoinRule() === JoinRule.Public) {
|
if (space.getJoinRule() === JoinRule.Public) {
|
||||||
addressesSection = (
|
addressesSection = (
|
||||||
<SettingsSection heading={_t("Address")}>
|
<SettingsSection heading={_t("room_settings|visibility|alias_section")}>
|
||||||
<AliasSettings
|
<AliasSettings
|
||||||
roomId={space.roomId}
|
roomId={space.roomId}
|
||||||
canSetCanonicalAlias={canSetCanonical}
|
canSetCanonicalAlias={canSetCanonical}
|
||||||
|
|
|
@ -31,7 +31,7 @@ const getRoomName = (room?: Room, oobName = ""): string => room?.name || oobName
|
||||||
* @returns {string} the room name
|
* @returns {string} the room name
|
||||||
*/
|
*/
|
||||||
export function useRoomName(room?: Room, oobData?: IOOBData): string {
|
export function useRoomName(room?: Room, oobData?: IOOBData): string {
|
||||||
let oobName = _t("Unnamed room");
|
let oobName = _t("common|unnamed_room");
|
||||||
if (oobData && oobData.name) {
|
if (oobData && oobData.name) {
|
||||||
oobName = oobData.name;
|
oobName = oobData.name;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
{
|
{
|
||||||
"Create new room": "إنشاء غرفة جديدة",
|
"Create new room": "إنشاء غرفة جديدة",
|
||||||
"Failed to change password. Is your password correct?": "فشلت عملية تعديل الكلمة السرية. هل كلمتك السرية صحيحة ؟",
|
|
||||||
"Send": "إرسال",
|
"Send": "إرسال",
|
||||||
"Unavailable": "غير متوفر",
|
"Unavailable": "غير متوفر",
|
||||||
"All Rooms": "كل الغُرف",
|
"All Rooms": "كل الغُرف",
|
||||||
"All messages": "كل الرسائل",
|
|
||||||
"Changelog": "سِجل التغييرات",
|
"Changelog": "سِجل التغييرات",
|
||||||
"Thank you!": "شكرًا !",
|
"Thank you!": "شكرًا !",
|
||||||
"Permission Required": "التصريح مطلوب",
|
|
||||||
"Sun": "الأحد",
|
"Sun": "الأحد",
|
||||||
"Mon": "الإثنين",
|
"Mon": "الإثنين",
|
||||||
"Tue": "الثلاثاء",
|
"Tue": "الثلاثاء",
|
||||||
|
@ -33,11 +30,9 @@
|
||||||
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(time)s",
|
"%(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": "%(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",
|
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s، %(day)s %(monthName)s %(fullYear)s %(time)s",
|
||||||
"Default": "المبدئي",
|
|
||||||
"Restricted": "مقيد",
|
"Restricted": "مقيد",
|
||||||
"Moderator": "مشرف",
|
"Moderator": "مشرف",
|
||||||
"Logs sent": "تم ارسال سجل الاحداث",
|
"Logs sent": "تم ارسال سجل الاحداث",
|
||||||
"Reason": "السبب",
|
|
||||||
"You signed in to a new session without verifying it:": "قمت بتسجيل الدخول لجلسة جديدة من غير التحقق منها:",
|
"You signed in to a new session without verifying it:": "قمت بتسجيل الدخول لجلسة جديدة من غير التحقق منها:",
|
||||||
"Verify your other session using one of the options below.": "أكِّد جلستك الأخرى باستخدام أحد الخيارات أدناه.",
|
"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 تم تسجيل الدخول لجلسة جديدة من غير التحقق منها:",
|
"%(name)s (%(userId)s) signed in to a new session without verifying it:": "%(name)s%(userId)s تم تسجيل الدخول لجلسة جديدة من غير التحقق منها:",
|
||||||
|
@ -137,105 +132,28 @@
|
||||||
"This room is running room version <roomVersion />, which this homeserver has marked as <i>unstable</i>.": "هذه الغرفة تشغل إصدار الغرفة <roomVersion /> ، والذي عده الخادم الوسيط هذا بأنه<i> غير مستقر </i>.",
|
"This room is running room version <roomVersion />, which this homeserver has marked as <i>unstable</i>.": "هذه الغرفة تشغل إصدار الغرفة <roomVersion /> ، والذي عده الخادم الوسيط هذا بأنه<i> غير مستقر </i>.",
|
||||||
"This room has already been upgraded.": "سبق وأن تمت ترقية هذه الغرفة.",
|
"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.": "ستؤدي ترقية هذه الغرفة إلى إغلاق النسخة الحالية للغرفة وإنشاء غرفة تمت ترقيتها بنفس الاسم.",
|
"Upgrading this room will shut down the current instance of the room and create an upgraded room with the same name.": "ستؤدي ترقية هذه الغرفة إلى إغلاق النسخة الحالية للغرفة وإنشاء غرفة تمت ترقيتها بنفس الاسم.",
|
||||||
"Room options": "خيارات الغرفة",
|
|
||||||
"%(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. هل تريد الانضمام إليها؟",
|
|
||||||
"You're previewing %(roomName)s. Want to join it?": "أنت تعاين %(roomName)s. تريد الانضمام إليها؟",
|
|
||||||
"Reject & Ignore user": "رفض الدعوة وتجاهل الداعي",
|
|
||||||
"<userName/> invited you": "<userName/> دعاك",
|
|
||||||
"Do you want to join %(roomName)s?": "هل تريد أن تنضم إلى %(roomName)s؟",
|
|
||||||
"Start chatting": "ابدأ المحادثة",
|
|
||||||
"<userName/> wants to chat": "<userName/> يريد محادثتك",
|
|
||||||
"Do you want to chat with %(user)s?": "هل تريد محادثة %(user)s؟",
|
|
||||||
"Share this email in Settings to receive invites directly in %(brand)s.": "شارك هذا البريد الإلكتروني في الإعدادات لتلقي الدعوات مباشرةً في %(brand)s.",
|
|
||||||
"Use an identity server in Settings to receive invites directly in %(brand)s.": "استخدم خادم هوية في الإعدادات لتلقي الدعوات مباشرة في %(brand)s.",
|
|
||||||
"This invite to %(roomName)s was sent to %(email)s": "الدعوة إلى %(roomName)s أرسلت إلى %(email)s",
|
|
||||||
"Link this email with your account in Settings to receive invites directly in %(brand)s.": "اربط هذا البريد الإلكتروني بحسابك في الإعدادات لتلقي الدعوات مباشرةً في%(brand)s.",
|
|
||||||
"This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "تم إرسال الدعوة إلى %(roomName)s إلى %(email)s الذي لا يرتبط بحسابك",
|
|
||||||
"Join the discussion": "انضم للنقاش",
|
|
||||||
"Try to join anyway": "حاول الانضمام على أي حال",
|
|
||||||
"You can only join it with a working invite.": "لا يمكن الإنضمام إليها إلا بدعوة صالحة.",
|
|
||||||
"unknown error code": "رمز خطأٍ غير معروف",
|
"unknown error code": "رمز خطأٍ غير معروف",
|
||||||
"Something went wrong with your invite to %(roomName)s": "حدث خطأ في دعوتك إلى %(roomName)s",
|
|
||||||
"You were banned from %(roomName)s by %(memberName)s": "لقد حُظِرت من غرفة %(roomName)s من قِبَل %(memberName)s",
|
|
||||||
"Re-join": "أعِد الانضمام",
|
|
||||||
"Forget this room": "انسَ هذه الغرفة",
|
|
||||||
"Reason: %(reason)s": "السبب: %(reason)s",
|
|
||||||
"Sign Up": "سجل",
|
|
||||||
"Join the conversation with an account": "انضم للمحادثة بحساب",
|
|
||||||
"Historical": "تاريخي",
|
|
||||||
"Low priority": "أولوية منخفضة",
|
|
||||||
"Explore public rooms": "استكشف الغرف العامة",
|
|
||||||
"Add room": "أضف غرفة",
|
|
||||||
"Rooms": "الغرف",
|
|
||||||
"Show Widgets": "إظهار عناصر الواجهة",
|
|
||||||
"Hide Widgets": "إخفاء عناصر الواجهة",
|
|
||||||
"Forget room": "انسَ الغرفة",
|
|
||||||
"Join Room": "انضم للغرفة",
|
"Join Room": "انضم للغرفة",
|
||||||
"(~%(count)s results)": {
|
"(~%(count)s results)": {
|
||||||
"one": "(~%(count)s نتيجة)",
|
"one": "(~%(count)s نتيجة)",
|
||||||
"other": "(~%(count)s نتائج)"
|
"other": "(~%(count)s نتائج)"
|
||||||
},
|
},
|
||||||
"Unnamed room": "غرفة بلا اسم",
|
"Unnamed room": "غرفة بلا اسم",
|
||||||
"No recently visited rooms": "لا توجد غرف تمت زيارتها مؤخرًا",
|
|
||||||
"Room %(name)s": "الغرفة %(name)s",
|
|
||||||
"Replying": "الرد",
|
|
||||||
"%(duration)sd": "%(duration)sي",
|
"%(duration)sd": "%(duration)sي",
|
||||||
"%(duration)sh": "%(duration)sس",
|
"%(duration)sh": "%(duration)sس",
|
||||||
"%(duration)sm": "%(duration)sد",
|
"%(duration)sm": "%(duration)sد",
|
||||||
"%(duration)ss": "%(duration)sث",
|
"%(duration)ss": "%(duration)sث",
|
||||||
"Italics": "مائل",
|
|
||||||
"You do not have permission to post to this room": "ليس لديك إذن للنشر في هذه الغرفة",
|
|
||||||
"This room has been replaced and is no longer active.": "تم استبدال هذه الغرفة ولم تعد نشطة.",
|
|
||||||
"The conversation continues here.": "تستمر المحادثة هنا.",
|
|
||||||
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (قوة %(powerLevelNumber)s)",
|
|
||||||
"Filter room members": "تصفية أعضاء الغرفة",
|
|
||||||
"Invited": "مدعو",
|
|
||||||
"Invite to this room": "ادع لهذه الغرفة",
|
|
||||||
"and %(count)s others...": {
|
"and %(count)s others...": {
|
||||||
"one": "وواحدة أخرى...",
|
"one": "وواحدة أخرى...",
|
||||||
"other": "و %(count)s أخر..."
|
"other": "و %(count)s أخر..."
|
||||||
},
|
},
|
||||||
"Close preview": "إغلاق المعاينة",
|
|
||||||
"Scroll to most recent messages": "انتقل إلى أحدث الرسائل",
|
"Scroll to most recent messages": "انتقل إلى أحدث الرسائل",
|
||||||
"The authenticity of this encrypted message can't be guaranteed on this device.": "لا يمكن ضمان موثوقية هذه الرسالة المشفرة على هذا الجهاز.",
|
"The authenticity of this encrypted message can't be guaranteed on this device.": "لا يمكن ضمان موثوقية هذه الرسالة المشفرة على هذا الجهاز.",
|
||||||
"Encrypted by a deleted session": "مشفرة باتصال محذوف",
|
"Encrypted by a deleted session": "مشفرة باتصال محذوف",
|
||||||
"Unencrypted": "غير مشفر",
|
"Unencrypted": "غير مشفر",
|
||||||
"Encrypted by an unverified session": "مشفرة باتصال لم يتم التحقق منه",
|
"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.",
|
"To report a Matrix-related security issue, please read the Matrix.org <a>Security Disclosure Policy</a>.": "للإبلاغ عن مشكلة أمنية متعلقة بMatrix ، يرجى قراءة <a>سياسة الإفصاح الأمني</a> في Matrix.org.",
|
||||||
"Discovery": "الاكتشاف",
|
|
||||||
"Deactivate account": "تعطيل الحساب",
|
"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": "فحص خادم",
|
|
||||||
"Back up your keys before signing out to avoid losing them.": "أضف مفاتيحك للاحتياطي قبل تسجيل الخروج لتتجنب ضياعها.",
|
"Back up your keys before signing out to avoid losing them.": "أضف مفاتيحك للاحتياطي قبل تسجيل الخروج لتتجنب ضياعها.",
|
||||||
"Backup version:": "نسخة الاحتياطي:",
|
"Backup version:": "نسخة الاحتياطي:",
|
||||||
"This backup is trusted because it has been restored on this session": "هذا الاحتياطي موثوق به لأنه تمت استعادته في هذا الاتصال",
|
"This backup is trusted because it has been restored on this session": "هذا الاحتياطي موثوق به لأنه تمت استعادته في هذا الاتصال",
|
||||||
|
@ -304,8 +222,6 @@
|
||||||
"Room avatar": "صورة الغرفة",
|
"Room avatar": "صورة الغرفة",
|
||||||
"Room Topic": "موضوع الغرفة",
|
"Room Topic": "موضوع الغرفة",
|
||||||
"Room Name": "اسم الغرفة",
|
"Room Name": "اسم الغرفة",
|
||||||
"Failed to set display name": "تعذر تعيين الاسم الظاهر",
|
|
||||||
"Authentication": "المصادقة",
|
|
||||||
"Set up": "تأسيس",
|
"Set up": "تأسيس",
|
||||||
"Warning!": "إنذار!",
|
"Warning!": "إنذار!",
|
||||||
"Show more": "أظهر أكثر",
|
"Show more": "أظهر أكثر",
|
||||||
|
@ -319,53 +235,6 @@
|
||||||
"You have verified this user. This user has verified all of their sessions.": "لقد تحققت من هذا المستخدم. لقد تحقق هذا المستخدم من جميع اتصالاته.",
|
"You have verified this user. This user has verified all of their sessions.": "لقد تحققت من هذا المستخدم. لقد تحقق هذا المستخدم من جميع اتصالاته.",
|
||||||
"You have not verified this user.": "أنت لم تتحقق من هذا المستخدم.",
|
"You have not verified this user.": "أنت لم تتحقق من هذا المستخدم.",
|
||||||
"This user has not verified all of their sessions.": "هذا المستخدم لم يتحقق من جميع اتصالاته.",
|
"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": "حسنا",
|
"Ok": "حسنا",
|
||||||
"Your homeserver has exceeded one of its resource limits.": "لقد تجاوز خادمك أحد حدود موارده.",
|
"Your homeserver has exceeded one of its resource limits.": "لقد تجاوز خادمك أحد حدود موارده.",
|
||||||
"Your homeserver has exceeded its user limit.": "لقد تجاوز خادمك حد عدد المستخدمين.",
|
"Your homeserver has exceeded its user limit.": "لقد تجاوز خادمك حد عدد المستخدمين.",
|
||||||
|
@ -428,14 +297,6 @@
|
||||||
"American Samoa": "ساموا الأمريكية",
|
"American Samoa": "ساموا الأمريكية",
|
||||||
"Algeria": "الجزائر",
|
"Algeria": "الجزائر",
|
||||||
"Åland Islands": "جزر آلاند",
|
"Å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": "باراغواي",
|
"Paraguay": "باراغواي",
|
||||||
"Netherlands": "هولندا",
|
"Netherlands": "هولندا",
|
||||||
"Greece": "اليونان",
|
"Greece": "اليونان",
|
||||||
|
@ -585,7 +446,11 @@
|
||||||
"general": "عام",
|
"general": "عام",
|
||||||
"profile": "الملف الشخصي",
|
"profile": "الملف الشخصي",
|
||||||
"display_name": "الاسم الظاهر",
|
"display_name": "الاسم الظاهر",
|
||||||
"user_avatar": "الصورة الشخصية"
|
"user_avatar": "الصورة الشخصية",
|
||||||
|
"authentication": "المصادقة",
|
||||||
|
"rooms": "الغرف",
|
||||||
|
"low_priority": "أولوية منخفضة",
|
||||||
|
"historical": "تاريخي"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"continue": "واصِل",
|
"continue": "واصِل",
|
||||||
|
@ -640,7 +505,9 @@
|
||||||
"review": "مراجعة",
|
"review": "مراجعة",
|
||||||
"manage": "إدارة",
|
"manage": "إدارة",
|
||||||
"mention": "إشارة",
|
"mention": "إشارة",
|
||||||
"unban": "فك الحظر"
|
"unban": "فك الحظر",
|
||||||
|
"explore_rooms": "استكشِف الغرف",
|
||||||
|
"explore_public_rooms": "استكشف الغرف العامة"
|
||||||
},
|
},
|
||||||
"labs": {
|
"labs": {
|
||||||
"pinning": "تثبيت الرسالة",
|
"pinning": "تثبيت الرسالة",
|
||||||
|
@ -669,7 +536,13 @@
|
||||||
"placeholder_reply_encrypted": "أرسل جواباً مشفراً …",
|
"placeholder_reply_encrypted": "أرسل جواباً مشفراً …",
|
||||||
"placeholder_reply": "أرسل جواباً …",
|
"placeholder_reply": "أرسل جواباً …",
|
||||||
"placeholder_encrypted": "أرسل رسالة مشفرة …",
|
"placeholder_encrypted": "أرسل رسالة مشفرة …",
|
||||||
"placeholder": "أرسل رسالة …"
|
"placeholder": "أرسل رسالة …",
|
||||||
|
"room_upgraded_link": "تستمر المحادثة هنا.",
|
||||||
|
"room_upgraded_notice": "تم استبدال هذه الغرفة ولم تعد نشطة.",
|
||||||
|
"no_perms_notice": "ليس لديك إذن للنشر في هذه الغرفة",
|
||||||
|
"poll_button_no_perms_title": "التصريح مطلوب",
|
||||||
|
"format_italics": "مائل",
|
||||||
|
"replying_title": "الرد"
|
||||||
},
|
},
|
||||||
"power_level": {
|
"power_level": {
|
||||||
"default": "المبدئي",
|
"default": "المبدئي",
|
||||||
|
@ -755,7 +628,14 @@
|
||||||
"inline_url_previews_room_account": "تمكين معاينة الروابط لهذه الغرفة (يؤثر عليك فقط)",
|
"inline_url_previews_room_account": "تمكين معاينة الروابط لهذه الغرفة (يؤثر عليك فقط)",
|
||||||
"inline_url_previews_room": "تمكين معاينة الروابط أصلاً لأي مشارك في هذه الغرفة",
|
"inline_url_previews_room": "تمكين معاينة الروابط أصلاً لأي مشارك في هذه الغرفة",
|
||||||
"voip": {
|
"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": {
|
"security": {
|
||||||
"send_analytics": "إرسال بيانات التحليلات",
|
"send_analytics": "إرسال بيانات التحليلات",
|
||||||
|
@ -808,7 +688,10 @@
|
||||||
"key_backup_connect": "اربط هذا الاتصال باحتياطي مفتاح",
|
"key_backup_connect": "اربط هذا الاتصال باحتياطي مفتاح",
|
||||||
"key_backup_complete": "جميع المفاتيح منسوخة في الاحتياطي",
|
"key_backup_complete": "جميع المفاتيح منسوخة في الاحتياطي",
|
||||||
"key_backup_algorithm": "الخوارزمية:",
|
"key_backup_algorithm": "الخوارزمية:",
|
||||||
"key_backup_inactive_warning": "مفاتيحك <b>لا احتياطيَّ لها من هذا الاتصال</b>."
|
"key_backup_inactive_warning": "مفاتيحك <b>لا احتياطيَّ لها من هذا الاتصال</b>.",
|
||||||
|
"key_backup_active_version_none": "لا شيء",
|
||||||
|
"ignore_users_section": "المستخدمون المتجاهَلون",
|
||||||
|
"e2ee_default_disabled_warning": "قام مسؤول الخادم بتعطيل التشفير من طرف إلى طرف أصلاً في الغرف الخاصة والرسائل الخاصّة."
|
||||||
},
|
},
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"room_list_heading": "قائمة الغرفة",
|
"room_list_heading": "قائمة الغرفة",
|
||||||
|
@ -831,7 +714,39 @@
|
||||||
"add_msisdn_dialog_title": "أضِف رقم الهاتف",
|
"add_msisdn_dialog_title": "أضِف رقم الهاتف",
|
||||||
"name_placeholder": "لا اسم ظاهر",
|
"name_placeholder": "لا اسم ظاهر",
|
||||||
"error_saving_profile_title": "تعذر حفظ ملفك الشخصي",
|
"error_saving_profile_title": "تعذر حفظ ملفك الشخصي",
|
||||||
"error_saving_profile": "تعذر إتمام العملية"
|
"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": {
|
"devtools": {
|
||||||
|
@ -969,6 +884,9 @@
|
||||||
"lightbox_title": "%(senderDisplayName)s غير صورة الغرفة %(roomName)s",
|
"lightbox_title": "%(senderDisplayName)s غير صورة الغرفة %(roomName)s",
|
||||||
"removed": "%(senderDisplayName)s حذف صورة الغرفة.",
|
"removed": "%(senderDisplayName)s حذف صورة الغرفة.",
|
||||||
"changed_img": "%(senderDisplayName)s غير صورة الغرفة إلى <img/>"
|
"changed_img": "%(senderDisplayName)s غير صورة الغرفة إلى <img/>"
|
||||||
|
},
|
||||||
|
"url_preview": {
|
||||||
|
"close": "إغلاق المعاينة"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"slash_command": {
|
"slash_command": {
|
||||||
|
@ -1132,7 +1050,14 @@
|
||||||
"send_event_type": "إرسال أحداث من نوع %(eventType)s",
|
"send_event_type": "إرسال أحداث من نوع %(eventType)s",
|
||||||
"title": "الأدوار والصلاحيات",
|
"title": "الأدوار والصلاحيات",
|
||||||
"permissions_section": "الصلاحيات",
|
"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": {
|
"security": {
|
||||||
"strict_encryption": "لا ترسل أبدًا رسائل مشفرة إلى اتصالات التي لم يتم التحقق منها في هذه الغرفة من هذا الاتصال",
|
"strict_encryption": "لا ترسل أبدًا رسائل مشفرة إلى اتصالات التي لم يتم التحقق منها في هذه الغرفة من هذا الاتصال",
|
||||||
|
@ -1157,14 +1082,28 @@
|
||||||
"default_url_previews_off": "معاينات URL معطلة بشكل أصلي للمشاركين في هذه الغرفة.",
|
"default_url_previews_off": "معاينات URL معطلة بشكل أصلي للمشاركين في هذه الغرفة.",
|
||||||
"url_preview_encryption_warning": "في الغرف المشفرة ، مثل هذه الغرفة ، يتم تعطيل معاينات URL أصلاً للتأكد من أن خادمك الوسيط (حيث يتم إنشاء المعاينات) لا يمكنه جمع معلومات حول الروابط التي تراها في هذه الغرفة.",
|
"url_preview_encryption_warning": "في الغرف المشفرة ، مثل هذه الغرفة ، يتم تعطيل معاينات URL أصلاً للتأكد من أن خادمك الوسيط (حيث يتم إنشاء المعاينات) لا يمكنه جمع معلومات حول الروابط التي تراها في هذه الغرفة.",
|
||||||
"url_preview_explainer": "عندما يضع شخص ما عنوان URL في رسالته ، يمكن عرض معاينة عنوان URL لإعطاء مزيد من المعلومات حول هذا الرابط مثل العنوان والوصف وصورة من موقع الويب.",
|
"url_preview_explainer": "عندما يضع شخص ما عنوان URL في رسالته ، يمكن عرض معاينة عنوان URL لإعطاء مزيد من المعلومات حول هذا الرابط مثل العنوان والوصف وصورة من موقع الويب.",
|
||||||
"url_previews_section": "معاينة الروابط"
|
"url_previews_section": "معاينة الروابط",
|
||||||
|
"aliases_section": "عناوين الغرف",
|
||||||
|
"other_section": "أخرى"
|
||||||
},
|
},
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"unfederated": "لا يمكن الوصول إلى هذه الغرفة بواسطة خوادم Matrix البعيدة",
|
"unfederated": "لا يمكن الوصول إلى هذه الغرفة بواسطة خوادم Matrix البعيدة",
|
||||||
"room_upgrade_button": "قم بترقية هذه الغرفة إلى إصدار الغرفة الموصى به",
|
"room_upgrade_button": "قم بترقية هذه الغرفة إلى إصدار الغرفة الموصى به",
|
||||||
"room_predecessor": "عرض رسائل أقدم في %(roomName)s.",
|
"room_predecessor": "عرض رسائل أقدم في %(roomName)s.",
|
||||||
"room_version_section": "إصدار الغرفة",
|
"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": {
|
"encryption": {
|
||||||
|
@ -1258,7 +1197,9 @@
|
||||||
"other": "أظهر %(count)s زيادة"
|
"other": "أظهر %(count)s زيادة"
|
||||||
},
|
},
|
||||||
"show_less": "أظهر أقل",
|
"show_less": "أظهر أقل",
|
||||||
"notification_options": "خيارات الإشعارات"
|
"notification_options": "خيارات الإشعارات",
|
||||||
|
"breadcrumbs_empty": "لا توجد غرف تمت زيارتها مؤخرًا",
|
||||||
|
"add_room_label": "أضف غرفة"
|
||||||
},
|
},
|
||||||
"a11y": {
|
"a11y": {
|
||||||
"n_unread_messages_mentions": {
|
"n_unread_messages_mentions": {
|
||||||
|
@ -1270,7 +1211,8 @@
|
||||||
"other": "%(count)s من الرسائل غير مقروءة."
|
"other": "%(count)s من الرسائل غير مقروءة."
|
||||||
},
|
},
|
||||||
"unread_messages": "رسائل غير المقروءة.",
|
"unread_messages": "رسائل غير المقروءة.",
|
||||||
"jump_first_invite": "الانتقال لأول دعوة."
|
"jump_first_invite": "الانتقال لأول دعوة.",
|
||||||
|
"room_name": "الغرفة %(name)s"
|
||||||
},
|
},
|
||||||
"setting": {
|
"setting": {
|
||||||
"help_about": {
|
"help_about": {
|
||||||
|
@ -1421,7 +1363,8 @@
|
||||||
"lists_heading": "قوائم متشرك بها",
|
"lists_heading": "قوائم متشرك بها",
|
||||||
"lists_description_1": "سيؤدي الاشتراك في قائمة الحظر إلى انضمامك إليها!",
|
"lists_description_1": "سيؤدي الاشتراك في قائمة الحظر إلى انضمامك إليها!",
|
||||||
"lists_description_2": "إذا لم يكن هذا ما تريده ، فيرجى استخدام أداة مختلفة لتجاهل المستخدمين.",
|
"lists_description_2": "إذا لم يكن هذا ما تريده ، فيرجى استخدام أداة مختلفة لتجاهل المستخدمين.",
|
||||||
"lists_new_label": "معرف الغرفة أو عنوان قائمة الحظر"
|
"lists_new_label": "معرف الغرفة أو عنوان قائمة الحظر",
|
||||||
|
"rules_empty": "لا شيء"
|
||||||
},
|
},
|
||||||
"room": {
|
"room": {
|
||||||
"drop_file_prompt": "قم بإسقاط الملف هنا ليُرفَع",
|
"drop_file_prompt": "قم بإسقاط الملف هنا ليُرفَع",
|
||||||
|
@ -1443,8 +1386,40 @@
|
||||||
"unfavourite": "فُضلت",
|
"unfavourite": "فُضلت",
|
||||||
"favourite": "تفضيل",
|
"favourite": "تفضيل",
|
||||||
"low_priority": "أولوية منخفضة",
|
"low_priority": "أولوية منخفضة",
|
||||||
"forget": "انسَ الغرفة"
|
"forget": "انسَ الغرفة",
|
||||||
}
|
"title": "خيارات الغرفة"
|
||||||
|
},
|
||||||
|
"invite_this_room": "ادع لهذه الغرفة",
|
||||||
|
"header": {
|
||||||
|
"forget_room_button": "انسَ الغرفة",
|
||||||
|
"hide_widgets_button": "إخفاء عناصر الواجهة",
|
||||||
|
"show_widgets_button": "إظهار عناصر الواجهة"
|
||||||
|
},
|
||||||
|
"join_title_account": "انضم للمحادثة بحساب",
|
||||||
|
"join_button_account": "سجل",
|
||||||
|
"kick_reason": "السبب: %(reason)s",
|
||||||
|
"forget_room": "انسَ هذه الغرفة",
|
||||||
|
"rejoin_button": "أعِد الانضمام",
|
||||||
|
"banned_from_room_by": "لقد حُظِرت من غرفة %(roomName)s من قِبَل %(memberName)s",
|
||||||
|
"3pid_invite_error_title_room": "حدث خطأ في دعوتك إلى %(roomName)s",
|
||||||
|
"3pid_invite_error_invite_subtitle": "لا يمكن الإنضمام إليها إلا بدعوة صالحة.",
|
||||||
|
"3pid_invite_error_invite_action": "حاول الانضمام على أي حال",
|
||||||
|
"join_the_discussion": "انضم للنقاش",
|
||||||
|
"3pid_invite_email_not_found_account_room": "تم إرسال الدعوة إلى %(roomName)s إلى %(email)s الذي لا يرتبط بحسابك",
|
||||||
|
"link_email_to_receive_3pid_invite": "اربط هذا البريد الإلكتروني بحسابك في الإعدادات لتلقي الدعوات مباشرةً في%(brand)s.",
|
||||||
|
"invite_sent_to_email_room": "الدعوة إلى %(roomName)s أرسلت إلى %(email)s",
|
||||||
|
"3pid_invite_no_is_subtitle": "استخدم خادم هوية في الإعدادات لتلقي الدعوات مباشرة في %(brand)s.",
|
||||||
|
"invite_email_mismatch_suggestion": "شارك هذا البريد الإلكتروني في الإعدادات لتلقي الدعوات مباشرةً في %(brand)s.",
|
||||||
|
"dm_invite_title": "هل تريد محادثة %(user)s؟",
|
||||||
|
"dm_invite_subtitle": "<userName/> يريد محادثتك",
|
||||||
|
"dm_invite_action": "ابدأ المحادثة",
|
||||||
|
"invite_title": "هل تريد أن تنضم إلى %(roomName)s؟",
|
||||||
|
"invite_subtitle": "<userName/> دعاك",
|
||||||
|
"invite_reject_ignore": "رفض الدعوة وتجاهل الداعي",
|
||||||
|
"peek_join_prompt": "أنت تعاين %(roomName)s. تريد الانضمام إليها؟",
|
||||||
|
"no_peek_join_prompt": "لا يمكن معاينة %(roomName)s. هل تريد الانضمام إليها؟",
|
||||||
|
"not_found_title_name": "الغرفة %(roomName)s ليست موجودة.",
|
||||||
|
"inaccessible_name": "لا يمكن الوصول إلى %(roomName)s في الوقت الحالي."
|
||||||
},
|
},
|
||||||
"space": {
|
"space": {
|
||||||
"context_menu": {
|
"context_menu": {
|
||||||
|
@ -1502,7 +1477,9 @@
|
||||||
"colour_bold": "ثخين",
|
"colour_bold": "ثخين",
|
||||||
"error_change_title": "تغيير إعدادات الإشعار",
|
"error_change_title": "تغيير إعدادات الإشعار",
|
||||||
"mark_all_read": "أشر عليها بأنها قرأت",
|
"mark_all_read": "أشر عليها بأنها قرأت",
|
||||||
"class_other": "أخرى"
|
"class_other": "أخرى",
|
||||||
|
"default": "المبدئي",
|
||||||
|
"all_messages": "كل الرسائل"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"admin_contact_short": "تواصل مع <a>مدير الخادم</a> الخاص بك.",
|
"admin_contact_short": "تواصل مع <a>مدير الخادم</a> الخاص بك.",
|
||||||
|
@ -1522,6 +1499,43 @@
|
||||||
"a11y_jump_first_unread_room": "الانتقال لأول غرفة لم تقرأ.",
|
"a11y_jump_first_unread_room": "الانتقال لأول غرفة لم تقرأ.",
|
||||||
"integration_manager": {
|
"integration_manager": {
|
||||||
"error_connecting_heading": "لا يمكن الاتصال بمدير التكامل",
|
"error_connecting_heading": "لا يمكن الاتصال بمدير التكامل",
|
||||||
"error_connecting": "مدري التكامل غير متصل بالإنرتنت أو لا يمكنه الوصول إلى خادمك الوسيط."
|
"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": "أدخل خادم هوية جديدًا"
|
||||||
|
},
|
||||||
|
"member_list": {
|
||||||
|
"invited_list_heading": "مدعو",
|
||||||
|
"filter_placeholder": "تصفية أعضاء الغرفة",
|
||||||
|
"power_label": "%(userName)s (قوة %(powerLevelNumber)s)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,26 +22,12 @@
|
||||||
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
|
"%(weekDayName)s %(time)s": "%(weekDayName)s %(time)s",
|
||||||
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(time)s",
|
"%(weekDayName)s, %(monthName)s %(day)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)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": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s",
|
||||||
"Default": "Varsayılan olaraq",
|
|
||||||
"Moderator": "Moderator",
|
"Moderator": "Moderator",
|
||||||
"Reason": "Səbəb",
|
|
||||||
"Incorrect verification code": "Təsdiq etmənin səhv kodu",
|
|
||||||
"Authentication": "Müəyyənləşdirilmə",
|
|
||||||
"Failed to set display name": "Görünüş adını təyin etmək bacarmadı",
|
|
||||||
"not specified": "qeyd edilmədi",
|
"not specified": "qeyd edilmədi",
|
||||||
"Failed to ban user": "İstifadəçini bloklamağı bacarmadı",
|
"Failed to ban user": "İstifadəçini bloklamağı bacarmadı",
|
||||||
"Failed to mute user": "İstifadəçini kəsməyi bacarmadı",
|
"Failed to mute user": "İstifadəçini kəsməyi bacarmadı",
|
||||||
"Are you sure?": "Siz əminsiniz?",
|
"Are you sure?": "Siz əminsiniz?",
|
||||||
"Unignore": "Blokdan çıxarmaq",
|
|
||||||
"Invited": "Dəvət edilmişdir",
|
|
||||||
"Filter room members": "İştirakçılara görə axtarış",
|
|
||||||
"You do not have permission to post to this room": "Siz bu otağa yaza bilmirsiniz",
|
|
||||||
"Join Room": "Otağa girmək",
|
"Join Room": "Otağa girmək",
|
||||||
"Forget room": "Otağı unutmaq",
|
|
||||||
"Low priority": "Əhəmiyyətsizlər",
|
|
||||||
"Historical": "Arxiv",
|
|
||||||
"Failed to unban": "Blokdan çıxarmağı bacarmadı",
|
|
||||||
"Banned by %(displayName)s": "%(displayName)s bloklanıb",
|
|
||||||
"unknown error code": "naməlum səhv kodu",
|
"unknown error code": "naməlum səhv kodu",
|
||||||
"Failed to forget room %(errCode)s": "Otağı unutmağı bacarmadı: %(errCode)s",
|
"Failed to forget room %(errCode)s": "Otağı unutmağı bacarmadı: %(errCode)s",
|
||||||
"Sunday": "Bazar",
|
"Sunday": "Bazar",
|
||||||
|
@ -52,14 +38,9 @@
|
||||||
"Create new room": "Otağı yaratmaq",
|
"Create new room": "Otağı yaratmaq",
|
||||||
"Home": "Başlanğıc",
|
"Home": "Başlanğıc",
|
||||||
"%(items)s and %(lastItem)s": "%(items)s və %(lastItem)s",
|
"%(items)s and %(lastItem)s": "%(items)s və %(lastItem)s",
|
||||||
"Deactivate Account": "Hesabı bağlamaq",
|
|
||||||
"An error has occurred.": "Səhv oldu.",
|
"An error has occurred.": "Səhv oldu.",
|
||||||
"Invalid Email Address": "Yanlış email",
|
|
||||||
"Verification Pending": "Gözləmə təsdiq etmələr",
|
"Verification Pending": "Gözləmə təsdiq etmələr",
|
||||||
"Please check your email and click on the link it contains. Once this is done, click continue.": "Öz elektron poçtunu yoxlayın və olan istinadı basın. Bundan sonra düyməni Davam etməyə basın.",
|
"Please check your email and click on the link it contains. Once this is done, click continue.": "Öz elektron poçtunu yoxlayın və olan istinadı basın. Bundan sonra düyməni Davam etməyə basın.",
|
||||||
"Unable to add email address": "Email-i əlavə etməyə müvəffəq olmur",
|
|
||||||
"Unable to verify email address.": "Email-i yoxlamağı bacarmadı.",
|
|
||||||
"Failed to change password. Is your password correct?": "Şifrəni əvəz etməyi bacarmadı. Siz cari şifrə düzgün daxil etdiniz?",
|
|
||||||
"Reject invitation": "Dəvəti rədd etmək",
|
"Reject invitation": "Dəvəti rədd etmək",
|
||||||
"Are you sure you want to reject the invitation?": "Siz əminsiniz ki, siz dəvəti rədd etmək istəyirsiniz?",
|
"Are you sure you want to reject the invitation?": "Siz əminsiniz ki, siz dəvəti rədd etmək istəyirsiniz?",
|
||||||
"Failed to reject invitation": "Dəvəti rədd etməyi bacarmadı",
|
"Failed to reject invitation": "Dəvəti rədd etməyi bacarmadı",
|
||||||
|
@ -68,18 +49,14 @@
|
||||||
"No more results": "Daha çox nəticə yoxdur",
|
"No more results": "Daha çox nəticə yoxdur",
|
||||||
"Failed to reject invite": "Dəvəti rədd etməyi bacarmadı",
|
"Failed to reject invite": "Dəvəti rədd etməyi bacarmadı",
|
||||||
"Failed to load timeline position": "Xronologiyadan nişanı yükləməyi bacarmadı",
|
"Failed to load timeline position": "Xronologiyadan nişanı yükləməyi bacarmadı",
|
||||||
"Unable to remove contact information": "Əlaqə məlumatlarının silməyi bacarmadı",
|
|
||||||
"A new password must be entered.": "Yeni parolu daxil edin.",
|
"A new password must be entered.": "Yeni parolu daxil edin.",
|
||||||
"New passwords must match each other.": "Yeni şifrələr uyğun olmalıdır.",
|
"New passwords must match each other.": "Yeni şifrələr uyğun olmalıdır.",
|
||||||
"Return to login screen": "Girişin ekranına qayıtmaq",
|
"Return to login screen": "Girişin ekranına qayıtmaq",
|
||||||
"Confirm passphrase": "Şifrəni təsdiqləyin",
|
|
||||||
"Permission Required": "İzn tələb olunur",
|
|
||||||
"Send": "Göndər",
|
"Send": "Göndər",
|
||||||
"PM": "24:00",
|
"PM": "24:00",
|
||||||
"AM": "12:00",
|
"AM": "12:00",
|
||||||
"Restricted": "Məhduddur",
|
"Restricted": "Məhduddur",
|
||||||
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
|
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s",
|
||||||
"Explore rooms": "Otaqları kəşf edin",
|
|
||||||
"common": {
|
"common": {
|
||||||
"analytics": "Analitik",
|
"analytics": "Analitik",
|
||||||
"error": "Səhv",
|
"error": "Səhv",
|
||||||
|
@ -96,7 +73,10 @@
|
||||||
"identity_server": "Eyniləşdirmənin serveri",
|
"identity_server": "Eyniləşdirmənin serveri",
|
||||||
"on": "Qoşmaq",
|
"on": "Qoşmaq",
|
||||||
"advanced": "Təfərrüatlar",
|
"advanced": "Təfərrüatlar",
|
||||||
"profile": "Profil"
|
"profile": "Profil",
|
||||||
|
"authentication": "Müəyyənləşdirilmə",
|
||||||
|
"low_priority": "Əhəmiyyətsizlər",
|
||||||
|
"historical": "Arxiv"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"continue": "Davam etmək",
|
"continue": "Davam etmək",
|
||||||
|
@ -114,7 +94,9 @@
|
||||||
"close": "Bağlamaq",
|
"close": "Bağlamaq",
|
||||||
"accept": "Qəbul etmək",
|
"accept": "Qəbul etmək",
|
||||||
"register": "Qeydiyyatdan keçmək",
|
"register": "Qeydiyyatdan keçmək",
|
||||||
"unban": "Blokdan çıxarmaq"
|
"unban": "Blokdan çıxarmaq",
|
||||||
|
"unignore": "Blokdan çıxarmaq",
|
||||||
|
"explore_rooms": "Otaqları kəşf edin"
|
||||||
},
|
},
|
||||||
"keyboard": {
|
"keyboard": {
|
||||||
"home": "Başlanğıc"
|
"home": "Başlanğıc"
|
||||||
|
@ -155,7 +137,18 @@
|
||||||
"msisdn_in_use": "Bu telefon nömrəsi artıq istifadə olunur",
|
"msisdn_in_use": "Bu telefon nömrəsi artıq istifadə olunur",
|
||||||
"add_email_dialog_title": "Emal ünvan əlavə etmək",
|
"add_email_dialog_title": "Emal ünvan əlavə etmək",
|
||||||
"add_email_failed_verification": "Email-i yoxlamağı bacarmadı: əmin olun ki, siz məktubda istinaddakı ünvana keçdiniz",
|
"add_email_failed_verification": "Email-i yoxlamağı bacarmadı: əmin olun ki, siz məktubda istinaddakı ünvana keçdiniz",
|
||||||
"add_msisdn_dialog_title": "Telefon nömrəsi əlavə etmək"
|
"add_msisdn_dialog_title": "Telefon nömrəsi əlavə etmək",
|
||||||
|
"error_password_change_403": "Şifrəni əvəz etməyi bacarmadı. Siz cari şifrə düzgün daxil etdiniz?",
|
||||||
|
"deactivate_section": "Hesabı bağlamaq",
|
||||||
|
"error_email_verification": "Email-i yoxlamağı bacarmadı.",
|
||||||
|
"incorrect_msisdn_verification": "Təsdiq etmənin səhv kodu",
|
||||||
|
"error_set_name": "Görünüş adını təyin etmək bacarmadı",
|
||||||
|
"error_remove_3pid": "Əlaqə məlumatlarının silməyi bacarmadı",
|
||||||
|
"error_invalid_email": "Yanlış email",
|
||||||
|
"error_add_email": "Email-i əlavə etməyə müvəffəq olmur"
|
||||||
|
},
|
||||||
|
"key_export_import": {
|
||||||
|
"confirm_passphrase": "Şifrəni təsdiqləyin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"timeline": {
|
"timeline": {
|
||||||
|
@ -280,7 +273,9 @@
|
||||||
"autocomplete": {
|
"autocomplete": {
|
||||||
"command_description": "Komandalar",
|
"command_description": "Komandalar",
|
||||||
"user_description": "İstifadəçilər"
|
"user_description": "İstifadəçilər"
|
||||||
}
|
},
|
||||||
|
"no_perms_notice": "Siz bu otağa yaza bilmirsiniz",
|
||||||
|
"poll_button_no_perms_title": "İzn tələb olunur"
|
||||||
},
|
},
|
||||||
"space": {
|
"space": {
|
||||||
"context_menu": {
|
"context_menu": {
|
||||||
|
@ -291,12 +286,18 @@
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"no_privileged_users": "Heç bir istifadəçi bu otaqda xüsusi hüquqlara malik deyil",
|
"no_privileged_users": "Heç bir istifadəçi bu otaqda xüsusi hüquqlara malik deyil",
|
||||||
"banned_users_section": "Bloklanmış istifadəçilər",
|
"banned_users_section": "Bloklanmış istifadəçilər",
|
||||||
"permissions_section": "Girişin hüquqları"
|
"permissions_section": "Girişin hüquqları",
|
||||||
|
"error_unbanning": "Blokdan çıxarmağı bacarmadı",
|
||||||
|
"banned_by": "%(displayName)s bloklanıb",
|
||||||
|
"ban_reason": "Səbəb"
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"history_visibility_legend": "Kim tarixi oxuya bilər?"
|
"history_visibility_legend": "Kim tarixi oxuya bilər?"
|
||||||
},
|
},
|
||||||
"upload_avatar_label": "Avatar-ı yükləmək"
|
"upload_avatar_label": "Avatar-ı yükləmək",
|
||||||
|
"general": {
|
||||||
|
"other_section": "Digər"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"failed_load_async_component": "Yükləmək olmur! Şəbəkə bağlantınızı yoxlayın və yenidən cəhd edin.",
|
"failed_load_async_component": "Yükləmək olmur! Şəbəkə bağlantınızı yoxlayın və yenidən cəhd edin.",
|
||||||
"upload_failed_generic": "'%(fileName)s' faylı yüklənə bilmədi.",
|
"upload_failed_generic": "'%(fileName)s' faylı yüklənə bilmədi.",
|
||||||
|
@ -337,16 +338,24 @@
|
||||||
"upgrade_error_description": "Serverinizin seçilmiş otaq versiyasını dəstəklədiyini bir daha yoxlayın və yenidən cəhd edin.",
|
"upgrade_error_description": "Serverinizin seçilmiş otaq versiyasını dəstəklədiyini bir daha yoxlayın və yenidən cəhd edin.",
|
||||||
"context_menu": {
|
"context_menu": {
|
||||||
"favourite": "Seçilmiş"
|
"favourite": "Seçilmiş"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"forget_room_button": "Otağı unutmaq"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"enable_prompt_toast_title": "Xəbərdarlıqlar",
|
"enable_prompt_toast_title": "Xəbərdarlıqlar",
|
||||||
"class_other": "Digər"
|
"class_other": "Digər",
|
||||||
|
"default": "Varsayılan olaraq"
|
||||||
},
|
},
|
||||||
"encryption": {
|
"encryption": {
|
||||||
"not_supported": "<dəstəklənmir>"
|
"not_supported": "<dəstəklənmir>"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"update_power_level": "Hüquqların səviyyəsini dəyişdirməyi bacarmadı"
|
"update_power_level": "Hüquqların səviyyəsini dəyişdirməyi bacarmadı"
|
||||||
|
},
|
||||||
|
"member_list": {
|
||||||
|
"invited_list_heading": "Dəvət edilmişdir",
|
||||||
|
"filter_placeholder": "İştirakçılara görə axtarış"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
{
|
{
|
||||||
"Failed to forget room %(errCode)s": "Не атрымалася забыць пакой %(errCode)s",
|
"Failed to forget room %(errCode)s": "Не атрымалася забыць пакой %(errCode)s",
|
||||||
"All messages": "Усе паведамленні",
|
|
||||||
"Invite to this room": "Запрасіць у гэты пакой",
|
|
||||||
"common": {
|
"common": {
|
||||||
"error": "Памылка",
|
"error": "Памылка",
|
||||||
"mute": "Без гуку",
|
"mute": "Без гуку",
|
||||||
|
@ -34,7 +32,8 @@
|
||||||
"failed_generic": "Не атрымалася выканаць аперацыю"
|
"failed_generic": "Не атрымалася выканаць аперацыю"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"enable_prompt_toast_title": "Апавяшчэнні"
|
"enable_prompt_toast_title": "Апавяшчэнні",
|
||||||
|
"all_messages": "Усе паведамленні"
|
||||||
},
|
},
|
||||||
"timeline": {
|
"timeline": {
|
||||||
"context_menu": {
|
"context_menu": {
|
||||||
|
@ -45,6 +44,7 @@
|
||||||
"context_menu": {
|
"context_menu": {
|
||||||
"favourite": "Улюбёнае",
|
"favourite": "Улюбёнае",
|
||||||
"low_priority": "Нізкі прыярытэт"
|
"low_priority": "Нізкі прыярытэт"
|
||||||
}
|
},
|
||||||
|
"invite_this_room": "Запрасіць у гэты пакой"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"Send": "Изпрати",
|
"Send": "Изпрати",
|
||||||
"Failed to change password. Is your password correct?": "Неуспешна промяна. Правилно ли сте въвели Вашата парола?",
|
|
||||||
"Sun": "нд.",
|
"Sun": "нд.",
|
||||||
"Mon": "пн.",
|
"Mon": "пн.",
|
||||||
"Tue": "вт.",
|
"Tue": "вт.",
|
||||||
|
@ -26,50 +25,31 @@
|
||||||
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s %(time)s",
|
"%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s": "%(weekDayName)s, %(day)s %(monthName)s %(fullYear)s %(time)s",
|
||||||
"unknown error code": "неизвестен код за грешка",
|
"unknown error code": "неизвестен код за грешка",
|
||||||
"Failed to forget room %(errCode)s": "Неуспешно забравяне на стаята %(errCode)s",
|
"Failed to forget room %(errCode)s": "Неуспешно забравяне на стаята %(errCode)s",
|
||||||
"Rooms": "Стаи",
|
|
||||||
"Unnamed room": "Стая без име",
|
"Unnamed room": "Стая без име",
|
||||||
"Warning!": "Внимание!",
|
"Warning!": "Внимание!",
|
||||||
"PM": "PM",
|
"PM": "PM",
|
||||||
"AM": "AM",
|
"AM": "AM",
|
||||||
"Default": "По подразбиране",
|
|
||||||
"Restricted": "Ограничен",
|
"Restricted": "Ограничен",
|
||||||
"Moderator": "Модератор",
|
"Moderator": "Модератор",
|
||||||
"Reason": "Причина",
|
|
||||||
"Incorrect verification code": "Неправилен код за потвърждение",
|
|
||||||
"Authentication": "Автентикация",
|
|
||||||
"Failed to set display name": "Неуспешно задаване на име",
|
|
||||||
"Failed to ban user": "Неуспешно блокиране на потребителя",
|
"Failed to ban user": "Неуспешно блокиране на потребителя",
|
||||||
"Failed to mute user": "Неуспешно заглушаване на потребителя",
|
"Failed to mute user": "Неуспешно заглушаване на потребителя",
|
||||||
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "След като си намалите нивото на достъп, няма да можете да възвърнете тази промяна. Ако сте последния потребител с привилегии в тази стая, ще бъде невъзможно да възвърнете привилегии си.",
|
"You will not be able to undo this change as you are demoting yourself, if you are the last privileged user in the room it will be impossible to regain privileges.": "След като си намалите нивото на достъп, няма да можете да възвърнете тази промяна. Ако сте последния потребител с привилегии в тази стая, ще бъде невъзможно да възвърнете привилегии си.",
|
||||||
"Are you sure?": "Сигурни ли сте?",
|
"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.": "Няма да можете да възвърнете тази промяна, тъй като повишавате този потребител до същото ниво на достъп като Вашето.",
|
"You will not be able to undo this change as you are promoting the user to have the same power level as yourself.": "Няма да можете да възвърнете тази промяна, тъй като повишавате този потребител до същото ниво на достъп като Вашето.",
|
||||||
"Unignore": "Премахни игнорирането",
|
|
||||||
"Admin Tools": "Инструменти на администратора",
|
"Admin Tools": "Инструменти на администратора",
|
||||||
"and %(count)s others...": {
|
"and %(count)s others...": {
|
||||||
"other": "и %(count)s други...",
|
"other": "и %(count)s други...",
|
||||||
"one": "и още един..."
|
"one": "и още един..."
|
||||||
},
|
},
|
||||||
"Invited": "Поканен",
|
|
||||||
"Filter room members": "Филтриране на членовете",
|
|
||||||
"%(userName)s (power %(powerLevelNumber)s)": "%(userName)s (ниво на достъп %(powerLevelNumber)s)",
|
|
||||||
"You do not have permission to post to this room": "Нямате разрешение да публикувате в тази стая",
|
|
||||||
"%(duration)ss": "%(duration)sсек",
|
"%(duration)ss": "%(duration)sсек",
|
||||||
"%(duration)sm": "%(duration)sмин",
|
"%(duration)sm": "%(duration)sмин",
|
||||||
"%(duration)sh": "%(duration)sч",
|
"%(duration)sh": "%(duration)sч",
|
||||||
"%(duration)sd": "%(duration)sд",
|
"%(duration)sd": "%(duration)sд",
|
||||||
"Replying": "Отговаря",
|
|
||||||
"(~%(count)s results)": {
|
"(~%(count)s results)": {
|
||||||
"other": "(~%(count)s резултати)",
|
"other": "(~%(count)s резултати)",
|
||||||
"one": "(~%(count)s резултат)"
|
"one": "(~%(count)s резултат)"
|
||||||
},
|
},
|
||||||
"Join Room": "Присъединяване към стаята",
|
"Join Room": "Присъединяване към стаята",
|
||||||
"Forget room": "Забрави стаята",
|
|
||||||
"Low priority": "Нисък приоритет",
|
|
||||||
"Historical": "Архив",
|
|
||||||
"%(roomName)s does not exist.": "%(roomName)s не съществува.",
|
|
||||||
"%(roomName)s is not accessible at this time.": "%(roomName)s не е достъпна към този момент.",
|
|
||||||
"Failed to unban": "Неуспешно отблокиране",
|
|
||||||
"Banned by %(displayName)s": "Блокиран от %(displayName)s",
|
|
||||||
"Jump to first unread message.": "Отиди до първото непрочетено съобщение.",
|
"Jump to first unread message.": "Отиди до първото непрочетено съобщение.",
|
||||||
"not specified": "неопределен",
|
"not specified": "неопределен",
|
||||||
"This room has no local addresses": "Тази стая няма локални адреси",
|
"This room has no local addresses": "Тази стая няма локални адреси",
|
||||||
|
@ -94,17 +74,11 @@
|
||||||
"other": "И %(count)s други..."
|
"other": "И %(count)s други..."
|
||||||
},
|
},
|
||||||
"Confirm Removal": "Потвърдете премахването",
|
"Confirm Removal": "Потвърдете премахването",
|
||||||
"Deactivate Account": "Затвори акаунта",
|
|
||||||
"Verification Pending": "Очакване на потвърждение",
|
"Verification Pending": "Очакване на потвърждение",
|
||||||
"An error has occurred.": "Възникна грешка.",
|
"An error has occurred.": "Възникна грешка.",
|
||||||
"Unable to restore session": "Неуспешно възстановяване на сесията",
|
"Unable to restore session": "Неуспешно възстановяване на сесията",
|
||||||
"Invalid Email Address": "Невалиден имейл адрес",
|
|
||||||
"This doesn't appear to be a valid email address": "Това не изглежда да е валиден имейл адрес",
|
|
||||||
"Please check your email and click on the link it contains. Once this is done, click continue.": "Моля, проверете своя имейл адрес и натиснете връзката, която той съдържа. След като направите това, натиснете продължи.",
|
"Please check your email and click on the link it contains. Once this is done, click continue.": "Моля, проверете своя имейл адрес и натиснете връзката, която той съдържа. След като направите това, натиснете продължи.",
|
||||||
"Unable to add email address": "Неуспешно добавяне на имейл адрес",
|
|
||||||
"Unable to verify email address.": "Неуспешно потвърждение на имейл адрес.",
|
|
||||||
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Беше направен опит да се зареди конкретна точка в хронологията на тази стая, но не я намери.",
|
"Tried to load a specific point in this room's timeline, but was unable to find it.": "Беше направен опит да се зареди конкретна точка в хронологията на тази стая, но не я намери.",
|
||||||
"Unable to remove contact information": "Неуспешно премахване на информацията за контакти",
|
|
||||||
"This will allow you to reset your password and receive notifications.": "Това ще Ви позволи да възстановите Вашата парола и да получавате известия.",
|
"This will allow you to reset your password and receive notifications.": "Това ще Ви позволи да възстановите Вашата парола и да получавате известия.",
|
||||||
"Reject invitation": "Отхвърли поканата",
|
"Reject invitation": "Отхвърли поканата",
|
||||||
"Are you sure you want to reject the invitation?": "Сигурни ли сте, че искате да отхвърлите поканата?",
|
"Are you sure you want to reject the invitation?": "Сигурни ли сте, че искате да отхвърлите поканата?",
|
||||||
|
@ -126,24 +100,12 @@
|
||||||
"one": "Качване на %(filename)s и %(count)s друг"
|
"one": "Качване на %(filename)s и %(count)s друг"
|
||||||
},
|
},
|
||||||
"Uploading %(filename)s": "Качване на %(filename)s",
|
"Uploading %(filename)s": "Качване на %(filename)s",
|
||||||
"No Microphones detected": "Няма открити микрофони",
|
|
||||||
"No Webcams detected": "Няма открити уеб камери",
|
|
||||||
"A new password must be entered.": "Трябва да бъде въведена нова парола.",
|
"A new password must be entered.": "Трябва да бъде въведена нова парола.",
|
||||||
"New passwords must match each other.": "Новите пароли трябва да съвпадат една с друга.",
|
"New passwords must match each other.": "Новите пароли трябва да съвпадат една с друга.",
|
||||||
"Return to login screen": "Връщане към страницата за влизане в профила",
|
"Return to login screen": "Връщане към страницата за влизане в профила",
|
||||||
"Session ID": "Идентификатор на сесията",
|
"Session ID": "Идентификатор на сесията",
|
||||||
"Passphrases must match": "Паролите трябва да съвпадат",
|
|
||||||
"Passphrase must not be empty": "Паролата не трябва да е празна",
|
|
||||||
"Export room keys": "Експортиране на ключове за стаята",
|
|
||||||
"Enter passphrase": "Въведи парола",
|
|
||||||
"Confirm passphrase": "Потвърди парола",
|
|
||||||
"Import room keys": "Импортиране на ключове за стая",
|
|
||||||
"File to import": "Файл за импортиране",
|
|
||||||
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "На път сте да бъдете отведени до друг сайт, където можете да удостоверите профила си за използване с %(integrationsUrl)s. Искате ли да продължите?",
|
"You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "На път сте да бъдете отведени до друг сайт, където можете да удостоверите профила си за използване с %(integrationsUrl)s. Искате ли да продължите?",
|
||||||
"If you have previously used a more recent version of %(brand)s, your session may be incompatible with this version. Close this window and return to the more recent version.": "Ако преди сте използвали по-нова версия на %(brand)s, Вашата сесия може да не бъде съвместима с текущата версия. Затворете този прозорец и се върнете в по-новата версия.",
|
"If you have previously used a more recent version of %(brand)s, your session may be incompatible with this version. Close this window and return to the more recent version.": "Ако преди сте използвали по-нова версия на %(brand)s, Вашата сесия може да не бъде съвместима с текущата версия. Затворете този прозорец и се върнете в по-новата версия.",
|
||||||
"This process allows you to export the keys for messages you have received in encrypted rooms to a local file. You will then be able to import the file into another Matrix client in the future, so that client will also be able to decrypt these messages.": "Този процес Ви позволява да експортирате във файл ключовете за съобщения в шифровани стаи. Така ще можете да импортирате файла в друг Matrix клиент, така че той също да може да разшифрова такива съобщения.",
|
|
||||||
"This process allows you to import encryption keys that you had previously exported from another Matrix client. You will then be able to decrypt any messages that the other client could decrypt.": "Този процес позволява да импортирате ключове за шифроване, които преди сте експортирали от друг Matrix клиент. Тогава ще можете да разшифровате всяко съобщение, което другият клиент може да разшифрова.",
|
|
||||||
"The export file will be protected with a passphrase. You should enter the passphrase here, to decrypt the file.": "Експортираният файл може да бъде предпазен с парола. Трябва да въведете парола тук, за да разшифровате файла.",
|
|
||||||
"You don't currently have any stickerpacks enabled": "В момента нямате включени пакети със стикери",
|
"You don't currently have any stickerpacks enabled": "В момента нямате включени пакети със стикери",
|
||||||
"Sunday": "Неделя",
|
"Sunday": "Неделя",
|
||||||
"Today": "Днес",
|
"Today": "Днес",
|
||||||
|
@ -160,8 +122,6 @@
|
||||||
"Monday": "Понеделник",
|
"Monday": "Понеделник",
|
||||||
"All Rooms": "Във всички стаи",
|
"All Rooms": "Във всички стаи",
|
||||||
"Wednesday": "Сряда",
|
"Wednesday": "Сряда",
|
||||||
"All messages": "Всички съобщения",
|
|
||||||
"Invite to this room": "Покани в тази стая",
|
|
||||||
"You cannot delete this message. (%(code)s)": "Това съобщение не може да бъде изтрито. (%(code)s)",
|
"You cannot delete this message. (%(code)s)": "Това съобщение не може да бъде изтрито. (%(code)s)",
|
||||||
"Thursday": "Четвъртък",
|
"Thursday": "Четвъртък",
|
||||||
"Logs sent": "Логовете са изпратени",
|
"Logs sent": "Логовете са изпратени",
|
||||||
|
@ -179,13 +139,10 @@
|
||||||
"Share User": "Споделяне на потребител",
|
"Share User": "Споделяне на потребител",
|
||||||
"Share Room Message": "Споделяне на съобщение от стая",
|
"Share Room Message": "Споделяне на съобщение от стая",
|
||||||
"Link to selected message": "Създай връзка към избраното съобщение",
|
"Link to selected message": "Създай връзка към избраното съобщение",
|
||||||
"No Audio Outputs detected": "Не са открити аудио изходи",
|
|
||||||
"Audio Output": "Аудио изходи",
|
|
||||||
"You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "Не можете да изпращате съобщения докато не прегледате и се съгласите с <consentLink>нашите правила и условия</consentLink>.",
|
"You can't send any messages until you review and agree to <consentLink>our terms and conditions</consentLink>.": "Не можете да изпращате съобщения докато не прегледате и се съгласите с <consentLink>нашите правила и условия</consentLink>.",
|
||||||
"Demote yourself?": "Понижете себе си?",
|
"Demote yourself?": "Понижете себе си?",
|
||||||
"Demote": "Понижение",
|
"Demote": "Понижение",
|
||||||
"This event could not be displayed": "Това събитие не може да бъде показано",
|
"This event could not be displayed": "Това събитие не може да бъде показано",
|
||||||
"Permission Required": "Необходимо е разрешение",
|
|
||||||
"Only room administrators will see this warning": "Само администратори на стаята виждат това предупреждение",
|
"Only room administrators will see this warning": "Само администратори на стаята виждат това предупреждение",
|
||||||
"Upgrade Room Version": "Обнови версията на стаята",
|
"Upgrade Room Version": "Обнови версията на стаята",
|
||||||
"Create a new room with the same name, description and avatar": "Създадем нова стая със същото име, описание и снимка",
|
"Create a new room with the same name, description and avatar": "Създадем нова стая със същото име, описание и снимка",
|
||||||
|
@ -194,8 +151,6 @@
|
||||||
"Put a link back to the old room at the start of the new room so people can see old messages": "Поставим връзка в новата стая, водещо обратно към старата, за да може хората да виждат старите съобщения",
|
"Put a link back to the old room at the start of the new room so people can see old messages": "Поставим връзка в новата стая, водещо обратно към старата, за да може хората да виждат старите съобщения",
|
||||||
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please <a>contact your service administrator</a> to continue using the service.": "Съобщението Ви не бе изпратено, защото този сървър е достигнал лимита си за потребители на месец. Моля, <a>свържете се с администратора на услугата</a> за да продължите да я използвате.",
|
"Your message wasn't sent because this homeserver has hit its Monthly Active User Limit. Please <a>contact your service administrator</a> to continue using the service.": "Съобщението Ви не бе изпратено, защото този сървър е достигнал лимита си за потребители на месец. Моля, <a>свържете се с администратора на услугата</a> за да продължите да я използвате.",
|
||||||
"Your message wasn't sent because this homeserver has exceeded a resource limit. Please <a>contact your service administrator</a> to continue using the service.": "Съобщението Ви не бе изпратено, защото този сървър е някой от лимитите си. Моля, <a>свържете се с администратора на услугата</a> за да продължите да я използвате.",
|
"Your message wasn't sent because this homeserver has exceeded a resource limit. Please <a>contact your service administrator</a> to continue using the service.": "Съобщението Ви не бе изпратено, защото този сървър е някой от лимитите си. Моля, <a>свържете се с администратора на услугата</a> за да продължите да я използвате.",
|
||||||
"This room has been replaced and is no longer active.": "Тази стая е била заменена и вече не е активна.",
|
|
||||||
"The conversation continues here.": "Разговора продължава тук.",
|
|
||||||
"Failed to upgrade room": "Неуспешно обновяване на стаята",
|
"Failed to upgrade room": "Неуспешно обновяване на стаята",
|
||||||
"The room upgrade could not be completed": "Обновяването на тази стая не можа да бъде завършено",
|
"The room upgrade could not be completed": "Обновяването на тази стая не можа да бъде завършено",
|
||||||
"Upgrade this room to version %(version)s": "Обновете тази стая до версия %(version)s",
|
"Upgrade this room to version %(version)s": "Обновете тази стая до версия %(version)s",
|
||||||
|
@ -218,33 +173,11 @@
|
||||||
"Invalid homeserver discovery response": "Невалиден отговор по време на откриването на конфигурацията за сървъра",
|
"Invalid homeserver discovery response": "Невалиден отговор по време на откриването на конфигурацията за сървъра",
|
||||||
"Invalid identity server discovery response": "Невалиден отговор по време на откриването на конфигурацията за сървъра за самоличност",
|
"Invalid identity server discovery response": "Невалиден отговор по време на откриването на конфигурацията за сървъра за самоличност",
|
||||||
"General failure": "Обща грешка",
|
"General failure": "Обща грешка",
|
||||||
"That matches!": "Това съвпада!",
|
|
||||||
"That doesn't match.": "Това не съвпада.",
|
|
||||||
"Go back to set it again.": "Върнете се за да настройте нова.",
|
|
||||||
"Unable to create key backup": "Неуспешно създаване на резервно копие на ключа",
|
|
||||||
"Unable to load commit detail: %(msg)s": "Неуспешно зареждане на информация за commit: %(msg)s",
|
"Unable to load commit detail: %(msg)s": "Неуспешно зареждане на информация за commit: %(msg)s",
|
||||||
"New Recovery Method": "Нов метод за възстановяване",
|
|
||||||
"If you didn't set the new recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Ако не сте настройвали новия метод за възстановяване, вероятно някой се опитва да проникне в акаунта Ви. Веднага променете паролата на акаунта си и настройте нов метод за възстановяване от Настройки.",
|
|
||||||
"Set up Secure Messages": "Настрой Защитени Съобщения",
|
|
||||||
"Go to Settings": "Отиди в Настройки",
|
|
||||||
"The following users may not exist": "Следните потребители може да не съществуват",
|
"The following users may not exist": "Следните потребители може да не съществуват",
|
||||||
"Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Не бяга открити профили за изброените по-долу Matrix идентификатори. Желаете ли да ги поканите въпреки това?",
|
"Unable to find profiles for the Matrix IDs listed below - would you like to invite them anyway?": "Не бяга открити профили за изброените по-долу Matrix идентификатори. Желаете ли да ги поканите въпреки това?",
|
||||||
"Invite anyway and never warn me again": "Покани въпреки това и не питай отново",
|
"Invite anyway and never warn me again": "Покани въпреки това и не питай отново",
|
||||||
"Invite anyway": "Покани въпреки това",
|
"Invite anyway": "Покани въпреки това",
|
||||||
"We've sent you an email to verify your address. Please follow the instructions there and then click the button below.": "Изпратихме Ви имейл за да потвърдим адреса Ви. Последвайте инструкциите в имейла и след това кликнете на бутона по-долу.",
|
|
||||||
"Email Address": "Имейл адрес",
|
|
||||||
"Unable to verify phone number.": "Неуспешно потвърждение на телефонния номер.",
|
|
||||||
"Verification code": "Код за потвърждение",
|
|
||||||
"Phone Number": "Телефонен номер",
|
|
||||||
"Room information": "Информация за стаята",
|
|
||||||
"Room Addresses": "Адреси на стаята",
|
|
||||||
"Email addresses": "Имейл адреси",
|
|
||||||
"Phone numbers": "Телефонни номера",
|
|
||||||
"Account management": "Управление на акаунта",
|
|
||||||
"Ignored users": "Игнорирани потребители",
|
|
||||||
"Missing media permissions, click the button below to request.": "Липсва достъп до медийните устройства. Кликнете бутона по-долу за да поискате такъв.",
|
|
||||||
"Request media permissions": "Поискай достъп до медийните устройства",
|
|
||||||
"Voice & Video": "Глас и видео",
|
|
||||||
"Main address": "Основен адрес",
|
"Main address": "Основен адрес",
|
||||||
"Room avatar": "Снимка на стаята",
|
"Room avatar": "Снимка на стаята",
|
||||||
"Room Name": "Име на стаята",
|
"Room Name": "Име на стаята",
|
||||||
|
@ -253,8 +186,6 @@
|
||||||
"Incoming Verification Request": "Входяща заявка за потвърждение",
|
"Incoming Verification Request": "Входяща заявка за потвърждение",
|
||||||
"Email (optional)": "Имейл (незадължително)",
|
"Email (optional)": "Имейл (незадължително)",
|
||||||
"Join millions for free on the largest public server": "Присъединете се безплатно към милиони други на най-големия публичен сървър",
|
"Join millions for free on the largest public server": "Присъединете се безплатно към милиони други на най-големия публичен сървър",
|
||||||
"Recovery Method Removed": "Методът за възстановяване беше премахнат",
|
|
||||||
"If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "Ако не сте премахнали метода за възстановяване, е възможно нападател да се опитва да получи достъп до акаунта Ви. Променете паролата на акаунта и настройте нов метод за възстановяване веднага от Настройки.",
|
|
||||||
"Dog": "Куче",
|
"Dog": "Куче",
|
||||||
"Cat": "Котка",
|
"Cat": "Котка",
|
||||||
"Lion": "Лъв",
|
"Lion": "Лъв",
|
||||||
|
@ -327,8 +258,6 @@
|
||||||
"You'll lose access to your encrypted messages": "Ще загубите достъп до шифрованите си съобщения",
|
"You'll lose access to your encrypted messages": "Ще загубите достъп до шифрованите си съобщения",
|
||||||
"Are you sure you want to sign out?": "Сигурни ли сте, че искате да излезете от профила?",
|
"Are you sure you want to sign out?": "Сигурни ли сте, че искате да излезете от профила?",
|
||||||
"<b>Warning</b>: you should only set up key backup from a trusted computer.": "<b>Внимание</b>: настройването на резервно копие на ключовете трябва да се прави само от доверен компютър.",
|
"<b>Warning</b>: you should only set up key backup from a trusted computer.": "<b>Внимание</b>: настройването на резервно копие на ключовете трябва да се прави само от доверен компютър.",
|
||||||
"Your keys are being backed up (the first backup could take a few minutes).": "Прави се резервно копие на ключовете Ви (първото копие може да отнеме няколко минути).",
|
|
||||||
"Success!": "Успешно!",
|
|
||||||
"Scissors": "Ножици",
|
"Scissors": "Ножици",
|
||||||
"Error updating main address": "Грешка при обновяване на основния адрес",
|
"Error updating main address": "Грешка при обновяване на основния адрес",
|
||||||
"There was an error updating the room's main address. 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.": "Случи се грешка при обновяването на основния адрес за стаята. Може да не е позволено от сървъра, или да се е случила друга временна грешка.",
|
||||||
|
@ -359,31 +288,10 @@
|
||||||
"Cancel All": "Откажи всички",
|
"Cancel All": "Откажи всички",
|
||||||
"Upload Error": "Грешка при качване",
|
"Upload Error": "Грешка при качване",
|
||||||
"Remember my selection for this widget": "Запомни избора ми за това приспособление",
|
"Remember my selection for this widget": "Запомни избора ми за това приспособление",
|
||||||
"Join the conversation with an account": "Присъедини се към разговор с акаунт",
|
|
||||||
"Sign Up": "Регистриране",
|
|
||||||
"Reason: %(reason)s": "Причина: %(reason)s",
|
|
||||||
"Forget this room": "Пропусни тази стая",
|
|
||||||
"Re-join": "Връщане",
|
|
||||||
"You were banned from %(roomName)s by %(memberName)s": "Получихте забрана за %(roomName)s от %(memberName)s",
|
|
||||||
"Something went wrong with your invite to %(roomName)s": "Нещо нежелано се случи с вашата покана към %(roomName)s",
|
|
||||||
"You can only join it with a working invite.": "Да се присъедините можете само с активна покана.",
|
|
||||||
"Join the discussion": "Присъединете се към разговора",
|
|
||||||
"Try to join anyway": "Опитай да се присъединиш все пак",
|
|
||||||
"Do you want to chat with %(user)s?": "Желаете ли да си поговорите с %(user)s?",
|
|
||||||
"Do you want to join %(roomName)s?": "Желаете ли да се присъедините към %(roomName)s?",
|
|
||||||
"<userName/> invited you": "<userName/> ви покани",
|
|
||||||
"You're previewing %(roomName)s. Want to join it?": "Предварителен преглед на %(roomName)s. Желаете ли да се влезете?",
|
|
||||||
"%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s не може да бъде прегледана предварително. Желаете ли да се влезете?",
|
|
||||||
"Uploaded sound": "Качен звук",
|
|
||||||
"Sounds": "Звуци",
|
|
||||||
"Notification sound": "Звук за уведомление",
|
|
||||||
"Set a new custom sound": "Настрой нов собствен звук",
|
|
||||||
"Browse": "Избор",
|
|
||||||
"This room has already been upgraded.": "Тази стая вече е била обновена.",
|
"This room has already been upgraded.": "Тази стая вече е била обновена.",
|
||||||
"edited": "редактирано",
|
"edited": "редактирано",
|
||||||
"Edit message": "Редактирай съобщението",
|
"Edit message": "Редактирай съобщението",
|
||||||
"Some characters not allowed": "Някои символи не са позволени",
|
"Some characters not allowed": "Някои символи не са позволени",
|
||||||
"Add room": "Добави стая",
|
|
||||||
"Failed to get autodiscovery configuration from server": "Неуспешно автоматично откриване на конфигурацията за сървъра",
|
"Failed to get autodiscovery configuration from server": "Неуспешно автоматично откриване на конфигурацията за сървъра",
|
||||||
"Invalid base_url for m.homeserver": "Невалиден base_url в m.homeserver",
|
"Invalid base_url for m.homeserver": "Невалиден base_url в m.homeserver",
|
||||||
"Homeserver URL does not appear to be a valid Matrix homeserver": "Homeserver адресът не изглежда да е валиден Matrix сървър",
|
"Homeserver URL does not appear to be a valid Matrix homeserver": "Homeserver адресът не изглежда да е валиден Matrix сървър",
|
||||||
|
@ -398,55 +306,15 @@
|
||||||
"Clear all data": "Изчисти всички данни",
|
"Clear all data": "Изчисти всички данни",
|
||||||
"Your homeserver doesn't seem to support this feature.": "Не изглежда сървърът ви да поддържа тази функция.",
|
"Your homeserver doesn't seem to support this feature.": "Не изглежда сървърът ви да поддържа тази функция.",
|
||||||
"Resend %(unsentCount)s reaction(s)": "Изпрати наново %(unsentCount)s реакция(и)",
|
"Resend %(unsentCount)s reaction(s)": "Изпрати наново %(unsentCount)s реакция(и)",
|
||||||
"Failed to re-authenticate due to a homeserver problem": "Неуспешна повторна автентикация поради проблем със сървъра",
|
|
||||||
"Clear personal data": "Изчисти личните данни",
|
|
||||||
"Find others by phone or email": "Открийте други по телефон или имейл",
|
"Find others by phone or email": "Открийте други по телефон или имейл",
|
||||||
"Be found by phone or email": "Бъдете открит по телефон или имейл",
|
"Be found by phone or email": "Бъдете открит по телефон или имейл",
|
||||||
"Checking 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 <idserver />?": "Прекъсване на връзката със сървър за самоличност <idserver />?",
|
|
||||||
"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> за да откривате и да бъдете открити от познати ваши контакти. Може да промените сървъра за самоличност по-долу.",
|
|
||||||
"You are not currently using an identity server. To discover and be discoverable by existing contacts you know, add one below.": "В момента не използвате сървър за самоличност. За да откривате и да бъдете открити от познати ваши контакти, добавете такъв по-долу.",
|
|
||||||
"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.": "Прекъсването на връзката със сървъра ви за самоличност означава че няма да можете да бъдете открити от други потребители или да каните хора по имейл или телефонен номер.",
|
|
||||||
"Enter a new identity server": "Въведете нов сървър за самоличност",
|
|
||||||
"Discovery": "Откриване",
|
|
||||||
"Deactivate account": "Деактивиране на акаунт",
|
"Deactivate account": "Деактивиране на акаунт",
|
||||||
"Unable to revoke sharing for email address": "Неуспешно оттегляне на споделянето на имейл адреса",
|
|
||||||
"Unable to share email address": "Неуспешно споделяне на имейл адрес",
|
|
||||||
"Discovery options will appear once you have added an email above.": "Опциите за откриване ще се покажат след като добавите имейл адрес по-горе.",
|
|
||||||
"Unable to revoke sharing for phone number": "Неуспешно оттегляне на споделянето на телефонен номер",
|
|
||||||
"Unable to share phone number": "Неуспешно споделяне на телефонен номер",
|
|
||||||
"Please enter verification code sent via text.": "Въведете кода за потвърждение получен в SMS.",
|
|
||||||
"Discovery options will appear once you have added a phone number above.": "Опциите за откриване ще се покажат след като добавите телефонен номер по-горе.",
|
|
||||||
"Remove %(email)s?": "Премахни %(email)s?",
|
|
||||||
"Remove %(phone)s?": "Премахни %(phone)s?",
|
|
||||||
"A text message has been sent to +%(msisdn)s. Please enter the verification code it contains.": "Беше изпратено SMS съобщение към +%(msisdn)s. Въведете съдържащият се код за потвърждение.",
|
|
||||||
"Command Help": "Помощ за команди",
|
"Command Help": "Помощ за команди",
|
||||||
"If you don't want to use <server /> to discover and be discoverable by existing contacts you know, enter another identity server below.": "Ако не искате да използвате <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.": "Използването на сървър за самоличност не е задължително. Ако не използвате такъв, няма да бъдете откриваеми от други потребители и няма да можете да ги каните по имейл или телефон.",
|
|
||||||
"Do not use an identity server": "Не ползвай сървър за самоличност",
|
|
||||||
"Agree to the identity server (%(serverName)s) Terms of Service to allow yourself to be discoverable by email address or phone number.": "Приемете условията за ползване на сървъра за самоличност (%(serverName)s) за да бъдете откриваеми по имейл адрес или телефонен номер.",
|
|
||||||
"Error changing power level": "Грешка при промяната на нивото на достъп",
|
|
||||||
"An error occurred changing the user's power level. Ensure you have sufficient permissions and try again.": "Възникна грешка при промяната на нивото на достъп на потребителя. Уверете се, че имате необходимите привилегии и опитайте пак.",
|
|
||||||
"Deactivate user?": "Деактивиране на потребителя?",
|
"Deactivate user?": "Деактивиране на потребителя?",
|
||||||
"Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Деактивирането на потребителя ще ги изхвърли от профила и няма да им позволи да влязат пак. Също така, ще напуснат всички стаи, в които са. Действието е необратимо. Сигурните ли сте, че искате да деактивирате този потребител?",
|
"Deactivating this user will log them out and prevent them from logging back in. Additionally, they will leave all the rooms they are in. This action cannot be reversed. Are you sure you want to deactivate this user?": "Деактивирането на потребителя ще ги изхвърли от профила и няма да им позволи да влязат пак. Също така, ще напуснат всички стаи, в които са. Действието е необратимо. Сигурните ли сте, че искате да деактивирате този потребител?",
|
||||||
"Deactivate user": "Деактивирай потребителя",
|
"Deactivate user": "Деактивирай потребителя",
|
||||||
"This invite to %(roomName)s was sent to %(email)s which is not associated with your account": "Тази покана за %(roomName)s е била изпратена към адрес %(email)s, който не е асоцииран с профила ви",
|
|
||||||
"Link this email with your account in Settings to receive invites directly in %(brand)s.": "Свържете този имейл адрес с профила си от Настройки за да получавате покани директно в %(brand)s.",
|
|
||||||
"This invite to %(roomName)s was sent to %(email)s": "Тази покана за %(roomName)s беше изпратена към адрес %(email)s",
|
|
||||||
"Use an identity server in Settings to receive invites directly in %(brand)s.": "Използвайте сървър за самоличност от Настройки за да получавате покани директно в %(brand)s.",
|
|
||||||
"Share this email in Settings to receive invites directly in %(brand)s.": "Споделете този имейл в Настройки за да получавате покани директно в %(brand)s.",
|
|
||||||
"Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.": "Използвайте сървър за самоличност за да каните по имейл. <default>Използвайте сървъра за самоличност по подразбиране (%(defaultIdentityServerName)s)</default> или настройте друг в <settings>Настройки</settings>.",
|
"Use an identity server to invite by email. <default>Use the default (%(defaultIdentityServerName)s)</default> or manage in <settings>Settings</settings>.": "Използвайте сървър за самоличност за да каните по имейл. <default>Използвайте сървъра за самоличност по подразбиране (%(defaultIdentityServerName)s)</default> или настройте друг в <settings>Настройки</settings>.",
|
||||||
"Use an identity server to invite by email. Manage in <settings>Settings</settings>.": "Използвайте сървър за самоличност за да каните по имейл. Управлявайте в <settings>Настройки</settings>.",
|
"Use an identity server to invite by email. Manage in <settings>Settings</settings>.": "Използвайте сървър за самоличност за да каните по имейл. Управлявайте в <settings>Настройки</settings>.",
|
||||||
"Change identity server": "Промени сървъра за самоличност",
|
|
||||||
"Disconnect from the identity server <current /> and connect to <new /> instead?": "Прекъсване на връзката със сървър за самоличност <current /> и свързване с <new />?",
|
|
||||||
"Disconnect identity server": "Прекъсни връзката със сървъра за самоличност",
|
|
||||||
"You are still <b>sharing your personal data</b> on the identity server <idserver />.": "Все още <b>споделяте лични данни</b> със сървър за самоличност <idserver />.",
|
|
||||||
"We recommend that you remove your email addresses and phone numbers from the identity server before disconnecting.": "Препоръчваме да премахнете имейл адреса и телефонния си номер от сървъра за самоличност преди прекъсване на връзката.",
|
|
||||||
"Disconnect anyway": "Прекъсни въпреки всичко",
|
|
||||||
"Error changing power level requirement": "Грешка при промяна на изискванията за ниво на достъп",
|
|
||||||
"An error occurred changing the room's power level requirements. Ensure you have sufficient permissions and try again.": "Възникна грешка при промяна на изискванията за нива на достъп до стаята. Уверете се, че имате необходимите права и опитайте пак.",
|
|
||||||
"No recent messages by %(user)s found": "Не са намерени скорошни съобщения от %(user)s",
|
"No recent messages by %(user)s found": "Не са намерени скорошни съобщения от %(user)s",
|
||||||
"Try scrolling up in the timeline to see if there are any earlier ones.": "Опитайте се да проверите по-нагоре в историята за по-ранни.",
|
"Try scrolling up in the timeline to see if there are any earlier ones.": "Опитайте се да проверите по-нагоре в историята за по-ранни.",
|
||||||
"Remove recent messages by %(user)s": "Премахване на скорошни съобщения от %(user)s",
|
"Remove recent messages by %(user)s": "Премахване на скорошни съобщения от %(user)s",
|
||||||
|
@ -456,19 +324,8 @@
|
||||||
"one": "Премахни 1 съобщение"
|
"one": "Премахни 1 съобщение"
|
||||||
},
|
},
|
||||||
"Remove recent messages": "Премахни скорошни съобщения",
|
"Remove recent messages": "Премахни скорошни съобщения",
|
||||||
"Italics": "Наклонено",
|
|
||||||
"Explore rooms": "Открий стаи",
|
|
||||||
"Verify the link in your inbox": "Потвърдете линка във вашата пощенска кутия",
|
|
||||||
"e.g. my-room": "например my-room",
|
"e.g. my-room": "например my-room",
|
||||||
"Close dialog": "Затвори прозореца",
|
"Close dialog": "Затвори прозореца",
|
||||||
"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 /> в момента не е достъпен.",
|
|
||||||
"You should:": "Ще е добре да:",
|
|
||||||
"check your browser plugins for anything that might block the identity server (such as Privacy Badger)": "проверите браузър добавките за всичко, което може да блокира връзката със сървъра за самоличност (например Privacy Badger)",
|
|
||||||
"contact the administrators of identity server <idserver />": "се свържете с администратора на сървъра за самоличност <idserver />",
|
|
||||||
"wait and try again later": "изчакате и опитате пак",
|
|
||||||
"Your email address hasn't been verified yet": "Имейл адресът ви все още не е потвърден",
|
|
||||||
"Click the link in the email you received to verify and then click continue again.": "Кликнете на връзката получена по имейл за да потвърдите, а след това натиснете продължи отново.",
|
|
||||||
"Room %(name)s": "Стая %(name)s",
|
|
||||||
"Failed to deactivate user": "Неуспешно деактивиране на потребител",
|
"Failed to deactivate user": "Неуспешно деактивиране на потребител",
|
||||||
"This client does not support end-to-end encryption.": "Този клиент не поддържа шифроване от край до край.",
|
"This client does not support end-to-end encryption.": "Този клиент не поддържа шифроване от край до край.",
|
||||||
"Messages in this room are not end-to-end encrypted.": "Съобщенията в тази стая не са шифровани от край до край.",
|
"Messages in this room are not end-to-end encrypted.": "Съобщенията в тази стая не са шифровани от край до край.",
|
||||||
|
@ -484,12 +341,7 @@
|
||||||
"%(name)s cancelled": "%(name)s отказа",
|
"%(name)s cancelled": "%(name)s отказа",
|
||||||
"%(name)s wants to verify": "%(name)s иска да извърши потвърждение",
|
"%(name)s wants to verify": "%(name)s иска да извърши потвърждение",
|
||||||
"You sent a verification request": "Изпратихте заявка за потвърждение",
|
"You sent a verification request": "Изпратихте заявка за потвърждение",
|
||||||
"Manage integrations": "Управление на интеграциите",
|
|
||||||
"None": "Няма нищо",
|
|
||||||
"Unencrypted": "Нешифровано",
|
"Unencrypted": "Нешифровано",
|
||||||
"Close preview": "Затвори прегледа",
|
|
||||||
"<userName/> wants to chat": "<userName/> иска да чати",
|
|
||||||
"Start chatting": "Започни чат",
|
|
||||||
"Failed to connect to integration manager": "Неуспешна връзка с мениджъра на интеграции",
|
"Failed to connect to integration manager": "Неуспешна връзка с мениджъра на интеграции",
|
||||||
"Hide verified sessions": "Скрий потвърдените сесии",
|
"Hide verified sessions": "Скрий потвърдените сесии",
|
||||||
"%(count)s verified sessions": {
|
"%(count)s verified sessions": {
|
||||||
|
@ -508,7 +360,6 @@
|
||||||
"You'll upgrade this room from <oldVersion /> to <newVersion />.": "Ще обновите стаята от <oldVersion /> до <newVersion />.",
|
"You'll upgrade this room from <oldVersion /> to <newVersion />.": "Ще обновите стаята от <oldVersion /> до <newVersion />.",
|
||||||
"Country Dropdown": "Падащо меню за избор на държава",
|
"Country Dropdown": "Падащо меню за избор на държава",
|
||||||
"Verification Request": "Заявка за потвърждение",
|
"Verification Request": "Заявка за потвърждение",
|
||||||
"Unable to set up secret storage": "Неуспешна настройка на секретно складиране",
|
|
||||||
"Recent Conversations": "Скорошни разговори",
|
"Recent Conversations": "Скорошни разговори",
|
||||||
"Show more": "Покажи повече",
|
"Show more": "Покажи повече",
|
||||||
"Direct Messages": "Директни съобщения",
|
"Direct Messages": "Директни съобщения",
|
||||||
|
@ -520,8 +371,6 @@
|
||||||
"Lock": "Заключи",
|
"Lock": "Заключи",
|
||||||
"This backup is trusted because it has been restored on this session": "Това резервно копие е доверено, защото е било възстановено в текущата сесия",
|
"This backup is trusted because it has been restored on this session": "Това резервно копие е доверено, защото е било възстановено в текущата сесия",
|
||||||
"To report a Matrix-related security issue, please read the Matrix.org <a>Security Disclosure Policy</a>.": "За да съобщените за проблем със сигурността свързан с Matrix, прочетете <a>Политиката за споделяне на проблеми със сигурността</a> на Matrix.org.",
|
"To report a Matrix-related security issue, please read the Matrix.org <a>Security Disclosure Policy</a>.": "За да съобщените за проблем със сигурността свързан с Matrix, прочетете <a>Политиката за споделяне на проблеми със сигурността</a> на Matrix.org.",
|
||||||
"This room is bridging messages to the following platforms. <a>Learn more.</a>": "Тази стая препредава съобщения със следните платформи. <a>Научи повече.</a>",
|
|
||||||
"Bridges": "Мостове",
|
|
||||||
"This user has not verified all of their sessions.": "Този потребител не е верифицирал всичките си сесии.",
|
"This user has not verified all of their sessions.": "Този потребител не е верифицирал всичките си сесии.",
|
||||||
"You have not verified this user.": "Не сте верифицирали този потребител.",
|
"You have not verified this user.": "Не сте верифицирали този потребител.",
|
||||||
"You have verified this user. This user has verified all of their sessions.": "Верифицирали сте този потребител. Този потребител е верифицирал всичките си сесии.",
|
"You have verified this user. This user has verified all of their sessions.": "Верифицирали сте този потребител. Този потребител е верифицирал всичките си сесии.",
|
||||||
|
@ -531,7 +380,6 @@
|
||||||
"Encrypted by an unverified session": "Шифровано от неверифицирана сесия",
|
"Encrypted by an unverified session": "Шифровано от неверифицирана сесия",
|
||||||
"Encrypted by a deleted session": "Шифровано от изтрита сесия",
|
"Encrypted by a deleted session": "Шифровано от изтрита сесия",
|
||||||
"Scroll to most recent messages": "Отиди до най-скорошните съобщения",
|
"Scroll to most recent messages": "Отиди до най-скорошните съобщения",
|
||||||
"Reject & Ignore user": "Откажи и игнорирай потребителя",
|
|
||||||
"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 alternative addresses. It may not be allowed by the server or a temporary failure occurred.": "Възникна грешка при обновяване на алтернативните адреси на стаята. Или не е позволено от сървъра или се е случила временна грешка.",
|
||||||
"Local address": "Локален адрес",
|
"Local address": "Локален адрес",
|
||||||
"Published Addresses": "Публикувани адреси",
|
"Published Addresses": "Публикувани адреси",
|
||||||
|
@ -639,24 +487,11 @@
|
||||||
"Keys restored": "Ключовете бяха възстановени",
|
"Keys restored": "Ключовете бяха възстановени",
|
||||||
"Successfully restored %(sessionCount)s keys": "Успешно бяха възстановени %(sessionCount)s ключа",
|
"Successfully restored %(sessionCount)s keys": "Успешно бяха възстановени %(sessionCount)s ключа",
|
||||||
"Sign in with SSO": "Влезте със SSO",
|
"Sign in with SSO": "Влезте със SSO",
|
||||||
"Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages.": "Администраторът на сървъра е изключил шифроване от край-до-край по подразбиране за лични стаи и за директни съобщения.",
|
|
||||||
"Switch theme": "Смени темата",
|
"Switch theme": "Смени темата",
|
||||||
"Confirm encryption setup": "Потвърждение на настройки за шифроване",
|
"Confirm encryption setup": "Потвърждение на настройки за шифроване",
|
||||||
"Click the button below to confirm setting up encryption.": "Кликнете бутона по-долу за да потвърдите настройването на шифроване.",
|
"Click the button below to confirm setting up encryption.": "Кликнете бутона по-долу за да потвърдите настройването на шифроване.",
|
||||||
"Enter your account password to confirm the upgrade:": "Въведете паролата за профила си за да потвърдите обновлението:",
|
|
||||||
"Restore your key backup to upgrade your encryption": "Възстановете резервното копие на ключа за да обновите шифроването",
|
|
||||||
"You'll need to authenticate with the server to confirm the upgrade.": "Ще трябва да се автентикирате пред сървъра за да потвърдите обновяването.",
|
|
||||||
"Upgrade this session to allow it to verify other sessions, granting them access to encrypted messages and marking them as trusted for other users.": "Обновете тази сесия, за да може да потвърждава други сесии, давайки им достъп до шифрованите съобщения и маркирайки ги като доверени за другите потребители.",
|
|
||||||
"Use a different passphrase?": "Използвай друга парола?",
|
|
||||||
"Unable to query secret storage status": "Неуспешно допитване за състоянието на секретното складиране",
|
|
||||||
"Upgrade your encryption": "Обновете шифроването",
|
|
||||||
"Create key backup": "Създай резервно копие на ключовете",
|
|
||||||
"This session is encrypting history using the new recovery method.": "Тази сесия шифрова историята използвайки новия метод за възстановяване.",
|
|
||||||
"If you did this accidentally, you can setup Secure Messages on this session which will re-encrypt this session's message history with a new recovery method.": "Ако сте направили това без да искате, може да настройте защитени съобщения за тази сесия, което ще зашифрова наново историята на съобщенията използвайки новия метод за възстановяване.",
|
|
||||||
"No recently visited rooms": "Няма наскоро-посетени стаи",
|
|
||||||
"The authenticity of this encrypted message can't be guaranteed on this device.": "Автентичността на това шифровано съобщение не може да бъде гарантирана на това устройство.",
|
"The authenticity of this encrypted message can't be guaranteed on this device.": "Автентичността на това шифровано съобщение не може да бъде гарантирана на това устройство.",
|
||||||
"Message preview": "Преглед на съобщението",
|
"Message preview": "Преглед на съобщението",
|
||||||
"Room options": "Настройки на стаята",
|
|
||||||
"This room is public": "Тази стая е публична",
|
"This room is public": "Тази стая е публична",
|
||||||
"Unable to set up keys": "Неуспешна настройка на ключовете",
|
"Unable to set up keys": "Неуспешна настройка на ключовете",
|
||||||
"Use your Security Key to continue.": "Използвайте ключа си за сигурност за да продължите.",
|
"Use your Security Key to continue.": "Използвайте ключа си за сигурност за да продължите.",
|
||||||
|
@ -705,23 +540,7 @@
|
||||||
"You can only pin up to %(count)s widgets": {
|
"You can only pin up to %(count)s widgets": {
|
||||||
"other": "Може да закачите максимум %(count)s приспособления"
|
"other": "Може да закачите максимум %(count)s приспособления"
|
||||||
},
|
},
|
||||||
"Explore public rooms": "Прегледай публични стаи",
|
|
||||||
"Show Widgets": "Покажи приспособленията",
|
|
||||||
"Hide Widgets": "Скрий приспособленията",
|
|
||||||
"Backup version:": "Версия на резервното копие:",
|
"Backup version:": "Версия на резервното копие:",
|
||||||
"Save your Security Key": "Запази ключа за сигурност",
|
|
||||||
"Confirm Security Phrase": "Потвърди фразата за сигурност",
|
|
||||||
"Set a Security Phrase": "Настрой фраза за сигурност",
|
|
||||||
"You can also set up Secure Backup & manage your keys in Settings.": "Също така, може да конфигурирате защитено резервно копиране и да управлявате ключовете си от Настройки.",
|
|
||||||
"If you cancel now, you may lose encrypted messages & data if you lose access to your logins.": "Ако се откажете сега, може да загубите достъп до шифрованите съобщения и данни, в случай че загубите достъп до тази сесия.",
|
|
||||||
"Use a secret phrase only you know, and optionally save a Security Key to use for backup.": "Използвайте секретна фраза, която знаете само вие. При необходимост запазете и ключа за сигурност за резервното копие.",
|
|
||||||
"Enter a Security Phrase": "Въведете фраза за сигурност",
|
|
||||||
"Generate a Security Key": "Генерирай ключ за сигурност",
|
|
||||||
"Safeguard against losing access to encrypted messages & data by backing up encryption keys on your server.": "Предпазете се от загуба на достъп до шифрованите съобщения и данни като направите резервно копие на ключовете за шифроване върху сървъра.",
|
|
||||||
"This session has detected that your Security Phrase and key for Secure Messages have been removed.": "Тази сесия откри, че вашата фраза за сигурност и ключ за защитени съобщения бяха премахнати.",
|
|
||||||
"A new Security Phrase and key for Secure Messages have been detected.": "Новa фраза за сигурност и ключ за защитени съобщения бяха открити.",
|
|
||||||
"Great! This Security Phrase looks strong enough.": "Чудесно! Тази фраза за сигурност изглежда достатъчно силна.",
|
|
||||||
"Confirm your Security Phrase": "Потвърдете вашата фраза за сигурност",
|
|
||||||
"Anguilla": "Ангила",
|
"Anguilla": "Ангила",
|
||||||
"British Indian Ocean Territory": "Британска територия в Индийския океан",
|
"British Indian Ocean Territory": "Британска територия в Индийския океан",
|
||||||
"Pitcairn Islands": "острови Питкерн",
|
"Pitcairn Islands": "острови Питкерн",
|
||||||
|
@ -971,18 +790,10 @@
|
||||||
"Afghanistan": "Афганистан",
|
"Afghanistan": "Афганистан",
|
||||||
"United States": "Съединените щати",
|
"United States": "Съединените щати",
|
||||||
"United Kingdom": "Обединеното кралство",
|
"United Kingdom": "Обединеното кралство",
|
||||||
"Add existing room": "Добави съществуваща стая",
|
|
||||||
"Leave space": "Напусни пространство",
|
"Leave space": "Напусни пространство",
|
||||||
"Create a space": "Създаване на пространство",
|
"Create a space": "Създаване на пространство",
|
||||||
"unknown person": "",
|
"unknown person": "",
|
||||||
"Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "Вашият %(brand)s не позволява да използвате мениджъра на интеграции за да направите това. Свържете се с администратор.",
|
"Your %(brand)s doesn't allow you to use an integration manager to do this. Please contact an admin.": "Вашият %(brand)s не позволява да използвате мениджъра на интеграции за да направите това. Свържете се с администратор.",
|
||||||
"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": "Адресът на сървъра за самоличност трябва да бъде HTTPS",
|
|
||||||
"common": {
|
"common": {
|
||||||
"about": "Относно",
|
"about": "Относно",
|
||||||
"analytics": "Статистика",
|
"analytics": "Статистика",
|
||||||
|
@ -1054,7 +865,13 @@
|
||||||
"general": "Общи",
|
"general": "Общи",
|
||||||
"profile": "Профил",
|
"profile": "Профил",
|
||||||
"display_name": "Име",
|
"display_name": "Име",
|
||||||
"user_avatar": "Профилна снимка"
|
"user_avatar": "Профилна снимка",
|
||||||
|
"authentication": "Автентикация",
|
||||||
|
"rooms": "Стаи",
|
||||||
|
"low_priority": "Нисък приоритет",
|
||||||
|
"historical": "Архив",
|
||||||
|
"go_to_settings": "Отиди в Настройки",
|
||||||
|
"setup_secure_messages": "Настрой Защитени Съобщения"
|
||||||
},
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"continue": "Продължи",
|
"continue": "Продължи",
|
||||||
|
@ -1136,7 +953,11 @@
|
||||||
"unban": "Отблокирай",
|
"unban": "Отблокирай",
|
||||||
"click_to_copy": "Натиснете за копиране",
|
"click_to_copy": "Натиснете за копиране",
|
||||||
"hide_advanced": "Скрий разширени настройки",
|
"hide_advanced": "Скрий разширени настройки",
|
||||||
"show_advanced": "Покажи разширени настройки"
|
"show_advanced": "Покажи разширени настройки",
|
||||||
|
"unignore": "Премахни игнорирането",
|
||||||
|
"explore_rooms": "Открий стаи",
|
||||||
|
"add_existing_room": "Добави съществуваща стая",
|
||||||
|
"explore_public_rooms": "Прегледай публични стаи"
|
||||||
},
|
},
|
||||||
"a11y": {
|
"a11y": {
|
||||||
"user_menu": "Потребителско меню",
|
"user_menu": "Потребителско меню",
|
||||||
|
@ -1149,7 +970,8 @@
|
||||||
"one": "1 непрочетено съобщение."
|
"one": "1 непрочетено съобщение."
|
||||||
},
|
},
|
||||||
"unread_messages": "Непрочетени съобщения.",
|
"unread_messages": "Непрочетени съобщения.",
|
||||||
"jump_first_invite": "Отиди до първата покана."
|
"jump_first_invite": "Отиди до първата покана.",
|
||||||
|
"room_name": "Стая %(name)s"
|
||||||
},
|
},
|
||||||
"labs": {
|
"labs": {
|
||||||
"pinning": "Функция за закачане на съобщения",
|
"pinning": "Функция за закачане на съобщения",
|
||||||
|
@ -1223,7 +1045,13 @@
|
||||||
"room_a11y": "Подсказка за стаи",
|
"room_a11y": "Подсказка за стаи",
|
||||||
"user_description": "Потребители",
|
"user_description": "Потребители",
|
||||||
"user_a11y": "Подсказка за потребители"
|
"user_a11y": "Подсказка за потребители"
|
||||||
}
|
},
|
||||||
|
"room_upgraded_link": "Разговора продължава тук.",
|
||||||
|
"room_upgraded_notice": "Тази стая е била заменена и вече не е активна.",
|
||||||
|
"no_perms_notice": "Нямате разрешение да публикувате в тази стая",
|
||||||
|
"poll_button_no_perms_title": "Необходимо е разрешение",
|
||||||
|
"format_italics": "Наклонено",
|
||||||
|
"replying_title": "Отговаря"
|
||||||
},
|
},
|
||||||
"Code": "Код",
|
"Code": "Код",
|
||||||
"power_level": {
|
"power_level": {
|
||||||
|
@ -1335,7 +1163,14 @@
|
||||||
"inline_url_previews_room_account": "Включване на URL прегледи за тази стая (засяга само Вас)",
|
"inline_url_previews_room_account": "Включване на URL прегледи за тази стая (засяга само Вас)",
|
||||||
"inline_url_previews_room": "Включване по подразбиране на URL прегледи за участници в тази стая",
|
"inline_url_previews_room": "Включване по подразбиране на URL прегледи за участници в тази стая",
|
||||||
"voip": {
|
"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": {
|
"security": {
|
||||||
"message_search_disable_warning": "Ако е изключено, съобщения от шифровани стаи няма да се показват в резултатите от търсения.",
|
"message_search_disable_warning": "Ако е изключено, съобщения от шифровани стаи няма да се показват в резултатите от търсения.",
|
||||||
|
@ -1397,7 +1232,10 @@
|
||||||
"key_backup_connect": "Свържи тази сесия с резервно копие на ключовете",
|
"key_backup_connect": "Свържи тази сесия с резервно копие на ключовете",
|
||||||
"key_backup_complete": "Всички ключове са в резервното копие",
|
"key_backup_complete": "Всички ключове са в резервното копие",
|
||||||
"key_backup_algorithm": "Алгоритъм:",
|
"key_backup_algorithm": "Алгоритъм:",
|
||||||
"key_backup_inactive_warning": "На ключовете ви <b>не се прави резервно копие от тази сесия</b>."
|
"key_backup_inactive_warning": "На ключовете ви <b>не се прави резервно копие от тази сесия</b>.",
|
||||||
|
"key_backup_active_version_none": "Няма нищо",
|
||||||
|
"ignore_users_section": "Игнорирани потребители",
|
||||||
|
"e2ee_default_disabled_warning": "Администраторът на сървъра е изключил шифроване от край-до-край по подразбиране за лични стаи и за директни съобщения."
|
||||||
},
|
},
|
||||||
"preferences": {
|
"preferences": {
|
||||||
"room_list_heading": "Списък със стаи",
|
"room_list_heading": "Списък със стаи",
|
||||||
|
@ -1426,7 +1264,81 @@
|
||||||
"add_msisdn_dialog_title": "Добави телефонен номер",
|
"add_msisdn_dialog_title": "Добави телефонен номер",
|
||||||
"name_placeholder": "Няма име",
|
"name_placeholder": "Няма име",
|
||||||
"error_saving_profile_title": "Неуспешно запазване на профила ви",
|
"error_saving_profile_title": "Неуспешно запазване на профила ви",
|
||||||
"error_saving_profile": "Операцията не можа да бъде завършена"
|
"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": "Въведете кода за потвърждение получен в SMS.",
|
||||||
|
"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": "Беше изпратено SMS съобщение към +%(msisdn)s. Въведете съдържащият се код за потвърждение.",
|
||||||
|
"msisdn_label": "Телефонен номер"
|
||||||
|
},
|
||||||
|
"key_backup": {
|
||||||
|
"backup_in_progress": "Прави се резервно копие на ключовете Ви (първото копие може да отнеме няколко минути).",
|
||||||
|
"backup_success": "Успешно!",
|
||||||
|
"create_title": "Създай резервно копие на ключовете",
|
||||||
|
"cannot_create_backup": "Неуспешно създаване на резервно копие на ключа",
|
||||||
|
"setup_secure_backup": {
|
||||||
|
"generate_security_key_title": "Генерирай ключ за сигурност",
|
||||||
|
"enter_phrase_title": "Въведете фраза за сигурност",
|
||||||
|
"description": "Предпазете се от загуба на достъп до шифрованите съобщения и данни като направите резервно копие на ключовете за шифроване върху сървъра.",
|
||||||
|
"requires_password_confirmation": "Въведете паролата за профила си за да потвърдите обновлението:",
|
||||||
|
"requires_key_restore": "Възстановете резервното копие на ключа за да обновите шифроването",
|
||||||
|
"requires_server_authentication": "Ще трябва да се автентикирате пред сървъра за да потвърдите обновяването.",
|
||||||
|
"session_upgrade_description": "Обновете тази сесия, за да може да потвърждава други сесии, давайки им достъп до шифрованите съобщения и маркирайки ги като доверени за другите потребители.",
|
||||||
|
"phrase_strong_enough": "Чудесно! Тази фраза за сигурност изглежда достатъчно силна.",
|
||||||
|
"pass_phrase_match_success": "Това съвпада!",
|
||||||
|
"use_different_passphrase": "Използвай друга парола?",
|
||||||
|
"pass_phrase_match_failed": "Това не съвпада.",
|
||||||
|
"set_phrase_again": "Върнете се за да настройте нова.",
|
||||||
|
"confirm_security_phrase": "Потвърдете вашата фраза за сигурност",
|
||||||
|
"secret_storage_query_failure": "Неуспешно допитване за състоянието на секретното складиране",
|
||||||
|
"cancel_warning": "Ако се откажете сега, може да загубите достъп до шифрованите съобщения и данни, в случай че загубите достъп до тази сесия.",
|
||||||
|
"settings_reminder": "Също така, може да конфигурирате защитено резервно копиране и да управлявате ключовете си от Настройки.",
|
||||||
|
"title_upgrade_encryption": "Обновете шифроването",
|
||||||
|
"title_set_phrase": "Настрой фраза за сигурност",
|
||||||
|
"title_confirm_phrase": "Потвърди фразата за сигурност",
|
||||||
|
"title_save_key": "Запази ключа за сигурност",
|
||||||
|
"unable_to_setup": "Неуспешна настройка на секретно складиране",
|
||||||
|
"use_phrase_only_you_know": "Използвайте секретна фраза, която знаете само вие. При необходимост запазете и ключа за сигурност за резервното копие."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"key_export_import": {
|
||||||
|
"export_title": "Експортиране на ключове за стаята",
|
||||||
|
"export_description_1": "Този процес Ви позволява да експортирате във файл ключовете за съобщения в шифровани стаи. Така ще можете да импортирате файла в друг Matrix клиент, така че той също да може да разшифрова такива съобщения.",
|
||||||
|
"enter_passphrase": "Въведи парола",
|
||||||
|
"confirm_passphrase": "Потвърди парола",
|
||||||
|
"phrase_cannot_be_empty": "Паролата не трябва да е празна",
|
||||||
|
"phrase_must_match": "Паролите трябва да съвпадат",
|
||||||
|
"import_title": "Импортиране на ключове за стая",
|
||||||
|
"import_description_1": "Този процес позволява да импортирате ключове за шифроване, които преди сте експортирали от друг Matrix клиент. Тогава ще можете да разшифровате всяко съобщение, което другият клиент може да разшифрова.",
|
||||||
|
"import_description_2": "Експортираният файл може да бъде предпазен с парола. Трябва да въведете парола тук, за да разшифровате файла.",
|
||||||
|
"file_to_import": "Файл за импортиране"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devtools": {
|
"devtools": {
|
||||||
|
@ -1665,6 +1577,9 @@
|
||||||
"creation_summary_room": "%(creator)s създаде и настрой стаята.",
|
"creation_summary_room": "%(creator)s създаде и настрой стаята.",
|
||||||
"context_menu": {
|
"context_menu": {
|
||||||
"external_url": "URL на източника"
|
"external_url": "URL на източника"
|
||||||
|
},
|
||||||
|
"url_preview": {
|
||||||
|
"close": "Затвори прегледа"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"slash_command": {
|
"slash_command": {
|
||||||
|
@ -1836,7 +1751,14 @@
|
||||||
"send_event_type": "Изпрати %(eventType)s събития",
|
"send_event_type": "Изпрати %(eventType)s събития",
|
||||||
"title": "Роли и привилегии",
|
"title": "Роли и привилегии",
|
||||||
"permissions_section": "Разрешения",
|
"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": {
|
"security": {
|
||||||
"strict_encryption": "Никога не изпращай шифровани съобщения към непотвърдени сесии в тази стая от тази сесия",
|
"strict_encryption": "Никога не изпращай шифровани съобщения към непотвърдени сесии в тази стая от тази сесия",
|
||||||
|
@ -1861,16 +1783,30 @@
|
||||||
"default_url_previews_off": "URL прегледи са изключени по подразбиране за участниците в тази стая.",
|
"default_url_previews_off": "URL прегледи са изключени по подразбиране за участниците в тази стая.",
|
||||||
"url_preview_encryption_warning": "В шифровани стаи като тази, по подразбиране URL прегледите са изключени, за да се подсигури че сървърът (където става генерирането на прегледите) не може да събира информация за връзките споделени в стаята.",
|
"url_preview_encryption_warning": "В шифровани стаи като тази, по подразбиране URL прегледите са изключени, за да се подсигури че сървърът (където става генерирането на прегледите) не може да събира информация за връзките споделени в стаята.",
|
||||||
"url_preview_explainer": "Когато се сподели URL връзка в съобщение, може да бъде показан URL преглед даващ повече информация за връзката (заглавие, описание и картинка от уебсайта).",
|
"url_preview_explainer": "Когато се сподели URL връзка в съобщение, може да бъде показан URL преглед даващ повече информация за връзката (заглавие, описание и картинка от уебсайта).",
|
||||||
"url_previews_section": "URL прегледи"
|
"url_previews_section": "URL прегледи",
|
||||||
|
"aliases_section": "Адреси на стаята",
|
||||||
|
"other_section": "Други"
|
||||||
},
|
},
|
||||||
"advanced": {
|
"advanced": {
|
||||||
"unfederated": "Тази стая не е достъпна за отдалечени Matrix сървъри",
|
"unfederated": "Тази стая не е достъпна за отдалечени Matrix сървъри",
|
||||||
"room_upgrade_button": "Обнови тази стая до препоръчаната версия на стаята",
|
"room_upgrade_button": "Обнови тази стая до препоръчаната версия на стаята",
|
||||||
"room_predecessor": "Виж по-стари съобщения в %(roomName)s.",
|
"room_predecessor": "Виж по-стари съобщения в %(roomName)s.",
|
||||||
"room_version_section": "Версия на стаята",
|
"room_version_section": "Версия на стаята",
|
||||||
"room_version": "Версия на стаята:"
|
"room_version": "Версия на стаята:",
|
||||||
|
"information_section_room": "Информация за стаята"
|
||||||
},
|
},
|
||||||
"upload_avatar_label": "Качи профилна снимка"
|
"upload_avatar_label": "Качи профилна снимка",
|
||||||
|
"bridges": {
|
||||||
|
"description": "Тази стая препредава съобщения със следните платформи. <a>Научи повече.</a>",
|
||||||
|
"title": "Мостове"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"uploaded_sound": "Качен звук",
|
||||||
|
"sounds_section": "Звуци",
|
||||||
|
"notification_sound": "Звук за уведомление",
|
||||||
|
"custom_sound_prompt": "Настрой нов собствен звук",
|
||||||
|
"browse_button": "Избор"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"encryption": {
|
"encryption": {
|
||||||
"verification": {
|
"verification": {
|
||||||
|
@ -1912,7 +1848,19 @@
|
||||||
"cross_signing_ready": "Кръстосаното-подписване е готово за използване.",
|
"cross_signing_ready": "Кръстосаното-подписване е готово за използване.",
|
||||||
"cross_signing_untrusted": "Профилът ви има самоличност за кръстосано подписване в секретно складиране, но все още не е доверено от тази сесия.",
|
"cross_signing_untrusted": "Профилът ви има самоличност за кръстосано подписване в секретно складиране, но все още не е доверено от тази сесия.",
|
||||||
"cross_signing_not_ready": "Кръстосаното-подписване не е настроено.",
|
"cross_signing_not_ready": "Кръстосаното-подписване не е настроено.",
|
||||||
"not_supported": "<не се поддържа>"
|
"not_supported": "<не се поддържа>",
|
||||||
|
"new_recovery_method_detected": {
|
||||||
|
"title": "Нов метод за възстановяване",
|
||||||
|
"description_1": "Новa фраза за сигурност и ключ за защитени съобщения бяха открити.",
|
||||||
|
"description_2": "Тази сесия шифрова историята използвайки новия метод за възстановяване.",
|
||||||
|
"warning": "Ако не сте настройвали новия метод за възстановяване, вероятно някой се опитва да проникне в акаунта Ви. Веднага променете паролата на акаунта си и настройте нов метод за възстановяване от Настройки."
|
||||||
|
},
|
||||||
|
"recovery_method_removed": {
|
||||||
|
"title": "Методът за възстановяване беше премахнат",
|
||||||
|
"description_1": "Тази сесия откри, че вашата фраза за сигурност и ключ за защитени съобщения бяха премахнати.",
|
||||||
|
"description_2": "Ако сте направили това без да искате, може да настройте защитени съобщения за тази сесия, което ще зашифрова наново историята на съобщенията използвайки новия метод за възстановяване.",
|
||||||
|
"warning": "Ако не сте премахнали метода за възстановяване, е възможно нападател да се опитва да получи достъп до акаунта Ви. Променете паролата на акаунта и настройте нов метод за възстановяване веднага от Настройки."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"emoji": {
|
"emoji": {
|
||||||
"category_frequently_used": "Често използвани",
|
"category_frequently_used": "Често използвани",
|
||||||
|
@ -2019,7 +1967,9 @@
|
||||||
"autodiscovery_unexpected_error_hs": "Неочаквана грешка в намирането на сървърната конфигурация",
|
"autodiscovery_unexpected_error_hs": "Неочаквана грешка в намирането на сървърната конфигурация",
|
||||||
"autodiscovery_unexpected_error_is": "Неочаквана грешка при откриване на конфигурацията на сървъра за самоличност",
|
"autodiscovery_unexpected_error_is": "Неочаквана грешка при откриване на конфигурацията на сървъра за самоличност",
|
||||||
"incorrect_credentials_detail": "Моля, обърнете внимание, че влизате в %(hs)s сървър, а не в matrix.org.",
|
"incorrect_credentials_detail": "Моля, обърнете внимание, че влизате в %(hs)s сървър, а не в matrix.org.",
|
||||||
"create_account_title": "Създай акаунт"
|
"create_account_title": "Създай акаунт",
|
||||||
|
"failed_soft_logout_homeserver": "Неуспешна повторна автентикация поради проблем със сървъра",
|
||||||
|
"soft_logout_subheading": "Изчисти личните данни"
|
||||||
},
|
},
|
||||||
"export_chat": {
|
"export_chat": {
|
||||||
"messages": "Съобщения"
|
"messages": "Съобщения"
|
||||||
|
@ -2038,7 +1988,9 @@
|
||||||
"show_less": "Покажи по-малко",
|
"show_less": "Покажи по-малко",
|
||||||
"notification_options": "Настройки за уведомление",
|
"notification_options": "Настройки за уведомление",
|
||||||
"failed_remove_tag": "Неуспешно премахване на %(tagName)s етикет от стаята",
|
"failed_remove_tag": "Неуспешно премахване на %(tagName)s етикет от стаята",
|
||||||
"failed_add_tag": "Неуспешно добавяне на %(tagName)s етикет в стаята"
|
"failed_add_tag": "Неуспешно добавяне на %(tagName)s етикет в стаята",
|
||||||
|
"breadcrumbs_empty": "Няма наскоро-посетени стаи",
|
||||||
|
"add_room_label": "Добави стая"
|
||||||
},
|
},
|
||||||
"report_content": {
|
"report_content": {
|
||||||
"missing_reason": "Въведете защо докладвате.",
|
"missing_reason": "Въведете защо докладвате.",
|
||||||
|
@ -2146,7 +2098,8 @@
|
||||||
"lists_heading": "Абонирани списъци",
|
"lists_heading": "Абонирани списъци",
|
||||||
"lists_description_1": "Абонирането към списък ще направи така, че да се присъедините към него!",
|
"lists_description_1": "Абонирането към списък ще направи така, че да се присъедините към него!",
|
||||||
"lists_description_2": "Ако това не е каквото искате, използвайте друг инструмент за игнориране на потребители.",
|
"lists_description_2": "Ако това не е каквото искате, използвайте друг инструмент за игнориране на потребители.",
|
||||||
"lists_new_label": "Идентификатор или адрес на стая списък за блокиране"
|
"lists_new_label": "Идентификатор или адрес на стая списък за блокиране",
|
||||||
|
"rules_empty": "Няма нищо"
|
||||||
},
|
},
|
||||||
"create_space": {
|
"create_space": {
|
||||||
"name_required": "Моля, въведете име на пространството",
|
"name_required": "Моля, въведете име на пространството",
|
||||||
|
@ -2183,8 +2136,40 @@
|
||||||
"unfavourite": "В любими",
|
"unfavourite": "В любими",
|
||||||
"favourite": "Любим",
|
"favourite": "Любим",
|
||||||
"low_priority": "Нисък приоритет",
|
"low_priority": "Нисък приоритет",
|
||||||
"forget": "Забрави стаята"
|
"forget": "Забрави стаята",
|
||||||
}
|
"title": "Настройки на стаята"
|
||||||
|
},
|
||||||
|
"invite_this_room": "Покани в тази стая",
|
||||||
|
"header": {
|
||||||
|
"forget_room_button": "Забрави стаята",
|
||||||
|
"hide_widgets_button": "Скрий приспособленията",
|
||||||
|
"show_widgets_button": "Покажи приспособленията"
|
||||||
|
},
|
||||||
|
"join_title_account": "Присъедини се към разговор с акаунт",
|
||||||
|
"join_button_account": "Регистриране",
|
||||||
|
"kick_reason": "Причина: %(reason)s",
|
||||||
|
"forget_room": "Пропусни тази стая",
|
||||||
|
"rejoin_button": "Връщане",
|
||||||
|
"banned_from_room_by": "Получихте забрана за %(roomName)s от %(memberName)s",
|
||||||
|
"3pid_invite_error_title_room": "Нещо нежелано се случи с вашата покана към %(roomName)s",
|
||||||
|
"3pid_invite_error_invite_subtitle": "Да се присъедините можете само с активна покана.",
|
||||||
|
"3pid_invite_error_invite_action": "Опитай да се присъединиш все пак",
|
||||||
|
"join_the_discussion": "Присъединете се към разговора",
|
||||||
|
"3pid_invite_email_not_found_account_room": "Тази покана за %(roomName)s е била изпратена към адрес %(email)s, който не е асоцииран с профила ви",
|
||||||
|
"link_email_to_receive_3pid_invite": "Свържете този имейл адрес с профила си от Настройки за да получавате покани директно в %(brand)s.",
|
||||||
|
"invite_sent_to_email_room": "Тази покана за %(roomName)s беше изпратена към адрес %(email)s",
|
||||||
|
"3pid_invite_no_is_subtitle": "Използвайте сървър за самоличност от Настройки за да получавате покани директно в %(brand)s.",
|
||||||
|
"invite_email_mismatch_suggestion": "Споделете този имейл в Настройки за да получавате покани директно в %(brand)s.",
|
||||||
|
"dm_invite_title": "Желаете ли да си поговорите с %(user)s?",
|
||||||
|
"dm_invite_subtitle": "<userName/> иска да чати",
|
||||||
|
"dm_invite_action": "Започни чат",
|
||||||
|
"invite_title": "Желаете ли да се присъедините към %(roomName)s?",
|
||||||
|
"invite_subtitle": "<userName/> ви покани",
|
||||||
|
"invite_reject_ignore": "Откажи и игнорирай потребителя",
|
||||||
|
"peek_join_prompt": "Предварителен преглед на %(roomName)s. Желаете ли да се влезете?",
|
||||||
|
"no_peek_join_prompt": "%(roomName)s не може да бъде прегледана предварително. Желаете ли да се влезете?",
|
||||||
|
"not_found_title_name": "%(roomName)s не съществува.",
|
||||||
|
"inaccessible_name": "%(roomName)s не е достъпна към този момент."
|
||||||
},
|
},
|
||||||
"file_panel": {
|
"file_panel": {
|
||||||
"guest_note": "Трябва да се <a>регистрирате</a>, за да използвате тази функционалност",
|
"guest_note": "Трябва да се <a>регистрирате</a>, за да използвате тази функционалност",
|
||||||
|
@ -2317,7 +2302,9 @@
|
||||||
"colour_bold": "Удебелено",
|
"colour_bold": "Удебелено",
|
||||||
"error_change_title": "Промяна на настройките за уведомление",
|
"error_change_title": "Промяна на настройките за уведомление",
|
||||||
"mark_all_read": "Маркирай всичко като прочетено",
|
"mark_all_read": "Маркирай всичко като прочетено",
|
||||||
"class_other": "Други"
|
"class_other": "Други",
|
||||||
|
"default": "По подразбиране",
|
||||||
|
"all_messages": "Всички съобщения"
|
||||||
},
|
},
|
||||||
"mobile_guide": {
|
"mobile_guide": {
|
||||||
"toast_title": "Използвайте приложението за по-добра работа",
|
"toast_title": "Използвайте приложението за по-добра работа",
|
||||||
|
@ -2338,6 +2325,43 @@
|
||||||
"a11y_jump_first_unread_room": "Отиди до първата непрочетена стая.",
|
"a11y_jump_first_unread_room": "Отиди до първата непрочетена стая.",
|
||||||
"integration_manager": {
|
"integration_manager": {
|
||||||
"error_connecting_heading": "Неуспешна връзка с мениджъра на интеграции",
|
"error_connecting_heading": "Неуспешна връзка с мениджъра на интеграции",
|
||||||
"error_connecting": "Мениджъра на интеграции е офлайн или не може да се свърже със сървъра ви."
|
"error_connecting": "Мениджъра на интеграции е офлайн или не може да се свърже със сървъра ви.",
|
||||||
|
"use_im_default": "Използвай мениджър на интеграции <b>%(serverName)s</b> за управление на ботове, приспособления и стикери.",
|
||||||
|
"use_im": "Използвай мениджър на интеграции за управление на ботове, приспособления и стикери.",
|
||||||
|
"manage_title": "Управление на интеграциите",
|
||||||
|
"explainer": "Мениджърът на интеграции получава конфигурационни данни, може да модифицира приспособления, да изпраща покани за стаи и да настройва нива на достъп от ваше име."
|
||||||
|
},
|
||||||
|
"identity_server": {
|
||||||
|
"url_not_https": "Адресът на сървъра за самоличност трябва да бъде 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": "Въведете нов сървър за самоличност"
|
||||||
|
},
|
||||||
|
"member_list": {
|
||||||
|
"invited_list_heading": "Поканен",
|
||||||
|
"filter_placeholder": "Филтриране на членовете",
|
||||||
|
"power_label": "%(userName)s (ниво на достъп %(powerLevelNumber)s)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue