From d419c42a4f32b993dcf3e2d5c674f83c41490a85 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 23 May 2017 15:16:31 +0100 Subject: [PATCH 01/42] Squash merge https://github.com/matrix-org/matrix-react-sdk/pull/801 --- README.md | 3 +- package.json | 1 + src/AddThreepid.js | 7 +- src/CallHandler.js | 48 +- src/ContentMessages.js | 10 +- src/DateUtils.js | 49 +- src/Lifecycle.js | 11 +- src/Notifier.js | 10 +- src/PasswordReset.js | 7 +- src/Roles.js | 17 +- src/ScalarMessaging.js | 33 +- src/SlashCommands.js | 8 +- src/TextForEvent.js | 95 ++- src/UserSettingsStore.js | 6 +- src/WhoIsTyping.js | 13 +- .../views/dialogs/EncryptedEventDialog.js | 45 +- src/autocomplete/CommandProvider.js | 6 +- src/autocomplete/EmojiProvider.js | 3 +- src/autocomplete/RoomProvider.js | 3 +- src/autocomplete/UserProvider.js | 3 +- src/components/structures/CreateRoom.js | 23 +- src/components/structures/FilePanel.js | 14 +- src/components/structures/MatrixChat.js | 38 +- .../structures/NotificationPanel.js | 5 +- src/components/structures/RoomStatusBar.js | 34 +- src/components/structures/RoomView.js | 74 +- src/components/structures/TimelinePanel.js | 13 +- src/components/structures/UserSettings.js | 192 +++-- .../structures/login/ForgotPassword.js | 55 +- src/components/structures/login/Login.js | 22 +- .../structures/login/PostRegistration.js | 13 +- .../structures/login/Registration.js | 25 +- .../views/dialogs/ChatInviteDialog.js | 9 +- .../views/dialogs/ConfirmUserActionDialog.js | 7 +- src/components/views/dialogs/ErrorDialog.js | 3 - .../views/dialogs/InteractiveAuthDialog.js | 6 - .../views/dialogs/NeedToRegisterDialog.js | 7 - .../views/dialogs/QuestionDialog.js | 1 - .../views/dialogs/TextInputDialog.js | 4 +- .../views/elements/LanguageDropdown.js | 140 ++++ .../views/elements/MemberEventListSummary.js | 164 +++- .../views/elements/PowerSelector.js | 17 +- src/components/views/login/PasswordLogin.js | 17 +- src/components/views/messages/MFileBody.js | 17 +- .../views/room_settings/AliasSettings.js | 27 +- src/components/views/rooms/EventTile.js | 7 +- src/components/views/rooms/MemberInfo.js | 62 +- src/components/views/rooms/MemberList.js | 9 +- src/components/views/rooms/MessageComposer.js | 33 +- .../views/rooms/MessageComposerInput.js | 13 +- .../views/rooms/MessageComposerInputOld.js | 16 +- src/components/views/rooms/RoomHeader.js | 18 +- src/components/views/rooms/RoomList.js | 24 +- src/components/views/rooms/RoomPreviewBar.js | 15 +- src/components/views/rooms/RoomSettings.js | 112 +-- .../views/rooms/SearchableEntityList.js | 9 +- .../views/rooms/TopUnreadMessagesBar.js | 6 +- .../views/settings/AddPhoneNumber.js | 16 +- .../views/settings/ChangePassword.js | 29 +- .../views/settings/DevicesPanelEntry.js | 8 +- src/createRoom.js | 10 +- src/i18n/strings/basefile.json | 1 + src/i18n/strings/da.json | 215 ++++++ src/i18n/strings/de_DE.json | 702 +++++++++++++++++ src/i18n/strings/en_EN.json | 656 ++++++++++++++++ src/i18n/strings/fr.json | 170 +++++ src/i18n/strings/ml.json | 2 + src/i18n/strings/pt.json | 503 +++++++++++++ src/i18n/strings/pt_BR.json | 706 ++++++++++++++++++ src/i18n/strings/ru.json | 476 ++++++++++++ src/index.js | 4 +- src/languageHandler.js | 166 ++++ test/test-utils.js | 6 + 73 files changed, 4660 insertions(+), 639 deletions(-) create mode 100644 src/components/views/elements/LanguageDropdown.js create mode 100644 src/i18n/strings/basefile.json create mode 100644 src/i18n/strings/da.json create mode 100644 src/i18n/strings/de_DE.json create mode 100644 src/i18n/strings/en_EN.json create mode 100644 src/i18n/strings/fr.json create mode 100644 src/i18n/strings/ml.json create mode 100644 src/i18n/strings/pt.json create mode 100644 src/i18n/strings/pt_BR.json create mode 100644 src/i18n/strings/ru.json create mode 100644 src/languageHandler.js diff --git a/README.md b/README.md index 3627225299..981a217c10 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Translationsstatus](https://translate.nordgedanken.de/widgets/riot-web/-/multi-auto.svg)](https://translate.nordgedanken.de/engage/riot-web/?utm_source=widget) + matrix-react-sdk ================ @@ -190,4 +192,3 @@ Alternative instructions: * Create an index.html file pulling in your compiled javascript and the CSS bundle from the skin you use. For now, you'll also need to manually import CSS from any skins that your skin inherts from. - diff --git a/package.json b/package.json index 059fdd390f..11106325f1 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "browser-request": "^0.3.3", "classnames": "^2.1.2", "commonmark": "^0.27.0", + "counterpart-riot": "^0.18.1", "draft-js": "^0.8.1", "draft-js-export-html": "^0.5.0", "draft-js-export-markdown": "^0.2.0", diff --git a/src/AddThreepid.js b/src/AddThreepid.js index c89de4f5fa..0503f4ff9d 100644 --- a/src/AddThreepid.js +++ b/src/AddThreepid.js @@ -16,6 +16,7 @@ limitations under the License. */ var MatrixClientPeg = require("./MatrixClientPeg"); +import _t from 'counterpart-riot'; /** * Allows a user to add a third party identifier to their Home Server and, @@ -44,7 +45,7 @@ class AddThreepid { return res; }, function(err) { if (err.errcode == 'M_THREEPID_IN_USE') { - err.message = "This email address is already in use"; + err.message = _t('This email address is already in use'); } else if (err.httpStatus) { err.message = err.message + ` (Status ${err.httpStatus})`; } @@ -69,7 +70,7 @@ class AddThreepid { return res; }, function(err) { if (err.errcode == 'M_THREEPID_IN_USE') { - err.message = "This phone number is already in use"; + err.message = _t('This phone number is already in use'); } else if (err.httpStatus) { err.message = err.message + ` (Status ${err.httpStatus})`; } @@ -91,7 +92,7 @@ class AddThreepid { id_server: identityServerDomain }, this.bind).catch(function(err) { if (err.httpStatus === 401) { - err.message = "Failed to verify email address: make sure you clicked the link in the email"; + err.message = _t('Failed to verify email address: make sure you clicked the link in the email'); } else if (err.httpStatus) { err.message += ` (Status ${err.httpStatus})`; diff --git a/src/CallHandler.js b/src/CallHandler.js index 5199ef0a67..f5283f6d49 100644 --- a/src/CallHandler.js +++ b/src/CallHandler.js @@ -55,6 +55,7 @@ var MatrixClientPeg = require('./MatrixClientPeg'); var PlatformPeg = require("./PlatformPeg"); var Modal = require('./Modal'); var sdk = require('./index'); +import _t from 'counterpart-riot'; var Matrix = require("matrix-js-sdk"); var dis = require("./dispatcher"); @@ -142,8 +143,9 @@ function _setCallListeners(call) { play("busyAudio"); var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - title: "Call Timeout", - description: "The remote side failed to pick up." + title: _t('Call Timeout'), + description: _t('The remote side failed to pick up') + '.', + button: _t("OK"), }); } else if (oldState === "invite_sent") { @@ -203,8 +205,9 @@ function _onAction(payload) { console.log("Can't capture screen: " + screenCapErrorString); const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - title: "Unable to capture screen", - description: screenCapErrorString + title: _t('Unable to capture screen'), + description: screenCapErrorString, + button: _t("OK"), }); return; } @@ -223,8 +226,9 @@ function _onAction(payload) { if (module.exports.getAnyActiveCall()) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - title: "Existing Call", - description: "You are already in a call." + title: _t('Existing Call'), + description: _t('You are already in a call') + '.', + button: _t("OK"), }); return; // don't allow >1 call to be placed. } @@ -233,8 +237,9 @@ function _onAction(payload) { if (!MatrixClientPeg.get().supportsVoip()) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - title: "VoIP is unsupported", - description: "You cannot place VoIP calls in this browser." + title: _t('VoIP is unsupported'), + description: _t('You cannot place VoIP calls in this browser') + '.', + button: _t("OK"), }); return; } @@ -249,7 +254,9 @@ function _onAction(payload) { if (members.length <= 1) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - description: "You cannot place a call with yourself." + description: _t('You cannot place a call with yourself') + '.', + title: _t("Error"), + button: _t("OK"), }); return; } @@ -275,14 +282,17 @@ function _onAction(payload) { if (!ConferenceHandler) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - description: "Conference calls are not supported in this client" + description: _t('Conference calls are not supported in this client'), + title: _t("Error"), + button: _t("OK"), }); } else if (!MatrixClientPeg.get().supportsVoip()) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - title: "VoIP is unsupported", - description: "You cannot place VoIP calls in this browser." + title: _t('VoIP is unsupported'), + description: _t('You cannot place VoIP calls in this browser') + '.', + button: _t("OK"), }); } else if (MatrixClientPeg.get().isRoomEncrypted(payload.room_id)) { @@ -294,14 +304,17 @@ function _onAction(payload) { // Therefore we disable conference calling in E2E rooms. const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - description: "Conference calls are not supported in encrypted rooms", + title: _t("Error"), + button: _t("OK"), + description: _t('Conference calls are not supported in encrypted rooms'), }); } else { var QuestionDialog = sdk.getComponent("dialogs.QuestionDialog"); Modal.createDialog(QuestionDialog, { - title: "Warning!", - description: "Conference calling is in development and may not be reliable.", + title: _t('Warning') + '!', + description: _t('Conference calling is in development and may not be reliable') + '.', + button: _t("OK"), onFinished: confirm=>{ if (confirm) { ConferenceHandler.createNewMatrixCall( @@ -312,8 +325,9 @@ function _onAction(payload) { const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); console.error("Conference call failed: " + err); Modal.createDialog(ErrorDialog, { - title: "Failed to set up conference call", - description: "Conference call failed. " + ((err && err.message) ? err.message : ""), + title: _t('Failed to set up conference call'), + description: _t('Conference call failed') + '. ' + ((err && err.message) ? err.message : ''), + button: _t("OK"), }); }); } diff --git a/src/ContentMessages.js b/src/ContentMessages.js index 4ab982c98f..6e31a60556 100644 --- a/src/ContentMessages.js +++ b/src/ContentMessages.js @@ -21,6 +21,7 @@ var extend = require('./extend'); var dis = require('./dispatcher'); var MatrixClientPeg = require('./MatrixClientPeg'); var sdk = require('./index'); +import _t from 'counterpart-riot'; var Modal = require('./Modal'); var encrypt = require("browser-encrypt-attachment"); @@ -347,14 +348,15 @@ class ContentMessages { }, function(err) { error = err; if (!upload.canceled) { - var desc = "The file '"+upload.fileName+"' failed to upload."; + var desc = _t('The file \'%(fileName)s\' failed to upload', {fileName: upload.fileName}) + '.'; if (err.http_status == 413) { - desc = "The file '"+upload.fileName+"' exceeds this home server's size limit for uploads"; + desc = _t('The file \'%(fileName)s\' exceeds this home server\'s size limit for uploads', {fileName: upload.fileName}); } var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog"); Modal.createDialog(ErrorDialog, { - title: "Upload Failed", - description: desc + title: _t('Upload Failed'), + description: desc, + button: _t("OK"), }); } }).finally(() => { diff --git a/src/DateUtils.js b/src/DateUtils.js index c58c09d4de..87b6fa690f 100644 --- a/src/DateUtils.js +++ b/src/DateUtils.js @@ -15,9 +15,36 @@ limitations under the License. */ 'use strict'; +import _t from 'counterpart-riot'; -var days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; -var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; +function getDaysArray() { + var days = []; + days.push(_t('Sun')); + days.push(_t('Mon')); + days.push(_t('Tue')); + days.push(_t('Wed')); + days.push(_t('Thu')); + days.push(_t('Fri')); + days.push(_t('Sat')); + return days; +} + +function getMonthsArray() { + var months = []; + months.push(_t('Jan')); + months.push(_t('Feb')); + months.push(_t('Mar')); + months.push(_t('Apr')); + months.push(_t('May')); + months.push(_t('Jun')); + months.push(_t('Jul')); + months.push(_t('Aug')); + months.push(_t('Sep')); + months.push(_t('Oct')); + months.push(_t('Nov')); + months.push(_t('Dec')); + return months; +} function pad(n) { return (n < 10 ? '0' : '') + n; @@ -27,16 +54,22 @@ module.exports = { formatDate: function(date) { // date.toLocaleTimeString is completely system dependent. // just go 24h for now + const days = getDaysArray(); + const months = getMonthsArray(); + // TODO: use standard date localize function provided in counterpart + var hoursAndMinutes = pad(date.getHours()) + ':' + pad(date.getMinutes()); var now = new Date(); if (date.toDateString() === now.toDateString()) { - return pad(date.getHours()) + ':' + pad(date.getMinutes()); + return hoursAndMinutes; } else if (now.getTime() - date.getTime() < 6 * 24 * 60 * 60 * 1000) { - return days[date.getDay()] + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); + // TODO: use standard date localize function provided in counterpart + return _t('%(weekDayName)s %(time)s', {weekDayName: days[date.getDay()], time: hoursAndMinutes}); } else if (now.getFullYear() === date.getFullYear()) { - return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); + // TODO: use standard date localize function provided in counterpart + return _t('%(weekDayName)s, %(monthName)s %(day)s %(time)s', {weekDayName: days[date.getDay()], monthName: months[date.getMonth()], day: date.getDate(), time: hoursAndMinutes}); } else { return this.formatFullDate(date); @@ -44,11 +77,13 @@ module.exports = { }, formatFullDate: function(date) { - return days[date.getDay()] + ", " + months[date.getMonth()] + " " + date.getDate() + " " + date.getFullYear() + " " + pad(date.getHours()) + ':' + pad(date.getMinutes()); + const days = getDaysArray(); + const months = getMonthsArray(); + var hoursAndMinutes = pad(date.getHours()) + ':' + pad(date.getMinutes()); + return _t('%(weekDayName)s, %(monthName)s %(day)s %(fullYear)s %(time)s', {weekDayName: days[date.getDay()], monthName: months[date.getMonth()], day: date.getDate(), fullYear: date.getFullYear(),time: hoursAndMinutes}); }, formatTime: function(date) { return pad(date.getHours()) + ':' + pad(date.getMinutes()); } }; - diff --git a/src/Lifecycle.js b/src/Lifecycle.js index f34aeae0e5..56af920609 100644 --- a/src/Lifecycle.js +++ b/src/Lifecycle.js @@ -27,6 +27,7 @@ import DMRoomMap from './utils/DMRoomMap'; import RtsClient from './RtsClient'; import Modal from './Modal'; import sdk from './index'; +import _t from 'counterpart-riot'; /** * Called at startup, to attempt to build a logged-in Matrix session. It tries @@ -229,14 +230,16 @@ function _handleRestoreFailure(e) { let msg = e.message; if (msg == "OLM.BAD_LEGACY_ACCOUNT_PICKLE") { - msg = "You need to log back in to generate end-to-end encryption keys " - + "for this device and submit the public key to your homeserver. " - + "This is a once off; sorry for the inconvenience."; + msg = _t( + 'You need to log back in to generate end-to-end ' + + 'encryption keys for this device and submit the public key to your homeserver. ' + + 'This is a once off; sorry for the inconvenience' + ) + '.'; _clearLocalStorage(); return q.reject(new Error( - "Unable to restore previous session: " + msg, + _t('Unable to restore previous session') + ': ' + msg, )); } diff --git a/src/Notifier.js b/src/Notifier.js index 6473ab4d9c..9d3e114b14 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -21,6 +21,7 @@ import TextForEvent from './TextForEvent'; import Avatar from './Avatar'; import dis from './dispatcher'; import sdk from './index'; +import _t from 'counterpart-riot'; import Modal from './Modal'; /* @@ -134,14 +135,13 @@ const Notifier = { if (result !== 'granted') { // The permission request was dismissed or denied const description = result === 'denied' - ? 'Riot does not have permission to send you notifications' - + ' - please check your browser settings' - : 'Riot was not given permission to send notifications' - + ' - please try again'; + ? _t('Riot does not have permission to send you notifications - please check your browser settings') + : _t('Riot was not given permission to send notifications - please try again'); const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); Modal.createDialog(ErrorDialog, { - title: 'Unable to enable Notifications', + title: _t('Unable to enable Notifications'), description, + button: _t("OK"), }); return; } diff --git a/src/PasswordReset.js b/src/PasswordReset.js index a03a565459..d5992e3e0a 100644 --- a/src/PasswordReset.js +++ b/src/PasswordReset.js @@ -15,6 +15,7 @@ limitations under the License. */ var Matrix = require("matrix-js-sdk"); +import _t from 'counterpart-riot'; /** * Allows a user to reset their password on a homeserver. @@ -53,7 +54,7 @@ class PasswordReset { return res; }, function(err) { if (err.errcode == 'M_THREEPID_NOT_FOUND') { - err.message = "This email address was not found"; + err.message = _t('This email address was not found'); } else if (err.httpStatus) { err.message = err.message + ` (Status ${err.httpStatus})`; } @@ -78,10 +79,10 @@ class PasswordReset { } }, this.password).catch(function(err) { if (err.httpStatus === 401) { - err.message = "Failed to verify email address: make sure you clicked the link in the email"; + err.message = _t('Failed to verify email address: make sure you clicked the link in the email'); } else if (err.httpStatus === 404) { - err.message = "Your email address does not appear to be associated with a Matrix ID on this Homeserver."; + err.message = _t('Your email address does not appear to be associated with a Matrix ID on this Homeserver') + '.'; } else if (err.httpStatus) { err.message += ` (Status ${err.httpStatus})`; diff --git a/src/Roles.js b/src/Roles.js index cef8670aad..0c7463c48a 100644 --- a/src/Roles.js +++ b/src/Roles.js @@ -13,14 +13,19 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -export const LEVEL_ROLE_MAP = { - undefined: 'Default', - 0: 'User', - 50: 'Moderator', - 100: 'Admin', -}; +import _t from 'counterpart-riot'; + +export function levelRoleMap() { + const LEVEL_ROLE_MAP = {}; + LEVEL_ROLE_MAP[undefined] = _t('Default'); + LEVEL_ROLE_MAP[0] = _t('User'); + LEVEL_ROLE_MAP[50] = _t('Moderator'); + LEVEL_ROLE_MAP[100] = _t('Admin'); + return LEVEL_ROLE_MAP; +} export function textualPowerLevel(level, userDefault) { + const LEVEL_ROLE_MAP = this.levelRoleMap(); if (LEVEL_ROLE_MAP[level]) { return LEVEL_ROLE_MAP[level] + (level !== undefined ? ` (${level})` : ` (${userDefault})`); } else { diff --git a/src/ScalarMessaging.js b/src/ScalarMessaging.js index dbb7e405df..abe39056e7 100644 --- a/src/ScalarMessaging.js +++ b/src/ScalarMessaging.js @@ -125,6 +125,7 @@ const SdkConfig = require('./SdkConfig'); const MatrixClientPeg = require("./MatrixClientPeg"); const MatrixEvent = require("matrix-js-sdk").MatrixEvent; const dis = require("./dispatcher"); +import _t from 'counterpart-riot'; function sendResponse(event, res) { const data = JSON.parse(JSON.stringify(event.data)); @@ -150,7 +151,7 @@ function inviteUser(event, roomId, userId) { console.log(`Received request to invite ${userId} into room ${roomId}`); const client = MatrixClientPeg.get(); if (!client) { - sendError(event, "You need to be logged in."); + sendError(event, _t('You need to be logged in') + '.'); return; } const room = client.getRoom(roomId); @@ -170,7 +171,7 @@ function inviteUser(event, roomId, userId) { success: true, }); }, function(err) { - sendError(event, "You need to be able to invite users to do that.", err); + sendError(event, _t('You need to be able to invite users to do that') + '.', err); }); } @@ -181,7 +182,7 @@ function setPlumbingState(event, roomId, status) { console.log(`Received request to set plumbing state to status "${status}" in room ${roomId}`); const client = MatrixClientPeg.get(); if (!client) { - sendError(event, "You need to be logged in."); + sendError(event, _t('You need to be logged in') + '.'); return; } client.sendStateEvent(roomId, "m.room.plumbing", { status : status }).done(() => { @@ -189,7 +190,7 @@ function setPlumbingState(event, roomId, status) { success: true, }); }, (err) => { - sendError(event, err.message ? err.message : "Failed to send request.", err); + sendError(event, err.message ? err.message : _t('Failed to send request') + '.', err); }); } @@ -197,7 +198,7 @@ function setBotOptions(event, roomId, userId) { console.log(`Received request to set options for bot ${userId} in room ${roomId}`); const client = MatrixClientPeg.get(); if (!client) { - sendError(event, "You need to be logged in."); + sendError(event, _t('You need to be logged in') + '.'); return; } client.sendStateEvent(roomId, "m.room.bot.options", event.data.content, "_" + userId).done(() => { @@ -205,20 +206,20 @@ function setBotOptions(event, roomId, userId) { success: true, }); }, (err) => { - sendError(event, err.message ? err.message : "Failed to send request.", err); + sendError(event, err.message ? err.message : _t('Failed to send request') + '.', err); }); } function setBotPower(event, roomId, userId, level) { if (!(Number.isInteger(level) && level >= 0)) { - sendError(event, "Power level must be positive integer."); + sendError(event, _t('Power level must be positive integer') + '.'); return; } console.log(`Received request to set power level to ${level} for bot ${userId} in room ${roomId}.`); const client = MatrixClientPeg.get(); if (!client) { - sendError(event, "You need to be logged in."); + sendError(event, _t('You need to be logged in') + '.'); return; } @@ -235,7 +236,7 @@ function setBotPower(event, roomId, userId, level) { success: true, }); }, (err) => { - sendError(event, err.message ? err.message : "Failed to send request.", err); + sendError(event, err.message ? err.message : _t('Failed to send request') + '.', err); }); }); } @@ -258,12 +259,12 @@ function botOptions(event, roomId, userId) { function returnStateEvent(event, roomId, eventType, stateKey) { const client = MatrixClientPeg.get(); if (!client) { - sendError(event, "You need to be logged in."); + sendError(event, _t('You need to be logged in') + '.'); return; } const room = client.getRoom(roomId); if (!room) { - sendError(event, "This room is not recognised."); + sendError(event, _t('This room is not recognised') + '.'); return; } const stateEvent = room.currentState.getStateEvents(eventType, stateKey); @@ -313,13 +314,13 @@ const onMessage = function(event) { const roomId = event.data.room_id; const userId = event.data.user_id; if (!roomId) { - sendError(event, "Missing room_id in request"); + sendError(event, _t('Missing room_id in request')); return; } let promise = Promise.resolve(currentRoomId); if (!currentRoomId) { if (!currentRoomAlias) { - sendError(event, "Must be viewing a room"); + sendError(event, _t('Must be viewing a room')); return; } // no room ID but there is an alias, look it up. @@ -331,7 +332,7 @@ const onMessage = function(event) { promise.then((viewingRoomId) => { if (roomId !== viewingRoomId) { - sendError(event, "Room " + roomId + " not visible"); + sendError(event, _t('Room %(roomId)s not visible', {roomId: roomId})); return; } @@ -345,7 +346,7 @@ const onMessage = function(event) { } if (!userId) { - sendError(event, "Missing user_id in request"); + sendError(event, _t('Missing user_id in request')); return; } switch (event.data.action) { @@ -370,7 +371,7 @@ const onMessage = function(event) { } }, (err) => { console.error(err); - sendError(event, "Failed to lookup current room."); + sendError(event, _t('Failed to lookup current room') + '.'); }); }; diff --git a/src/SlashCommands.js b/src/SlashCommands.js index bd68f1a6fe..e2c69ad46a 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -18,6 +18,7 @@ import MatrixClientPeg from "./MatrixClientPeg"; import dis from "./dispatcher"; import Tinter from "./Tinter"; import sdk from './index'; +import _t from 'counterpart-riot'; import Modal from './Modal'; @@ -41,7 +42,7 @@ class Command { } getUsage() { - return "Usage: " + this.getCommandWithArgs(); + return _t('Usage') + ': ' + this.getCommandWithArgs(); } } @@ -68,8 +69,9 @@ const commands = { const ErrorDialog = sdk.getComponent('dialogs.ErrorDialog'); // TODO Don't explain this away, actually show a search UI here. Modal.createDialog(ErrorDialog, { - title: "/ddg is not a command", - description: "To use it, just wait for autocomplete results to load and tab through them.", + title: _t('/ddg is not a command'), + description: _t('To use it, just wait for autocomplete results to load and tab through them') + '.', + button: _t("OK"), }); return success(); }), diff --git a/src/TextForEvent.js b/src/TextForEvent.js index 3f200a089d..25af1e5b5d 100644 --- a/src/TextForEvent.js +++ b/src/TextForEvent.js @@ -16,7 +16,7 @@ limitations under the License. var MatrixClientPeg = require("./MatrixClientPeg"); var CallHandler = require("./CallHandler"); - +import _t from 'counterpart-riot'; import * as Roles from './Roles'; function textForMemberEvent(ev) { @@ -25,45 +25,42 @@ function textForMemberEvent(ev) { var targetName = ev.target ? ev.target.name : ev.getStateKey(); var ConferenceHandler = CallHandler.getConferenceHandler(); var reason = ev.getContent().reason ? ( - " Reason: " + ev.getContent().reason + _t('Reason') + ': ' + ev.getContent().reason ) : ""; switch (ev.getContent().membership) { case 'invite': var threePidContent = ev.getContent().third_party_invite; if (threePidContent) { if (threePidContent.display_name) { - return targetName + " accepted the invitation for " + - threePidContent.display_name + "."; + return _t('%(targetName)s accepted the invitation for %(displayName)s', {targetName: targetName, displayName: threePidContent.display_name}) + "."; } else { - return targetName + " accepted an invitation."; + return _t('%(targetName)s accepted an invitation', {targetName: targetName}) + '.'; } } else { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { - return senderName + " requested a VoIP conference"; + return _t('%(senderName)s requested a VoIP conference', {senderName: senderName}); } else { - return senderName + " invited " + targetName + "."; + return _t('%(senderName)s invited %(targetName)s', {senderName: senderName, targetName: targetName}) + '.'; } } case 'ban': - return senderName + " banned " + targetName + "." + reason; + return _t('%(senderName)s banned %(targetName)s', {senderName: senderName, targetName: targetName}) + '. ' + reason; case 'join': if (ev.getPrevContent() && ev.getPrevContent().membership == 'join') { if (ev.getPrevContent().displayname && ev.getContent().displayname && ev.getPrevContent().displayname != ev.getContent().displayname) { - return ev.getSender() + " changed their display name from " + - ev.getPrevContent().displayname + " to " + - ev.getContent().displayname; + return _t('%(senderName)s changed their display name from %(oldDisplayName)s to %(displayName)s', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname, displayName: ev.getContent().displayname}); } else if (!ev.getPrevContent().displayname && ev.getContent().displayname) { - return ev.getSender() + " set their display name to " + ev.getContent().displayname; + return _t('%(senderName)s set their display name to %(displayName)s', {senderName: ev.getSender(), displayName: ev.getContent().displayname}); } else if (ev.getPrevContent().displayname && !ev.getContent().displayname) { - return ev.getSender() + " removed their display name (" + ev.getPrevContent().displayname + ")"; + return _t('%(senderName)s removed their display name (%(oldDisplayName)s)', {senderName: ev.getSender(), oldDisplayName: ev.getPrevContent().displayname}); } else if (ev.getPrevContent().avatar_url && !ev.getContent().avatar_url) { - return senderName + " removed their profile picture"; + return _t('%(senderName)s removed their profile picture', {senderName: senderName}); } else if (ev.getPrevContent().avatar_url && ev.getContent().avatar_url && ev.getPrevContent().avatar_url != ev.getContent().avatar_url) { - return senderName + " changed their profile picture"; + return _t('%(senderName)s changed their profile picture', {senderName: senderName}); } else if (!ev.getPrevContent().avatar_url && ev.getContent().avatar_url) { - return senderName + " set a profile picture"; + return _t('%(senderName)s set a profile picture', {senderName: senderName}); } else { // suppress null rejoins return ''; @@ -71,49 +68,48 @@ function textForMemberEvent(ev) { } else { if (!ev.target) console.warn("Join message has no target! -- " + ev.getContent().state_key); if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { - return "VoIP conference started"; + return _t('VoIP conference started'); } else { - return targetName + " joined the room."; + return _t('%(targetName)s joined the room', {targetName: targetName}) + '.'; } } case 'leave': if (ev.getSender() === ev.getStateKey()) { if (ConferenceHandler && ConferenceHandler.isConferenceUser(ev.getStateKey())) { - return "VoIP conference finished"; + return _t('VoIP conference finished'); } else if (ev.getPrevContent().membership === "invite") { - return targetName + " rejected the invitation."; + return _t('%(targetName)s rejected the invitation', {targetName: targetName}) + '.'; } else { - return targetName + " left the room."; + return _t('%(targetName)s left the room', {targetName: targetName}) + '.'; } } else if (ev.getPrevContent().membership === "ban") { - return senderName + " unbanned " + targetName + "."; + return _t('%(senderName)s unbanned %(targetName)s', {senderName: senderName, targetName: targetName}) + '.'; } else if (ev.getPrevContent().membership === "join") { - return senderName + " kicked " + targetName + "." + reason; + return _t('%(senderName)s kicked %(targetName)s', {senderName: senderName, targetName: targetName}) + '. ' + reason; } else if (ev.getPrevContent().membership === "invite") { - return senderName + " withdrew " + targetName + "'s invitation." + reason; + return _t('%(senderName)s withdrew %(targetName)s\'s inivitation', {senderName: senderName, targetName: targetName}) + '. ' + reason; } else { - return targetName + " left the room."; + return _t('%(targetName)s left the room', {targetName: targetName}) + '.'; } } } function textForTopicEvent(ev) { var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - - return senderDisplayName + ' changed the topic to "' + ev.getContent().topic + '"'; + return _t('%(senderDisplayName)s changed the topic to "%(topic)s"', {senderDisplayName: senderDisplayName, topic: ev.getContent().topic}); } function textForRoomNameEvent(ev) { var senderDisplayName = ev.sender && ev.sender.name ? ev.sender.name : ev.getSender(); - return senderDisplayName + ' changed the room name to "' + ev.getContent().name + '"'; + return _t('%(senderDisplayName)s changed the room name to %(roomName)s', {senderDisplayName: senderDisplayName, roomName: ev.getContent().name}); } function textForMessageEvent(ev) { @@ -122,66 +118,65 @@ function textForMessageEvent(ev) { if (ev.getContent().msgtype === "m.emote") { message = "* " + senderDisplayName + " " + message; } else if (ev.getContent().msgtype === "m.image") { - message = senderDisplayName + " sent an image."; + message = _t('%(senderDisplayName)s sent an image', {senderDisplayName: senderDisplayName}) + '.'; } return message; } function textForCallAnswerEvent(event) { - var senderName = event.sender ? event.sender.name : "Someone"; - var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)"; - return senderName + " answered the call." + supported; + var senderName = event.sender ? event.sender.name : _t('Someone'); + var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)'); + return _t('%(senderName)s answered the call', {senderName: senderName}) + '. ' + supported; } function textForCallHangupEvent(event) { - var senderName = event.sender ? event.sender.name : "Someone"; - var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)"; - return senderName + " ended the call." + supported; + var senderName = event.sender ? event.sender.name : _t('Someone'); + var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)'); + return _t('%(senderName)s ended the call', {senderName: senderName}) + '. ' + supported; } function textForCallInviteEvent(event) { - var senderName = event.sender ? event.sender.name : "Someone"; + var senderName = event.sender ? event.sender.name : _t('Someone'); // FIXME: Find a better way to determine this from the event? var type = "voice"; if (event.getContent().offer && event.getContent().offer.sdp && event.getContent().offer.sdp.indexOf('m=video') !== -1) { type = "video"; } - var supported = MatrixClientPeg.get().supportsVoip() ? "" : " (not supported by this browser)"; - return senderName + " placed a " + type + " call." + supported; + var supported = MatrixClientPeg.get().supportsVoip() ? "" : _t('(not supported by this browser)'); + return _t('%(senderName)s placed a %(callType)s call', {senderName: senderName, callType: type}) + '. ' + supported; } function textForThreePidInviteEvent(event) { var senderName = event.sender ? event.sender.name : event.getSender(); - return senderName + " sent an invitation to " + event.getContent().display_name + - " to join the room."; + return _t('%(senderName)s sent an invitation to %(targetDisplayName)s to join the room', {senderName: senderName, targetDisplayName: event.getContent().display_name}) + "."; } function textForHistoryVisibilityEvent(event) { var senderName = event.sender ? event.sender.name : event.getSender(); var vis = event.getContent().history_visibility; - var text = senderName + " made future room history visible to "; + var text = _t('%(senderName)s made future room history visible to', {senderName: senderName}) + ' '; if (vis === "invited") { - text += "all room members, from the point they are invited."; + text += _t('all room members, from the point they are invited') + '.'; } else if (vis === "joined") { - text += "all room members, from the point they joined."; + text += _t('all room members, from the point they joined') + '.'; } else if (vis === "shared") { - text += "all room members."; + text += _t('all room members') + '.'; } else if (vis === "world_readable") { - text += "anyone."; + text += _t('anyone') + '.'; } else { - text += " unknown (" + vis + ")"; + text += ' ' + _t('unknown') + ' (' + vis + ')'; } return text; } function textForEncryptionEvent(event) { var senderName = event.sender ? event.sender.name : event.getSender(); - return senderName + " turned on end-to-end encryption (algorithm " + event.getContent().algorithm + ")"; + return _t('%(senderName)s turned on end-to-end encryption (algorithm %(algorithm)s)', {senderName: senderName, algorithm: algorithm}); } // Currently will only display a change if a user's power level is changed @@ -211,16 +206,14 @@ function textForPowerEvent(event) { const to = event.getContent().users[userId]; if (to !== from) { diff.push( - userId + - ' from ' + Roles.textualPowerLevel(from, userDefault) + - ' to ' + Roles.textualPowerLevel(to, userDefault) + _t('%(userId)s from %(fromPowerLevel)s to %(toPowerLevel)s', {userId: userId, fromPowerLevel: Roles.textualPowerLevel(from, userDefault), toPowerLevel: Roles.textualPowerLevel(to, userDefault)}) ); } }); if (!diff.length) { return ''; } - return senderName + ' changed the power level of ' + diff.join(', '); + return _t('%(senderName)s changed the power level of %(powerLevelDiffText)s', {senderName: senderName, powerLevelDiffText: diff.join(", ")}); } var handlers = { diff --git a/src/UserSettingsStore.js b/src/UserSettingsStore.js index 9de291249f..fd5ccb0de3 100644 --- a/src/UserSettingsStore.js +++ b/src/UserSettingsStore.js @@ -23,10 +23,14 @@ import Notifier from './Notifier'; * TODO: Make things use this. This is all WIP - see UserSettings.js for usage. */ +/* + * TODO: Find a way to translate the names of LABS_FEATURES. In other words, guarantee that languages were already loaded before building this array. + */ + module.exports = { LABS_FEATURES: [ { - name: 'New Composer & Autocomplete', + name: "New Composer & Autocomplete", id: 'rich_text_editor', default: false, }, diff --git a/src/WhoIsTyping.js b/src/WhoIsTyping.js index 4502b0ccd9..b2eef42f5a 100644 --- a/src/WhoIsTyping.js +++ b/src/WhoIsTyping.js @@ -15,6 +15,7 @@ limitations under the License. */ var MatrixClientPeg = require("./MatrixClientPeg"); +import _t from 'counterpart-riot'; module.exports = { usersTypingApartFromMe: function(room) { @@ -56,18 +57,18 @@ module.exports = { if (whoIsTyping.length == 0) { return ''; } else if (whoIsTyping.length == 1) { - return whoIsTyping[0].name + ' is typing'; + return _t('%(displayName)s is typing', {displayName: whoIsTyping[0].name}); } const names = whoIsTyping.map(function(m) { return m.name; }); - if (othersCount) { - const other = ' other' + (othersCount > 1 ? 's' : ''); - return names.slice(0, limit - 1).join(', ') + ' and ' + - othersCount + other + ' are typing'; + if (othersCount==1) { + return _t('%(names)s and one other are typing', {names: names.slice(0, limit - 1).join(', ')}); + } else if (othersCount>1) { + return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount}); } else { const lastPerson = names.pop(); - return names.join(', ') + ' and ' + lastPerson + ' are typing'; + return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson}); } } }; diff --git a/src/async-components/views/dialogs/EncryptedEventDialog.js b/src/async-components/views/dialogs/EncryptedEventDialog.js index ba706e0aa5..279f6bbda1 100644 --- a/src/async-components/views/dialogs/EncryptedEventDialog.js +++ b/src/async-components/views/dialogs/EncryptedEventDialog.js @@ -15,6 +15,7 @@ limitations under the License. */ var React = require("react"); +import _t from 'counterpart-riot'; var sdk = require('../../../index'); var MatrixClientPeg = require("../../../MatrixClientPeg"); @@ -78,33 +79,33 @@ module.exports = React.createClass({ _renderDeviceInfo: function() { var device = this.state.device; if (!device) { - return (unknown device); + return ({ _t('unknown device') }); } - var verificationStatus = (NOT verified); + var verificationStatus = ({ _t('NOT verified') }); if (device.isBlocked()) { - verificationStatus = (Blacklisted); + verificationStatus = ({ _t('Blacklisted') }); } else if (device.isVerified()) { - verificationStatus = "verified"; + verificationStatus = _t('verified'); } return ( - + - + - + - + @@ -119,32 +120,32 @@ module.exports = React.createClass({
Name{ _t('Name') } { device.getDisplayName() }
Device ID{ _t('Device ID') } { device.deviceId }
Verification{ _t('Verification') } { verificationStatus }
Ed25519 fingerprint{ _t('Ed25519 fingerprint') } {device.getFingerprint()}
- + - - + + - - + + - - + + { event.getContent().msgtype === 'm.bad.encrypted' ? ( - + ) : null } - - + +
User ID{ _t('User ID') } { event.getSender() }
Curve25519 identity key{ event.getSenderKey() || none }{ _t('Curve25519 identity key') }{ event.getSenderKey() || { _t('none') } }
Claimed Ed25519 fingerprint key{ event.getKeysClaimed().ed25519 || none }{ _t('Claimed Ed25519 fingerprint key') }{ event.getKeysClaimed().ed25519 || { _t('none') } }
Algorithm{ event.getWireContent().algorithm || unencrypted }{ _t('Algorithm') }{ event.getWireContent().algorithm || { _t('unencrypted') } }
Decryption error{ _t('Decryption error') } { event.getContent().body }
Session ID{ event.getWireContent().session_id || none }{ _t('Session ID') }{ event.getWireContent().session_id || { _t('none') } }
@@ -166,18 +167,18 @@ module.exports = React.createClass({ return (
- End-to-end encryption information + { _t('End-to-end encryption information') }
-

Event information

+

{ _t('Event information') }

{this._renderEventInfo()} -

Sender device information

+

{ _t('Sender device information') }

{this._renderDeviceInfo()}
{buttons}
diff --git a/src/autocomplete/CommandProvider.js b/src/autocomplete/CommandProvider.js index 60171bc72f..d72c8d1c07 100644 --- a/src/autocomplete/CommandProvider.js +++ b/src/autocomplete/CommandProvider.js @@ -1,8 +1,10 @@ import React from 'react'; +import _t from 'counterpart-riot'; import AutocompleteProvider from './AutocompleteProvider'; import Fuse from 'fuse.js'; import {TextualCompletion} from './Components'; +// Warning: Since the description string will be translated in _t(result.description), all these strings below must be in i18n/strings/en_EN.json file const COMMANDS = [ { command: '/me', @@ -68,7 +70,7 @@ export default class CommandProvider extends AutocompleteProvider { component: (), range, }; @@ -78,7 +80,7 @@ export default class CommandProvider extends AutocompleteProvider { } getName() { - return '*️⃣ Commands'; + return '*️⃣ ' + _t('Commands'); } static getInstance(): CommandProvider { diff --git a/src/autocomplete/EmojiProvider.js b/src/autocomplete/EmojiProvider.js index d488ac53ae..d0675e9ffc 100644 --- a/src/autocomplete/EmojiProvider.js +++ b/src/autocomplete/EmojiProvider.js @@ -1,4 +1,5 @@ import React from 'react'; +import _t from 'counterpart-riot'; import AutocompleteProvider from './AutocompleteProvider'; import {emojioneList, shortnameToImage, shortnameToUnicode} from 'emojione'; import Fuse from 'fuse.js'; @@ -39,7 +40,7 @@ export default class EmojiProvider extends AutocompleteProvider { } getName() { - return '😃 Emoji'; + return '😃 ' + _t('Emoji'); } static getInstance() { diff --git a/src/autocomplete/RoomProvider.js b/src/autocomplete/RoomProvider.js index 8d1e555e56..175aaf2691 100644 --- a/src/autocomplete/RoomProvider.js +++ b/src/autocomplete/RoomProvider.js @@ -1,4 +1,5 @@ import React from 'react'; +import _t from 'counterpart-riot'; import AutocompleteProvider from './AutocompleteProvider'; import MatrixClientPeg from '../MatrixClientPeg'; import Fuse from 'fuse.js'; @@ -50,7 +51,7 @@ export default class RoomProvider extends AutocompleteProvider { } getName() { - return '💬 Rooms'; + return '💬 ' + _t('Rooms'); } static getInstance() { diff --git a/src/autocomplete/UserProvider.js b/src/autocomplete/UserProvider.js index 4d40fbdf94..c2fccbc418 100644 --- a/src/autocomplete/UserProvider.js +++ b/src/autocomplete/UserProvider.js @@ -1,4 +1,5 @@ import React from 'react'; +import _t from 'counterpart-riot'; import AutocompleteProvider from './AutocompleteProvider'; import Q from 'q'; import Fuse from 'fuse.js'; @@ -51,7 +52,7 @@ export default class UserProvider extends AutocompleteProvider { } getName() { - return '👥 Users'; + return '👥 ' + _t('Users'); } setUserList(users) { diff --git a/src/components/structures/CreateRoom.js b/src/components/structures/CreateRoom.js index 24ebfea07f..48ce4e274e 100644 --- a/src/components/structures/CreateRoom.js +++ b/src/components/structures/CreateRoom.js @@ -16,15 +16,16 @@ limitations under the License. 'use strict'; -var React = require("react"); -var MatrixClientPeg = require("../../MatrixClientPeg"); -var PresetValues = { +import React from 'react'; +import q from 'q'; +import _t from 'counterpart-riot'; +import sdk from '../../index'; +import MatrixClientPeg from '../../MatrixClientPeg'; +const PresetValues = { PrivateChat: "private_chat", PublicChat: "public_chat", Custom: "custom", }; -var q = require('q'); -var sdk = require('../../index'); module.exports = React.createClass({ displayName: 'CreateRoom', @@ -231,7 +232,7 @@ module.exports = React.createClass({ if (curr_phase == this.phases.ERROR) { error_box = (
- An error occured: {this.state.error_string} + {_t('An error occured: %(error_string)s', {error_string: this.state.error_string})}
); } @@ -248,27 +249,27 @@ module.exports = React.createClass({
-
-