diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles new file mode 100644 index 0000000000..f1b63d7367 --- /dev/null +++ b/.eslintignore.errorfiles @@ -0,0 +1,187 @@ +# autogenerated file: run scripts/generate-eslint-error-ignore-file to update. + +src/AddThreepid.js +src/async-components/views/dialogs/EncryptedEventDialog.js +src/autocomplete/AutocompleteProvider.js +src/autocomplete/Autocompleter.js +src/autocomplete/Components.js +src/autocomplete/DuckDuckGoProvider.js +src/autocomplete/EmojiProvider.js +src/autocomplete/RoomProvider.js +src/autocomplete/UserProvider.js +src/Avatar.js +src/BasePlatform.js +src/CallHandler.js +src/component-index.js +src/components/structures/ContextualMenu.js +src/components/structures/CreateRoom.js +src/components/structures/FilePanel.js +src/components/structures/InteractiveAuth.js +src/components/structures/LoggedInView.js +src/components/structures/login/ForgotPassword.js +src/components/structures/login/Login.js +src/components/structures/login/PostRegistration.js +src/components/structures/login/Registration.js +src/components/structures/MatrixChat.js +src/components/structures/MessagePanel.js +src/components/structures/NotificationPanel.js +src/components/structures/RoomStatusBar.js +src/components/structures/RoomView.js +src/components/structures/ScrollPanel.js +src/components/structures/TimelinePanel.js +src/components/structures/UploadBar.js +src/components/structures/UserSettings.js +src/components/views/avatars/BaseAvatar.js +src/components/views/avatars/MemberAvatar.js +src/components/views/avatars/RoomAvatar.js +src/components/views/create_room/CreateRoomButton.js +src/components/views/create_room/Presets.js +src/components/views/create_room/RoomAlias.js +src/components/views/dialogs/ChatCreateOrReuseDialog.js +src/components/views/dialogs/ChatInviteDialog.js +src/components/views/dialogs/DeactivateAccountDialog.js +src/components/views/dialogs/InteractiveAuthDialog.js +src/components/views/dialogs/SetMxIdDialog.js +src/components/views/dialogs/UnknownDeviceDialog.js +src/components/views/elements/AccessibleButton.js +src/components/views/elements/ActionButton.js +src/components/views/elements/AddressSelector.js +src/components/views/elements/AddressTile.js +src/components/views/elements/CreateRoomButton.js +src/components/views/elements/DeviceVerifyButtons.js +src/components/views/elements/DirectorySearchBox.js +src/components/views/elements/Dropdown.js +src/components/views/elements/EditableText.js +src/components/views/elements/EditableTextContainer.js +src/components/views/elements/HomeButton.js +src/components/views/elements/LanguageDropdown.js +src/components/views/elements/MemberEventListSummary.js +src/components/views/elements/PowerSelector.js +src/components/views/elements/ProgressBar.js +src/components/views/elements/RoomDirectoryButton.js +src/components/views/elements/SettingsButton.js +src/components/views/elements/StartChatButton.js +src/components/views/elements/TintableSvg.js +src/components/views/elements/TruncatedList.js +src/components/views/elements/UserSelector.js +src/components/views/login/CaptchaForm.js +src/components/views/login/CasLogin.js +src/components/views/login/CountryDropdown.js +src/components/views/login/CustomServerDialog.js +src/components/views/login/InteractiveAuthEntryComponents.js +src/components/views/login/LoginHeader.js +src/components/views/login/PasswordLogin.js +src/components/views/login/RegistrationForm.js +src/components/views/login/ServerConfig.js +src/components/views/messages/MAudioBody.js +src/components/views/messages/MessageEvent.js +src/components/views/messages/MFileBody.js +src/components/views/messages/MImageBody.js +src/components/views/messages/MVideoBody.js +src/components/views/messages/RoomAvatarEvent.js +src/components/views/messages/TextualBody.js +src/components/views/messages/TextualEvent.js +src/components/views/room_settings/AliasSettings.js +src/components/views/room_settings/ColorSettings.js +src/components/views/room_settings/UrlPreviewSettings.js +src/components/views/rooms/Autocomplete.js +src/components/views/rooms/AuxPanel.js +src/components/views/rooms/EntityTile.js +src/components/views/rooms/EventTile.js +src/components/views/rooms/LinkPreviewWidget.js +src/components/views/rooms/MemberDeviceInfo.js +src/components/views/rooms/MemberInfo.js +src/components/views/rooms/MemberList.js +src/components/views/rooms/MemberTile.js +src/components/views/rooms/MessageComposer.js +src/components/views/rooms/MessageComposerInput.js +src/components/views/rooms/MessageComposerInputOld.js +src/components/views/rooms/PresenceLabel.js +src/components/views/rooms/ReadReceiptMarker.js +src/components/views/rooms/RoomHeader.js +src/components/views/rooms/RoomList.js +src/components/views/rooms/RoomNameEditor.js +src/components/views/rooms/RoomPreviewBar.js +src/components/views/rooms/RoomSettings.js +src/components/views/rooms/RoomTile.js +src/components/views/rooms/RoomTopicEditor.js +src/components/views/rooms/SearchableEntityList.js +src/components/views/rooms/SearchResultTile.js +src/components/views/rooms/TabCompleteBar.js +src/components/views/rooms/TopUnreadMessagesBar.js +src/components/views/rooms/UserTile.js +src/components/views/settings/AddPhoneNumber.js +src/components/views/settings/ChangeAvatar.js +src/components/views/settings/ChangeDisplayName.js +src/components/views/settings/ChangePassword.js +src/components/views/settings/DevicesPanel.js +src/components/views/settings/DevicesPanelEntry.js +src/components/views/settings/EnableNotificationsButton.js +src/components/views/voip/CallView.js +src/components/views/voip/IncomingCallBox.js +src/components/views/voip/VideoFeed.js +src/components/views/voip/VideoView.js +src/ContentMessages.js +src/createRoom.js +src/DateUtils.js +src/email.js +src/Entities.js +src/extend.js +src/HtmlUtils.js +src/ImageUtils.js +src/Invite.js +src/languageHandler.js +src/linkify-matrix.js +src/Login.js +src/Markdown.js +src/MatrixClientPeg.js +src/Modal.js +src/Notifier.js +src/ObjectUtils.js +src/PasswordReset.js +src/PlatformPeg.js +src/Presence.js +src/ratelimitedfunc.js +src/Resend.js +src/RichText.js +src/Roles.js +src/RoomListSorter.js +src/RoomNotifs.js +src/Rooms.js +src/RtsClient.js +src/ScalarAuthClient.js +src/ScalarMessaging.js +src/SdkConfig.js +src/Skinner.js +src/SlashCommands.js +src/stores/LifecycleStore.js +src/TabComplete.js +src/TabCompleteEntries.js +src/TextForEvent.js +src/Tinter.js +src/UiEffects.js +src/Unread.js +src/UserActivity.js +src/utils/DecryptFile.js +src/utils/DMRoomMap.js +src/utils/FormattingUtils.js +src/utils/MultiInviter.js +src/utils/Receipt.js +src/Velociraptor.js +src/VelocityBounce.js +src/WhoIsTyping.js +src/wrappers/WithMatrixClient.js +test/all-tests.js +test/components/structures/login/Registration-test.js +test/components/structures/MessagePanel-test.js +test/components/structures/ScrollPanel-test.js +test/components/structures/TimelinePanel-test.js +test/components/stub-component.js +test/components/views/dialogs/InteractiveAuthDialog-test.js +test/components/views/elements/MemberEventListSummary-test.js +test/components/views/login/RegistrationForm-test.js +test/components/views/rooms/MessageComposerInput-test.js +test/mock-clock.js +test/skinned-sdk.js +test/stores/RoomViewStore-test.js +test/test-utils.js diff --git a/.travis.yml b/.travis.yml index a405b9ef35..918cec696b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,4 @@ install: - npm install - (cd node_modules/matrix-js-sdk && npm install) script: - # don't run the riot tests unless the react-sdk tests pass, otherwise - # the output is confusing. - - npm run test && ./.travis-test-riot.sh + ./scripts/travis.sh diff --git a/jenkins.sh b/jenkins.sh index 6a77911c27..d9bb62855b 100755 --- a/jenkins.sh +++ b/jenkins.sh @@ -21,6 +21,11 @@ npm run test # run eslint npm run lintall -- -f checkstyle -o eslint.xml || true +# re-run the linter, excluding any files known to have errors or warnings. +./node_modules/.bin/eslint --max-warnings 0 \ + --ignore-path .eslintignore.errorfiles \ + src test + # delete the old tarball, if it exists rm -f matrix-react-sdk-*.tgz diff --git a/scripts/fix-i18n.pl b/scripts/fix-i18n.pl index ea19d710df..def352463d 100755 --- a/scripts/fix-i18n.pl +++ b/scripts/fix-i18n.pl @@ -66,6 +66,11 @@ Some of your messages have not been sent. This room is private or inaccessible to guests. You may be able to join if you register. 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 you do not have permission to view the message in question. +This action cannot be performed by a guest user. Please register to be able to do this. +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 you do not have permission to view the message in question. +You are trying to access %(roomName)s. +You will not be able to undo this change as you are promoting the user to have the same power level as yourself. EOT )]; } @@ -89,7 +94,7 @@ if ($_ =~ m/^(\s+)"(.*?)"(: *)"(.*?)"(,?)$/) { $sub = 1; } - if ($src eq $fixup && $dst !~ /\.$/) { + if ($ARGV !~ /(zh_Hans|zh_Hant|th)\.json$/ && $src eq $fixup && $dst !~ /\.$/) { print STDERR "fixing up dst: $dst\n"; $dst .= '.'; $sub = 1; diff --git a/scripts/generate-eslint-error-ignore-file b/scripts/generate-eslint-error-ignore-file new file mode 100755 index 0000000000..3a635f5a7d --- /dev/null +++ b/scripts/generate-eslint-error-ignore-file @@ -0,0 +1,21 @@ +#!/bin/sh +# +# generates .eslintignore.errorfiles to list the files which have errors in, +# so that they can be ignored in future automated linting. + +out=.eslintignore.errorfiles + +cd `dirname $0`/.. + +echo "generating $out" + +{ + cat < 0) | .filePath' | + sed -e 's/.*matrix-react-sdk\///'; +} > "$out" diff --git a/scripts/travis.sh b/scripts/travis.sh new file mode 100755 index 0000000000..f349b06ad5 --- /dev/null +++ b/scripts/travis.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -ex + +npm run test +./.travis-test-riot.sh + +# run the linter, but exclude any files known to have errors or warnings. +./node_modules/.bin/eslint --max-warnings 0 \ + --ignore-path .eslintignore.errorfiles \ + src test diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 8af1894c79..aec32092ed 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -345,6 +345,7 @@ export function bodyToHtml(content, highlights, opts) { } safeBody = sanitizeHtml(body, sanitizeHtmlParams); safeBody = unicodeToImage(safeBody); + safeBody = addCodeCopyButton(safeBody); } finally { delete sanitizeHtmlParams.textFilter; @@ -363,6 +364,23 @@ export function bodyToHtml(content, highlights, opts) { return ; } +function addCodeCopyButton(safeBody) { + // Adds 'copy' buttons to pre blocks + // Note that this only manipulates the markup to add the buttons: + // we need to add the event handlers once the nodes are in the DOM + // since we can't save functions in the markup. + // This is done in TextualBody + const el = document.createElement("div"); + el.innerHTML = safeBody; + const codeBlocks = Array.from(el.getElementsByTagName("pre")); + codeBlocks.forEach(p => { + const button = document.createElement("span"); + button.className = "mx_EventTile_copyButton"; + p.appendChild(button); + }); + return el.innerHTML; +} + export function emojifyText(text) { return { __html: unicodeToImage(escape(text)), diff --git a/src/Login.js b/src/Login.js index 87731744e9..8db6e99b89 100644 --- a/src/Login.js +++ b/src/Login.js @@ -97,11 +97,6 @@ export default class Login { guest: true }; }, (error) => { - if (error.httpStatus === 403) { - error.friendlyText = _t("Guest access is disabled on this Home Server."); - } else { - error.friendlyText = _t("Failed to register as guest:") + ' ' + error.data; - } throw error; }); } @@ -157,15 +152,7 @@ export default class Login { accessToken: data.access_token }); }, function(error) { - if (error.httpStatus == 400 && loginParams.medium) { - error.friendlyText = ( - _t('This Home Server does not support login using email address.') - ); - } - else if (error.httpStatus === 403) { - error.friendlyText = ( - _t('Incorrect username and/or password.') - ); + if (error.httpStatus === 403) { if (self._fallbackHsUrl) { var fbClient = Matrix.createClient({ baseUrl: self._fallbackHsUrl, @@ -186,11 +173,6 @@ export default class Login { }); } } - else { - error.friendlyText = ( - _t("There was a problem logging in.") + ' (HTTP ' + error.httpStatus + ")" - ); - } throw error; }); } diff --git a/src/async-components/views/dialogs/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/ExportE2eKeysDialog.js index d6f16a7105..045ea63c34 100644 --- a/src/async-components/views/dialogs/ExportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ExportE2eKeysDialog.js @@ -120,7 +120,7 @@ export default React.createClass({ '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.' + 'decrypt these messages.', ) }

