diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index b782266288f..89ab7724d56 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -142,7 +142,7 @@ jobs: run: | echo "sha=$(cat webapp/sha)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: # XXX: We're checking out untrusted code in a secure context # We need to be careful to not trust anything this code outputs/may do @@ -163,7 +163,7 @@ jobs: echo "CYPRESS_RUST_CRYPTO=1" >> "$GITHUB_ENV" - name: Run Cypress tests - uses: cypress-io/github-action@fa88e4afe551e64c8827a4b9e379afc63d8f691a + uses: cypress-io/github-action@2558ee6af05072a19de2ce92cb68b38616132726 with: working-directory: matrix-react-sdk # The built-in Electron runner seems to grind to a halt trying to run the tests, so use chrome. diff --git a/.github/workflows/element-web.yaml b/.github/workflows/element-web.yaml index d369641f171..9f8b098147a 100644 --- a/.github/workflows/element-web.yaml +++ b/.github/workflows/element-web.yaml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ${{ inputs.react-sdk-repository || github.repository }} diff --git a/.github/workflows/i18n_check.yml b/.github/workflows/i18n_check.yml index e72f8ca7b62..39b57028f82 100644 --- a/.github/workflows/i18n_check.yml +++ b/.github/workflows/i18n_check.yml @@ -7,12 +7,12 @@ jobs: permissions: pull-requests: read steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Get modified files" id: changed_files if: github.event_name == 'pull_request' && github.event.pull_request.user.login != 'RiotTranslateBot' && github.event.pull_request.user.login != 't3chguy' - uses: tj-actions/changed-files@1c26215f3fbd51eba03bc199e5cbabdfc3584ce3 # v38 + uses: tj-actions/changed-files@48566bbcc22ceb7c5809ebdd27377309f2c3de8c # v39 with: files: | src/i18n/strings/* diff --git a/.github/workflows/netlify.yaml b/.github/workflows/netlify.yaml index 248fb50c9ef..aa243d29628 100644 --- a/.github/workflows/netlify.yaml +++ b/.github/workflows/netlify.yaml @@ -41,7 +41,7 @@ jobs: - name: ☁️ Deploy to Netlify id: netlify - uses: nwtgck/actions-netlify@5da65c9f74c7961c5501a3ba329b8d0912f39c03 # v2.0 + uses: nwtgck/actions-netlify@7a92f00dde8c92a5a9e8385ec2919775f7647352 # v2.1 with: publish-dir: webapp deploy-message: "Deploy from GitHub Actions" diff --git a/.github/workflows/notify-element-web.yml b/.github/workflows/notify-element-web.yml index 39a252034c3..9f88c61e8a1 100644 --- a/.github/workflows/notify-element-web.yml +++ b/.github/workflows/notify-element-web.yml @@ -12,7 +12,7 @@ jobs: if: github.repository == 'matrix-org/matrix-react-sdk' steps: - name: Notify element-web repo that a new SDK build is on develop - uses: peter-evans/repository-dispatch@26b39ed245ab8f31526069329e112ab2fb224588 # v2 + uses: peter-evans/repository-dispatch@bf47d102fdb849e755b0b0023ea3e81a44b6f570 # v2 with: token: ${{ secrets.ELEMENT_BOT_TOKEN }} repository: vector-im/element-web diff --git a/.github/workflows/static_analysis.yaml b/.github/workflows/static_analysis.yaml index 13893058ac5..32c69a8fed2 100644 --- a/.github/workflows/static_analysis.yaml +++ b/.github/workflows/static_analysis.yaml @@ -20,7 +20,7 @@ jobs: name: "Typescript Syntax Check" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: @@ -61,7 +61,7 @@ jobs: name: "Rethemendex Check" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - run: ./res/css/rethemendex.sh @@ -71,7 +71,7 @@ jobs: name: "ESLint" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: @@ -88,7 +88,7 @@ jobs: name: "Style Lint" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: @@ -105,7 +105,7 @@ jobs: name: "Analyse Dead Code" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ad8fdab8c7b..8dc9928e22b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,7 +32,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: ${{ inputs.matrix-js-sdk-sha && 'matrix-org/matrix-react-sdk' || github.repository }} @@ -93,7 +93,7 @@ jobs: name: Element Web Integration Tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: ${{ inputs.matrix-js-sdk-sha && 'matrix-org/matrix-react-sdk' || github.repository }} diff --git a/cypress.config.ts b/cypress.config.ts index bc247638527..c56f0e70971 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -15,9 +15,10 @@ limitations under the License. */ import { defineConfig } from "cypress"; +import * as fs from "node:fs"; export default defineConfig({ - videoUploadOnPasses: false, + video: true, projectId: "ppvnzg", experimentalInteractiveRunEvents: true, experimentalMemoryManagement: true, @@ -25,6 +26,18 @@ export default defineConfig({ chromeWebSecurity: false, e2e: { setupNodeEvents(on, config) { + // Delete videos of passing tests + on("after:spec", (spec, results) => { + if (results && results.video) { + const failures = results.tests.some((test) => + test.attempts.some((attempt) => attempt.state === "failed"), + ); + if (!failures) { + fs.unlinkSync(results.video); + } + } + }); + return require("./cypress/plugins/index.ts").default(on, config); }, baseUrl: "http://localhost:8080", diff --git a/cypress/e2e/spotlight/spotlight.spec.ts b/cypress/e2e/spotlight/spotlight.spec.ts index d7ce14eff9e..4ce3adc372b 100644 --- a/cypress/e2e/spotlight/spotlight.spec.ts +++ b/cypress/e2e/spotlight/spotlight.spec.ts @@ -227,7 +227,6 @@ describe("Spotlight", () => { cy.spotlightSearch().type("{backspace}"); cy.get(".mx_SpotlightDialog_filter").should("not.exist"); - cy.spotlightSearch().type("{downArrow}"); cy.spotlightSearch().type("{downArrow}"); cy.get("#mx_SpotlightDialog_button_explorePublicRooms").should("have.attr", "aria-selected", "true"); cy.spotlightSearch().type("{enter}"); diff --git a/package.json b/package.json index 99bc0c0db91..68969bb87eb 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "emojibase-regex": "15.0.0", "escape-html": "^1.0.3", "file-saver": "^2.0.5", - "filesize": "10.0.7", + "filesize": "10.0.12", "focus-visible": "^5.2.0", "gfm.css": "^1.1.2", "glob-to-regexp": "^0.4.1", @@ -106,7 +106,7 @@ "opus-recorder": "^8.0.3", "pako": "^2.0.3", "png-chunks-extract": "^1.0.0", - "posthog-js": "1.73.1", + "posthog-js": "1.77.2", "proposal-temporal": "^0.9.0", "qrcode": "1.5.3", "re-resizable": "^6.9.0", @@ -159,7 +159,7 @@ "@types/fs-extra": "^11.0.0", "@types/geojson": "^7946.0.8", "@types/glob-to-regexp": "^0.4.1", - "@types/jest": "29.5.3", + "@types/jest": "29.5.4", "@types/katex": "^0.16.0", "@types/lodash": "^4.14.168", "@types/modernizr": "^3.5.3", @@ -185,19 +185,19 @@ "babel-jest": "^29.0.0", "blob-polyfill": "^7.0.0", "chokidar": "^3.5.1", - "cypress": "^12.0.0", + "cypress": "^13.0.0", "cypress-axe": "^1.0.0", "cypress-multi-reporters": "^1.6.1", "cypress-real-events": "^1.7.1", "cypress-terminal-report": "^5.3.2", - "eslint": "8.45.0", + "eslint": "8.48.0", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^9.0.0", - "eslint-plugin-deprecate": "^0.7.0", + "eslint-plugin-deprecate": "0.7.0", "eslint-plugin-import": "^2.25.4", "eslint-plugin-jest": "^27.2.1", "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-matrix-org": "1.2.0", + "eslint-plugin-matrix-org": "1.2.1", "eslint-plugin-react": "^7.28.0", "eslint-plugin-react-hooks": "^4.3.0", "eslint-plugin-unicorn": "^48.0.0", diff --git a/res/css/compound/_SuccessDialog.pcss b/res/css/compound/_SuccessDialog.pcss index 61f98a97df7..9085cedc11b 100644 --- a/res/css/compound/_SuccessDialog.pcss +++ b/res/css/compound/_SuccessDialog.pcss @@ -18,7 +18,7 @@ limitations under the License. text-align: center; .mx_Icon { - mask-border: $spacing-16; + margin-bottom: $spacing-16; } .mx_Dialog_header { diff --git a/src/LegacyCallHandler.tsx b/src/LegacyCallHandler.tsx index 675756b462d..c6dee72e118 100644 --- a/src/LegacyCallHandler.tsx +++ b/src/LegacyCallHandler.tsx @@ -567,7 +567,7 @@ export default class LegacyCallHandler extends EventEmitter { } Modal.createDialog(ErrorDialog, { - title: _t("Call Failed"), + title: _t("voip|call_failed"), description: err.message, }); }); @@ -708,7 +708,7 @@ export default class LegacyCallHandler extends EventEmitter { title = _t("User Busy"); description = _t("The user you called is busy."); } else { - title = _t("Call Failed"); + title = _t("voip|call_failed"); description = _t("The call could not be established"); } @@ -856,23 +856,17 @@ export default class LegacyCallHandler extends EventEmitter { let description; if (call.type === CallType.Voice) { - title = _t("Unable to access microphone"); - description = ( -
- {_t( - "Call failed because microphone could not be accessed. Check that a microphone is plugged in and set up correctly.", - )} -
- ); + title = _t("voip|unable_to_access_microphone"); + description =
{_t("voip|call_failed_microphone")}
; } else if (call.type === CallType.Video) { - title = _t("Unable to access webcam / microphone"); + title = _t("voip|unable_to_access_media"); description = (
- {_t("Call failed because webcam or microphone could not be accessed. Check that:")} + {_t("voip|call_failed_media")}
); @@ -914,8 +908,8 @@ export default class LegacyCallHandler extends EventEmitter { this.addCallForRoom(roomId, call); } catch (e) { Modal.createDialog(ErrorDialog, { - title: _t("Already in call"), - description: _t("You're already in a call with this person."), + title: _t("voip|already_in_call"), + description: _t("voip|already_in_call_person"), }); return; } @@ -956,8 +950,8 @@ export default class LegacyCallHandler extends EventEmitter { // if the runtime env doesn't do VoIP, whine. if (!cli.supportsVoip()) { Modal.createDialog(ErrorDialog, { - title: _t("Calls are unsupported"), - description: _t("You cannot place calls in this browser."), + title: _t("voip|unsupported"), + description: _t("voip|unsupported_browser"), }); return; } diff --git a/src/accessibility/KeyboardShortcutUtils.ts b/src/accessibility/KeyboardShortcutUtils.ts index b2ecbc6ce2f..dc006dcd6a2 100644 --- a/src/accessibility/KeyboardShortcutUtils.ts +++ b/src/accessibility/KeyboardShortcutUtils.ts @@ -43,7 +43,7 @@ const getUIOnlyShortcuts = (): IKeyboardShortcuts => { key: Key.ENTER, ctrlOrCmdKey: ctrlEnterToSend, }, - displayName: _td("Send message"), + displayName: _td("composer|send_button_title"), }, [KeyBindingAction.NewLine]: { default: { diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index 59906c5a748..d1b569ccfa3 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -2205,6 +2205,7 @@ export class RoomView extends React.Component { knocked={myMembership === "knock" || this.state.knocked} onSubmitAskToJoin={this.onSubmitAskToJoin} onCancelAskToJoin={this.onCancelAskToJoin} + onForgetClick={this.onForgetClick} /> diff --git a/src/components/views/beacon/RoomCallBanner.tsx b/src/components/views/beacon/RoomCallBanner.tsx index 27df9e22742..f85666c652d 100644 --- a/src/components/views/beacon/RoomCallBanner.tsx +++ b/src/components/views/beacon/RoomCallBanner.tsx @@ -72,7 +72,7 @@ const RoomCallBannerInner: React.FC = ({ roomId, call }) => return (
- {_t("Video call")} + {_t("voip|video_call")}
diff --git a/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx b/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx index 9e2372545c6..3b09adbb526 100644 --- a/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx +++ b/src/components/views/dialogs/AnalyticsLearnMoreDialog.tsx @@ -51,7 +51,7 @@ export const AnalyticsLearnMoreDialog: React.FC = ({ const privacyPolicyLink = privacyPolicyUrl ? ( {_t( - "You can read all our terms here", + "analytics|privacy_policy", {}, { PrivacyPolicyUrl: (sub) => { @@ -71,33 +71,18 @@ export const AnalyticsLearnMoreDialog: React.FC = ({
- {_t( - "Help us identify issues and improve %(analyticsOwner)s by sharing anonymous usage data. To understand how people use multiple devices, we'll generate a random identifier, shared by your devices.", - { analyticsOwner }, - )} + {_t("analytics|pseudonymous_usage_data", { analyticsOwner })}
    -
  • - {_t( - "We don't record or profile any account data", - {}, - { Bold: (sub) => {sub} }, - )} -
  • -
  • - {_t( - "We don't share information with third parties", - {}, - { Bold: (sub) => {sub} }, - )} -
  • -
  • {_t("You can turn this off anytime in settings")}
  • +
  • {_t("analytics|bullet_1", {}, { Bold: (sub) => {sub} })}
  • +
  • {_t("analytics|bullet_2", {}, { Bold: (sub) => {sub} })}
  • +
  • {_t("analytics|disable_prompt")}
{privacyPolicyLink}
diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 60ad2b74f01..a2bcd0a8a2a 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -41,8 +41,8 @@ enum Category { } const categoryLabels: Record = { - [Category.Room]: _td("common|room"), - [Category.Other]: _td("Other"), + [Category.Room]: _td("devtools|category_room"), + [Category.Other]: _td("devtools|category_other"), }; export type Tool = React.FC | ((props: IDevtoolsProps) => JSX.Element); diff --git a/src/components/views/dialogs/SessionRestoreErrorDialog.tsx b/src/components/views/dialogs/SessionRestoreErrorDialog.tsx index 27e8965f6f6..7893e271dba 100644 --- a/src/components/views/dialogs/SessionRestoreErrorDialog.tsx +++ b/src/components/views/dialogs/SessionRestoreErrorDialog.tsx @@ -67,7 +67,7 @@ export default class SessionRestoreErrorDialog extends React.Component { if (SdkConfig.get().bug_report_endpoint_url) { dialogButtons = ( - {_t("Identity server")} + {_t("common|identity_server")}
({host})
); case SERVICE_TYPES.IM: return (
- {_t("Integration manager")} + {_t("common|integration_manager")}
({host})
); diff --git a/src/components/views/dialogs/UploadFailureDialog.tsx b/src/components/views/dialogs/UploadFailureDialog.tsx index 6d478605c74..7a677acb62e 100644 --- a/src/components/views/dialogs/UploadFailureDialog.tsx +++ b/src/components/views/dialogs/UploadFailureDialog.tsx @@ -51,7 +51,7 @@ export default class UploadFailureDialog extends React.Component { message = _t( "This file is too large to upload. The file size limit is %(limit)s but this file is %(sizeOfThisFile)s.", { - limit: fileSize(this.props.contentMessages.getUploadLimit()), + limit: fileSize(this.props.contentMessages.getUploadLimit()!), sizeOfThisFile: fileSize(this.props.badFiles[0].size), }, { @@ -70,7 +70,7 @@ export default class UploadFailureDialog extends React.Component { message = _t( "These files are too large to upload. The file size limit is %(limit)s.", { - limit: fileSize(this.props.contentMessages.getUploadLimit()), + limit: fileSize(this.props.contentMessages.getUploadLimit()!), }, { b: (sub) => {sub}, @@ -88,7 +88,7 @@ export default class UploadFailureDialog extends React.Component { message = _t( "Some files are too large to be uploaded. The file size limit is %(limit)s.", { - limit: fileSize(this.props.contentMessages.getUploadLimit()), + limit: fileSize(this.props.contentMessages.getUploadLimit()!), }, { b: (sub) => {sub}, diff --git a/src/components/views/elements/ErrorBoundary.tsx b/src/components/views/elements/ErrorBoundary.tsx index 4a24d22f98e..81584a51451 100644 --- a/src/components/views/elements/ErrorBoundary.tsx +++ b/src/components/views/elements/ErrorBoundary.tsx @@ -85,7 +85,7 @@ export default class ErrorBoundary extends React.PureComponent {

{_t( - "Please create a new issue on GitHub so that we can investigate this bug.", + "bug_reporting|create_new_issue", {}, { newIssueLink: (sub) => { diff --git a/src/components/views/elements/EventListSummary.tsx b/src/components/views/elements/EventListSummary.tsx index e0279a7c9c6..9ed1345a0b0 100644 --- a/src/components/views/elements/EventListSummary.tsx +++ b/src/components/views/elements/EventListSummary.tsx @@ -133,7 +133,7 @@ export default class EventListSummary extends React.Component< const desc = formatCommaSeparatedList(descs); - return _t("%(nameList)s %(transitionList)s", { nameList, transitionList: desc }); + return _t("timeline|summary|format", { nameList, transitionList: desc }); }); if (!summaries) { @@ -250,101 +250,101 @@ export default class EventListSummary extends React.Component< case TransitionType.Joined: res = userCount > 1 - ? _t("%(severalUsers)sjoined %(count)s times", { severalUsers: "", count }) - : _t("%(oneUser)sjoined %(count)s times", { oneUser: "", count }); + ? _t("timeline|summary|joined_multiple", { severalUsers: "", count }) + : _t("timeline|summary|joined", { oneUser: "", count }); break; case TransitionType.Left: res = userCount > 1 - ? _t("%(severalUsers)sleft %(count)s times", { severalUsers: "", count }) - : _t("%(oneUser)sleft %(count)s times", { oneUser: "", count }); + ? _t("timeline|summary|left_multiple", { severalUsers: "", count }) + : _t("timeline|summary|left", { oneUser: "", count }); break; case TransitionType.JoinedAndLeft: res = userCount > 1 - ? _t("%(severalUsers)sjoined and left %(count)s times", { severalUsers: "", count }) - : _t("%(oneUser)sjoined and left %(count)s times", { oneUser: "", count }); + ? _t("timeline|summary|joined_and_left_multiple", { severalUsers: "", count }) + : _t("timeline|summary|joined_and_left", { oneUser: "", count }); break; case TransitionType.LeftAndJoined: res = userCount > 1 - ? _t("%(severalUsers)sleft and rejoined %(count)s times", { severalUsers: "", count }) - : _t("%(oneUser)sleft and rejoined %(count)s times", { oneUser: "", count }); + ? _t("timeline|summary|rejoined_multiple", { severalUsers: "", count }) + : _t("timeline|summary|rejoined", { oneUser: "", count }); break; case TransitionType.InviteReject: res = userCount > 1 - ? _t("%(severalUsers)srejected their invitations %(count)s times", { + ? _t("timeline|summary|rejected_invite_multiple", { severalUsers: "", count, }) - : _t("%(oneUser)srejected their invitation %(count)s times", { oneUser: "", count }); + : _t("timeline|summary|rejected_invite", { oneUser: "", count }); break; case TransitionType.InviteWithdrawal: res = userCount > 1 - ? _t("%(severalUsers)shad their invitations withdrawn %(count)s times", { + ? _t("timeline|summary|invite_withdrawn_multiple", { severalUsers: "", count, }) - : _t("%(oneUser)shad their invitation withdrawn %(count)s times", { oneUser: "", count }); + : _t("timeline|summary|invite_withdrawn", { oneUser: "", count }); break; case TransitionType.Invited: res = userCount > 1 - ? _t("were invited %(count)s times", { count }) - : _t("was invited %(count)s times", { count }); + ? _t("timeline|summary|invited_multiple", { count }) + : _t("timeline|summary|invited", { count }); break; case TransitionType.Banned: res = userCount > 1 - ? _t("were banned %(count)s times", { count }) - : _t("was banned %(count)s times", { count }); + ? _t("timeline|summary|banned_multiple", { count }) + : _t("timeline|summary|banned", { count }); break; case TransitionType.Unbanned: res = userCount > 1 - ? _t("were unbanned %(count)s times", { count }) - : _t("was unbanned %(count)s times", { count }); + ? _t("timeline|summary|unbanned_multiple", { count }) + : _t("timeline|summary|unbanned", { count }); break; case TransitionType.Kicked: res = userCount > 1 - ? _t("were removed %(count)s times", { count }) - : _t("was removed %(count)s times", { count }); + ? _t("timeline|summary|kicked_multiple", { count }) + : _t("timeline|summary|kicked", { count }); break; case TransitionType.ChangedName: res = userCount > 1 - ? _t("%(severalUsers)schanged their name %(count)s times", { severalUsers: "", count }) - : _t("%(oneUser)schanged their name %(count)s times", { oneUser: "", count }); + ? _t("timeline|summary|changed_name_multiple", { severalUsers: "", count }) + : _t("timeline|summary|changed_name", { oneUser: "", count }); break; case TransitionType.ChangedAvatar: res = userCount > 1 - ? _t("%(severalUsers)schanged their profile picture %(count)s times", { + ? _t("timeline|summary|changed_avatar_multiple", { severalUsers: "", count, }) - : _t("%(oneUser)schanged their profile picture %(count)s times", { oneUser: "", count }); + : _t("timeline|summary|changed_avatar", { oneUser: "", count }); break; case TransitionType.NoChange: res = userCount > 1 - ? _t("%(severalUsers)smade no changes %(count)s times", { severalUsers: "", count }) - : _t("%(oneUser)smade no changes %(count)s times", { oneUser: "", count }); + ? _t("timeline|summary|no_change_multiple", { severalUsers: "", count }) + : _t("timeline|summary|no_change", { oneUser: "", count }); break; case TransitionType.ServerAcl: res = userCount > 1 - ? _t("%(severalUsers)schanged the server ACLs %(count)s times", { severalUsers: "", count }) - : _t("%(oneUser)schanged the server ACLs %(count)s times", { oneUser: "", count }); + ? _t("timeline|summary|server_acls_multiple", { severalUsers: "", count }) + : _t("timeline|summary|server_acls", { oneUser: "", count }); break; case TransitionType.ChangedPins: res = userCount > 1 ? _t( - "%(severalUsers)schanged the pinned messages for the room %(count)s times", + "timeline|summary|pinned_events_multiple", { severalUsers: "", count }, { a: (sub) => ( @@ -355,7 +355,7 @@ export default class EventListSummary extends React.Component< }, ) : _t( - "%(oneUser)schanged the pinned messages for the room %(count)s times", + "timeline|summary|pinned_events", { oneUser: "", count }, { a: (sub) => ( @@ -369,14 +369,14 @@ export default class EventListSummary extends React.Component< case TransitionType.MessageRemoved: res = userCount > 1 - ? _t("%(severalUsers)sremoved a message %(count)s times", { severalUsers: "", count }) - : _t("%(oneUser)sremoved a message %(count)s times", { oneUser: "", count }); + ? _t("timeline|summary|redacted_multiple", { severalUsers: "", count }) + : _t("timeline|summary|redacted", { oneUser: "", count }); break; case TransitionType.HiddenEvent: res = userCount > 1 - ? _t("%(severalUsers)ssent %(count)s hidden messages", { severalUsers: "", count }) - : _t("%(oneUser)ssent %(count)s hidden messages", { oneUser: "", count }); + ? _t("timeline|summary|hidden_event_multiple", { severalUsers: "", count }) + : _t("timeline|summary|hidden_event", { oneUser: "", count }); break; } diff --git a/src/components/views/emojipicker/EmojiPicker.tsx b/src/components/views/emojipicker/EmojiPicker.tsx index 51b3b777964..7d1956c8159 100644 --- a/src/components/views/emojipicker/EmojiPicker.tsx +++ b/src/components/views/emojipicker/EmojiPicker.tsx @@ -87,63 +87,63 @@ class EmojiPicker extends React.Component { this.categories = [ { id: "recent", - name: _t("Frequently Used"), + name: _t("emoji|category_frequently_used"), enabled: this.recentlyUsed.length > 0, visible: this.recentlyUsed.length > 0, ref: React.createRef(), }, { id: "people", - name: _t("Smileys & People"), + name: _t("emoji|category_smileys_people"), enabled: true, visible: true, ref: React.createRef(), }, { id: "nature", - name: _t("Animals & Nature"), + name: _t("emoji|category_animals_nature"), enabled: true, visible: false, ref: React.createRef(), }, { id: "foods", - name: _t("Food & Drink"), + name: _t("emoji|category_food_drink"), enabled: true, visible: false, ref: React.createRef(), }, { id: "activity", - name: _t("Activities"), + name: _t("emoji|category_activities"), enabled: true, visible: false, ref: React.createRef(), }, { id: "places", - name: _t("Travel & Places"), + name: _t("emoji|category_travel_places"), enabled: true, visible: false, ref: React.createRef(), }, { id: "objects", - name: _t("Objects"), + name: _t("emoji|category_objects"), enabled: true, visible: false, ref: React.createRef(), }, { id: "symbols", - name: _t("Symbols"), + name: _t("emoji|category_symbols"), enabled: true, visible: false, ref: React.createRef(), }, { id: "flags", - name: _t("Flags"), + name: _t("emoji|category_flags"), enabled: true, visible: false, ref: React.createRef(), diff --git a/src/components/views/emojipicker/Header.tsx b/src/components/views/emojipicker/Header.tsx index c3643f6e2a9..27638bae1ad 100644 --- a/src/components/views/emojipicker/Header.tsx +++ b/src/components/views/emojipicker/Header.tsx @@ -95,7 +95,7 @@ class Header extends React.PureComponent {

diff --git a/src/components/views/right_panel/VerificationPanel.tsx b/src/components/views/right_panel/VerificationPanel.tsx index 7d5e6950e24..5f1344fb66f 100644 --- a/src/components/views/right_panel/VerificationPanel.tsx +++ b/src/components/views/right_panel/VerificationPanel.tsx @@ -90,14 +90,7 @@ export default class VerificationPanel extends React.PureComponent - {_t( - "The device you are trying to verify doesn't support scanning a QR code or emoji verification, which is what %(brand)s supports. Try with a different client.", - { brand }, - )} -

- ) : null; + !showSAS && !showQR ?

{_t("encryption|verification|no_support_qr_emoji", { brand })}

: null; if (this.props.layout === "dialog") { // HACK: This is a terrible idea. @@ -106,7 +99,7 @@ export default class VerificationPanel extends React.PureComponent -

{_t("Scan this unique code")}

+

{_t("encryption|verification|qr_prompt")}

); @@ -114,9 +107,9 @@ export default class VerificationPanel extends React.PureComponent -

{_t("Compare unique emoji")}

+

{_t("encryption|verification|sas_prompt")}

- {_t("Compare a unique set of emoji if you don't have a camera on either device")} + {_t("encryption|verification|sas_description")} - {_t("%(qrCode)s or %(emojiCompare)s", { + {_t("encryption|verification|qr_or_sas", { emojiCompare: "", qrCode: "", })} @@ -139,7 +132,7 @@ export default class VerificationPanel extends React.PureComponent - {_t("Verify this device by completing one of the following:")} + {_t("encryption|verification|qr_or_sas_header")}
{qrBlockDialog} {or} diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 986eafcca9c..77fb270768f 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -1003,7 +1003,7 @@ export class UnwrappedEventTile extends React.Component let avatar: JSX.Element | null = null; let sender: JSX.Element | null = null; - let avatarSize: string; + let avatarSize: string | null; let needsSenderProfile: boolean; if (isRenderingNotification) { @@ -1021,7 +1021,7 @@ export class UnwrappedEventTile extends React.Component avatarSize = "32px"; needsSenderProfile = true; } else if (eventType === EventType.RoomCreate || isBubbleMessage) { - avatarSize = "0"; + avatarSize = null; needsSenderProfile = false; } else if (this.props.layout == Layout.IRC) { avatarSize = "14px"; @@ -1032,14 +1032,14 @@ export class UnwrappedEventTile extends React.Component ElementCall.CALL_EVENT_TYPE.matches(eventType) ) { // no avatar or sender profile for continuation messages and call tiles - avatarSize = "0"; + avatarSize = null; needsSenderProfile = false; } else { avatarSize = "30px"; needsSenderProfile = true; } - if (this.props.mxEvent.sender && avatarSize) { + if (this.props.mxEvent.sender && avatarSize !== null) { let member: RoomMember | null = null; // set member to receiver (target) if it is a 3PID invite // so that the correct avatar is shown as the text is diff --git a/src/components/views/rooms/LegacyRoomHeader.tsx b/src/components/views/rooms/LegacyRoomHeader.tsx index 8f20acab71c..9cbcb9ff78d 100644 --- a/src/components/views/rooms/LegacyRoomHeader.tsx +++ b/src/components/views/rooms/LegacyRoomHeader.tsx @@ -112,8 +112,8 @@ const VoiceCallButton: FC = ({ room, busy, setBusy, behavi @@ -228,8 +228,8 @@ const VideoCallButton: FC = ({ room, busy, setBusy, behavi inputRef={buttonRef} className="mx_LegacyRoomHeader_button mx_LegacyRoomHeader_videoCallButton" onClick={onClick} - title={_t("Video call")} - tooltip={tooltip ?? _t("Video call")} + title={_t("voip|video_call")} + tooltip={tooltip ?? _t("voip|video_call")} alignment={Alignment.Bottom} disabled={disabled || busy} /> diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx index 44c494765f2..8ef680df3a1 100644 --- a/src/components/views/rooms/MessageComposer.tsx +++ b/src/components/views/rooms/MessageComposer.tsx @@ -78,7 +78,7 @@ function SendButton(props: ISendButtonProps): JSX.Element { ); @@ -303,19 +303,19 @@ export class MessageComposer extends React.Component { if (this.props.replyToEvent) { const replyingToThread = this.props.relation?.rel_type === THREAD_RELATION_TYPE.name; if (replyingToThread && this.props.e2eStatus) { - return _t("Reply to encrypted thread…"); + return _t("composer|placeholder_thread_encrypted"); } else if (replyingToThread) { - return _t("Reply to thread…"); + return _t("composer|placeholder_thread"); } else if (this.props.e2eStatus) { - return _t("Send an encrypted reply…"); + return _t("composer|placeholder_reply_encrypted"); } else { - return _t("Send a reply…"); + return _t("composer|placeholder_reply"); } } else { if (this.props.e2eStatus) { - return _t("Send an encrypted message…"); + return _t("composer|placeholder_encrypted"); } else { - return _t("Send a message…"); + return _t("composer|placeholder"); } } }; diff --git a/src/components/views/rooms/RoomHeader.tsx b/src/components/views/rooms/RoomHeader.tsx index 78d3f8139f3..884314c39f2 100644 --- a/src/components/views/rooms/RoomHeader.tsx +++ b/src/components/views/rooms/RoomHeader.tsx @@ -175,10 +175,10 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element { {!useElementCallExclusively && ( - + { evt.stopPropagation(); placeCall(room, CallType.Voice, voiceCallType); @@ -188,10 +188,10 @@ export default function RoomHeader({ room }: { room: Room }): JSX.Element { )} - + { evt.stopPropagation(); placeCall(room, CallType.Video, videoCallType); diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 32e7259c6c4..4d13193e125 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -419,7 +419,7 @@ const TAG_AESTHETICS: TagAestheticsMap = { defaultHidden: false, }, [DefaultTagID.ServerNotice]: { - sectionLabel: _td("System Alerts"), + sectionLabel: _td("common|system_alerts"), isInvite: false, defaultHidden: false, }, diff --git a/src/components/views/rooms/RoomPreviewBar.tsx b/src/components/views/rooms/RoomPreviewBar.tsx index 5f610c344e6..ee1a9a69fb7 100644 --- a/src/components/views/rooms/RoomPreviewBar.tsx +++ b/src/components/views/rooms/RoomPreviewBar.tsx @@ -62,6 +62,7 @@ enum MessageCase { OtherError = "OtherError", PromptAskToJoin = "PromptAskToJoin", Knocked = "Knocked", + RequestDenied = "requestDenied", } interface IProps { @@ -188,7 +189,11 @@ export default class RoomPreviewBar extends React.Component { const myMember = this.getMyMember(); if (myMember) { + const previousMembership = myMember.events.member?.getPrevContent().membership; if (myMember.isKicked()) { + if (previousMembership === "knock") { + return MessageCase.RequestDenied; + } return MessageCase.Kicked; } else if (myMember.membership === "ban") { return MessageCase.Banned; @@ -397,6 +402,21 @@ export default class RoomPreviewBar extends React.Component { } break; } + case MessageCase.RequestDenied: { + title = _t("You have been denied access"); + + subTitle = _t( + "As you have been denied access, you cannot rejoin unless you are invited by the admin or moderator of the group.", + ); + + if (isSpace) { + primaryActionLabel = _t("Forget this space"); + } else { + primaryActionLabel = _t("Forget this room"); + } + primaryActionHandler = this.props.onForgetClick; + break; + } case MessageCase.Banned: { const { memberName, reason } = this.getKickOrBanInfo(); if (roomName) { diff --git a/src/components/views/rooms/Stickerpicker.tsx b/src/components/views/rooms/Stickerpicker.tsx index a929c7196aa..d133c3c8a47 100644 --- a/src/components/views/rooms/Stickerpicker.tsx +++ b/src/components/views/rooms/Stickerpicker.tsx @@ -261,7 +261,7 @@ export default class Stickerpicker extends React.PureComponent { // Load stickerpack content if (!!stickerpickerWidget?.content?.url) { // Set default name - stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("Stickerpack"); + stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("common|stickerpack"); // FIXME: could this use the same code as other apps? const stickerApp: IWidget = { diff --git a/src/components/views/settings/IntegrationManager.tsx b/src/components/views/settings/IntegrationManager.tsx index 355928376eb..486b6d31c90 100644 --- a/src/components/views/settings/IntegrationManager.tsx +++ b/src/components/views/settings/IntegrationManager.tsx @@ -104,6 +104,6 @@ export default class IntegrationManager extends React.Component ); } - return