@@ -130,7 +130,7 @@ export default React.createClass({ 'careful to keep it secure. To help with this, you should enter ' + 'a passphrase below, which will be used to encrypt the exported ' + 'data. It will only be possible to import the data by using the ' + - 'same passphrase.' + 'same passphrase.', ) }

diff --git a/src/async-components/views/dialogs/ImportE2eKeysDialog.js b/src/async-components/views/dialogs/ImportE2eKeysDialog.js index 61d2aeec74..91010d33b9 100644 --- a/src/async-components/views/dialogs/ImportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ImportE2eKeysDialog.js @@ -122,13 +122,13 @@ export default React.createClass({ '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.' + 'messages that the other client could decrypt.', ) }

{ _t( 'The export file will be protected with a passphrase. ' + - 'You should enter the passphrase here, to decrypt the file.' + 'You should enter the passphrase here, to decrypt the file.', ) }

diff --git a/src/components/structures/LoggedInView.js b/src/components/structures/LoggedInView.js index a201a0bea7..8fa35e84d7 100644 --- a/src/components/structures/LoggedInView.js +++ b/src/components/structures/LoggedInView.js @@ -223,10 +223,8 @@ export default React.createClass({ ref='roomView' autoJoin={this.props.autoJoin} onRegistered={this.props.onRegistered} - eventId={this.props.initialEventId} thirdPartyInvite={this.props.thirdPartyInvite} oobData={this.props.roomOobData} - highlightedEventId={this.props.highlightedEventId} eventPixelOffset={this.props.initialEventPixelOffset} key={this.props.currentRoomId || 'roomview'} opacity={this.props.middleOpacity} diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index d11e100f26..b6e3552a95 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -607,6 +607,8 @@ module.exports = React.createClass({ // @param {boolean=} roomInfo.show_settings Makes RoomView show the room settings dialog. // @param {string=} roomInfo.event_id ID of the event in this room to show: this will cause a switch to the // context of that particular event. + // @param {boolean=} roomInfo.highlighted If true, add event_id to the hash of the URL + // and alter the EventTile to appear highlighted. // @param {Object=} roomInfo.third_party_invite Object containing data about the third party // we received to join the room, if any. // @param {string=} roomInfo.third_party_invite.inviteSignUrl 3pid invite sign URL @@ -618,40 +620,20 @@ module.exports = React.createClass({ this.focusComposer = true; const newState = { - initialEventId: roomInfo.event_id, - highlightedEventId: roomInfo.event_id, - initialEventPixelOffset: undefined, page_type: PageTypes.RoomView, thirdPartyInvite: roomInfo.third_party_invite, roomOobData: roomInfo.oob_data, - currentRoomAlias: roomInfo.room_alias, autoJoin: roomInfo.auto_join, }; - if (!roomInfo.room_alias) { - newState.currentRoomId = roomInfo.room_id; - } - - // if we aren't given an explicit event id, look for one in the - // scrollStateMap. - // - // TODO: do this in RoomView rather than here - if (!roomInfo.event_id && this.refs.loggedInView) { - const scrollState = this.refs.loggedInView.getScrollStateForRoom(roomInfo.room_id); - if (scrollState) { - newState.initialEventId = scrollState.focussedEvent; - newState.initialEventPixelOffset = scrollState.pixelOffset; - } - } - if (roomInfo.room_alias) { console.log( `Switching to room alias ${roomInfo.room_alias} at event ` + - newState.initialEventId, + roomInfo.event_id, ); } else { console.log(`Switching to room id ${roomInfo.room_id} at event ` + - newState.initialEventId, + roomInfo.event_id, ); } @@ -680,7 +662,7 @@ module.exports = React.createClass({ } } - if (roomInfo.event_id) { + if (roomInfo.event_id && roomInfo.highlighted) { presentedId += "/" + roomInfo.event_id; } this.notifyNewScreen('room/' + presentedId); @@ -1137,6 +1119,10 @@ module.exports = React.createClass({ const payload = { action: 'view_room', event_id: eventId, + // If an event ID is given in the URL hash, notify RoomViewStore to mark + // it as highlighted, which will propagate to RoomView and highlight the + // associated EventTile. + highlighted: Boolean(eventId), third_party_invite: thirdPartyInvite, oob_data: oobData, }; diff --git a/src/components/structures/RoomView.js b/src/components/structures/RoomView.js index df21715d75..79420e776a 100644 --- a/src/components/structures/RoomView.js +++ b/src/components/structures/RoomView.js @@ -83,36 +83,8 @@ module.exports = React.createClass({ // * invited us tovthe room oobData: React.PropTypes.object, - // id of an event to jump to. If not given, will go to the end of the - // live timeline. - eventId: React.PropTypes.string, - - // where to position the event given by eventId, in pixels from the - // bottom of the viewport. If not given, will try to put the event - // 1/3 of the way down the viewport. - eventPixelOffset: React.PropTypes.number, - - // ID of an event to highlight. If undefined, no event will be highlighted. - // Typically this will either be the same as 'eventId', or undefined. - highlightedEventId: React.PropTypes.string, - // is the RightPanel collapsed? collapsedRhs: React.PropTypes.bool, - - // a map from room id to scroll state, which will be updated on unmount. - // - // If there is no special scroll state (ie, we are following the live - // timeline), the scroll state is null. Otherwise, it is an object with - // the following properties: - // - // focussedEvent: the ID of the 'focussed' event. Typically this is - // the last event fully visible in the viewport, though if we - // have done an explicit scroll to an explicit event, it will be - // that event. - // - // pixelOffset: the number of pixels the window is scrolled down - // from the focussedEvent. - scrollStateMap: React.PropTypes.object, }, getInitialState: function() { @@ -122,6 +94,13 @@ module.exports = React.createClass({ roomLoading: true, peekLoading: false, + // The event to be scrolled to initially + initialEventId: null, + // The offset in pixels from the event with which to scroll vertically + initialEventPixelOffset: null, + // Whether to highlight the event scrolled to + isInitialEventHighlighted: null, + forwardingEvent: null, editingRoomSettings: false, uploadingRoomSettings: false, @@ -180,13 +159,32 @@ module.exports = React.createClass({ if (this.unmounted) { return; } - this.setState({ + const newState = { roomId: RoomViewStore.getRoomId(), roomAlias: RoomViewStore.getRoomAlias(), roomLoading: RoomViewStore.isRoomLoading(), roomLoadError: RoomViewStore.getRoomLoadError(), joining: RoomViewStore.isJoining(), - }, () => { + initialEventId: RoomViewStore.getInitialEventId(), + initialEventPixelOffset: RoomViewStore.getInitialEventPixelOffset(), + isInitialEventHighlighted: RoomViewStore.isInitialEventHighlighted(), + }; + + // Clear the search results when clicking a search result (which changes the + // currently scrolled to event, this.state.initialEventId). + if (this.state.initialEventId !== newState.initialEventId) { + newState.searchResults = null; + } + + // Store the scroll state for the previous room so that we can return to this + // position when viewing this room in future. + if (this.state.roomId !== newState.roomId) { + this._updateScrollMap(this.state.roomId); + } + + this.setState(newState, () => { + // At this point, this.state.roomId could be null (e.g. the alias might not + // have been resolved yet) so anything called here must handle this case. this._onHaveRoom(); this.onRoom(MatrixClientPeg.get().getRoom(this.state.roomId)); }); @@ -287,13 +285,6 @@ module.exports = React.createClass({ } }, - componentWillReceiveProps: function(newProps) { - if (newProps.eventId != this.props.eventId) { - // when we change focussed event id, hide the search results. - this.setState({searchResults: null}); - } - }, - shouldComponentUpdate: function(nextProps, nextState) { return (!ObjectUtils.shallowEqual(this.props, nextProps) || !ObjectUtils.shallowEqual(this.state, nextState)); @@ -319,7 +310,7 @@ module.exports = React.createClass({ this.unmounted = true; // update the scroll map before we get unmounted - this._updateScrollMap(); + this._updateScrollMap(this.state.roomId); if (this.refs.roomView) { // disconnect the D&D event listeners from the room view. This @@ -598,6 +589,18 @@ module.exports = React.createClass({ }); }, + _updateScrollMap(roomId) { + // No point updating scroll state if the room ID hasn't been resolved yet + if (!roomId) { + return; + } + dis.dispatch({ + action: 'update_scroll_state', + room_id: roomId, + scroll_state: this._getScrollState(), + }); + }, + onRoom: function(room) { if (!room || room.roomId !== this.state.roomId) { return; @@ -1240,21 +1243,6 @@ module.exports = React.createClass({ } }, - // update scrollStateMap on unmount - _updateScrollMap: function() { - if (!this.state.room) { - // we were instantiated on a room alias and haven't yet joined the room. - return; - } - if (!this.props.scrollStateMap) return; - - var roomId = this.state.room.roomId; - - var state = this._getScrollState(); - this.props.scrollStateMap[roomId] = state; - }, - - // get the current scroll position of the room, so that it can be // restored when we switch back to it. // @@ -1677,6 +1665,14 @@ module.exports = React.createClass({ hideMessagePanel = true; } + const shouldHighlight = this.state.isInitialEventHighlighted; + let highlightedEventId = null; + if (this.state.forwardingEvent) { + highlightedEventId = this.state.forwardingEvent.getId(); + } else if (shouldHighlight) { + highlightedEventId = this.state.initialEventId; + } + // console.log("ShowUrlPreview for %s is %s", this.state.room.roomId, this.state.showUrlPreview); var messagePanel = (
); diff --git a/src/components/views/dialogs/SessionRestoreErrorDialog.js b/src/components/views/dialogs/SessionRestoreErrorDialog.js index e7f6e19db7..a3eb7c6962 100644 --- a/src/components/views/dialogs/SessionRestoreErrorDialog.js +++ b/src/components/views/dialogs/SessionRestoreErrorDialog.js @@ -18,7 +18,7 @@ import React from 'react'; import sdk from '../../../index'; import SdkConfig from '../../../SdkConfig'; import Modal from '../../../Modal'; -import { _t } from '../../../languageHandler'; +import { _t, _tJsx } from '../../../languageHandler'; export default React.createClass({ @@ -44,8 +44,11 @@ export default React.createClass({ if (SdkConfig.get().bug_report_endpoint_url) { bugreport = ( -

Otherwise, - click here to send a bug report. +

+ {_tJsx( + "Otherwise, click here to send a bug report.", + /(.*?)<\/a>/, (sub) => {sub}, + )}

); } diff --git a/src/components/views/dialogs/UnknownDeviceDialog.js b/src/components/views/dialogs/UnknownDeviceDialog.js index 8f91b84519..6ebd0c3efc 100644 --- a/src/components/views/dialogs/UnknownDeviceDialog.js +++ b/src/components/views/dialogs/UnknownDeviceDialog.js @@ -145,7 +145,7 @@ export default React.createClass({ console.log("UnknownDeviceDialog closed by escape"); this.props.onFinished(); }} - title='Room contains unknown devices' + title={_t('Room contains unknown devices')} >

@@ -162,7 +162,7 @@ export default React.createClass({ this.props.onFinished(); Resend.resendUnsentEvents(this.props.room); }}> - Send anyway + {_t("Send anyway")}