diff --git a/conference.js b/conference.js index 620287dc5..3d5c94f2b 100644 --- a/conference.js +++ b/conference.js @@ -195,11 +195,7 @@ function maybeRedirectToWelcomePage(showThankYou) { if (showThankYou) { APP.UI.messageHandler.openMessageDialog( - null, null, null, - APP.translation.translateString( - "dialog.thankYou", {appName:interfaceConfig.APP_NAME} - ) - ); + null, "dialog.thankYou", {appName:interfaceConfig.APP_NAME}); } if (!config.enableWelcomePage) { @@ -1045,21 +1041,20 @@ export default { // TrackErrors.GENERAL // and any other let dialogTxt; - let dialogTitle; + let dialogTitleKey; if (err.name === TrackErrors.PERMISSION_DENIED) { dialogTxt = APP.translation.generateTranslationHTML( "dialog.screenSharingPermissionDeniedError"); - dialogTitle = APP.translation.generateTranslationHTML( - "dialog.error"); + dialogTitleKey = "dialog.error"; } else { dialogTxt = APP.translation.generateTranslationHTML( "dialog.failtoinstall"); - dialogTitle = APP.translation.generateTranslationHTML( - "dialog.permissionDenied"); + dialogTitleKey = "dialog.permissionDenied"; } - APP.UI.messageHandler.openDialog(dialogTitle, dialogTxt, false); + APP.UI.messageHandler.openDialog( + dialogTitleKey, dialogTxt, false); }); } else { createLocalTracks({ devices: ['video'] }).then( diff --git a/index.html b/index.html index db8160e88..3426833d3 100644 --- a/index.html +++ b/index.html @@ -105,7 +105,7 @@
- + @@ -138,11 +138,11 @@
- +
- +

@@ -175,7 +175,7 @@
-
+
diff --git a/lang/main.json b/lang/main.json index 86de9cc2d..95f1b6e1b 100644 --- a/lang/main.json +++ b/lang/main.json @@ -1,5 +1,5 @@ { - "contactlist": "Participants", + "contactlist": "Participants (__pcount__)", "addParticipants": "Add Participants", "roomLocked": "Callers must enter a password", "roomUnlocked": "Anyone with the link can join", @@ -99,6 +99,7 @@ "cameraDisabled": "Camera is not available", "micDisabled": "Microphone is not available", "filmstrip": "Show / hide videos", + "profile": "Edit your profile", "raiseHand": "Raise hand to speak" }, "bottomtoolbar": { @@ -135,7 +136,8 @@ "profile": { "title": "Profile", "setDisplayNameLabel": "Set your display name", - "setEmailLabel": "Set your gravatar email" + "setEmailLabel": "Set your gravatar email", + "setEmailInput": "Enter e-mail" }, "videothumbnail": { @@ -342,7 +344,7 @@ "ATTACHED": "Attached", "FETCH_SESSION_ID": "Obtaining session-id...", "GOT_SESSION_ID": "Obtaining session-id... Done", - "GET_SESSION_ID_ERROR": "Get session-id error: ", + "GET_SESSION_ID_ERROR": "Get session-id error: __code__", "USER_CONNECTION_INTERRUPTED": "__displayName__ is having connectivity issues..." }, "recording": diff --git a/lang/readme.md b/lang/readme.md index 2911c3412..e9b8ca244 100644 --- a/lang/readme.md +++ b/lang/readme.md @@ -1,20 +1,20 @@ Jitsi Meet Translation ========================== -Jitsi Meet uses [i18next](http://i18next.com) library for translation. -i18next uses separate json files for each language. +Jitsi Meet uses [i18next](http://i18next.com) library for translation. +i18next uses separate json files for each language. Translating Jitsi Meet ====================== -The translation of Jitsi Meet is integrated with Pootle. You can translate Jitsi Meet via our Pootle user interface on -[http://translate.jitsi.org](http://translate.jitsi.org). +The translation of Jitsi Meet is integrated with Pootle. You can translate Jitsi Meet via our Pootle user interface on +[http://translate.jitsi.org](http://translate.jitsi.org). **WARNING: Please don't create or edit manually the language files! Please use our Pootle user interface!** Development =========== If you want to add new functionality for Jitsi Meet and you have texts that need to be translated please use our translation module. -It is located in modules/translation. You must add key and value in main.json file in English for each translatable text. +It is located in modules/translation. You must add key and value in main.json file in English for each translatable text. Than you can use the key to get the translated text for the current language. **WARNING: Please don't change the other language files except main.json! They must be updated and translated via our Pootle user interface!** @@ -36,21 +36,19 @@ You can add translatable text in the HTML: ``` APP.translation.generateTranslationHTML("dialog.OK") // returns OK ``` - + The value in the options parameter will be added in data-i18n-options attribute of the element. - + **Note:** If you dynamically add HTML elements don't forget to call APP.translation.translateElement(jquery_selector) to translate the text initially. -* **via Javascript string** - call APP.translation.translateString(key, options). You can use that method to get the translated string in Javascript and then attach it in the HTML. - + ``` - APP.translation.translateString("dialog.OK") // returns the value for the key of the current language file. "OK" for example. + APP.translation.translateString("dialog.OK") // returns the value for the key of the current language file. "OK" for example. ``` -For the available values of ``options`` parameter for the above methods of translation module see [i18next documentation](http://i18next.com/pages/doc_features). +For the available values of ``options`` parameter for the above methods of translation module see [i18next documentation](http://i18next.com/pages/doc_features). **Note:** It is useful to add attributes in the HTML for persistent HTML elements because when the language is changed the text will be automatically translated. - Otherwise you should call ``APP.translation.translateString`` and manually change the text every time the language is changed. diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 64690b411..74917a5ef 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -75,17 +75,13 @@ JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NO_DATA_FROM_SOURCE] */ function promptDisplayName() { let labelKey = 'dialog.enterDisplayName'; - let labelStr = APP.translation.translateString(labelKey); - let titleStr - = APP.translation.translateString('dialog.displayNameRequired'); - let defaultNickMsg = APP.translation.translateString("defaultNickname"); let message = ( `
- + + autofocus>
` ); @@ -94,7 +90,7 @@ function promptDisplayName() { let buttons = {Ok:true}; let dialog = messageHandler.openDialog( - titleStr, + 'dialog.displayNameRequired', message, true, buttons, @@ -167,11 +163,10 @@ UI.notifyGracefulShutdown = function () { * Notify user that reservation error happened. */ UI.notifyReservationError = function (code, msg) { - var title = APP.translation.generateTranslationHTML( - "dialog.reservationError"); var message = APP.translation.generateTranslationHTML( "dialog.reservationErrorMsg", {code: code, msg: msg}); - messageHandler.openDialog(title, message, true, {}, () => false); + messageHandler.openDialog( + "dialog.reservationError", message, true, {}, () => false); }; /** @@ -190,9 +185,8 @@ UI.notifyKicked = function () { UI.notifyConferenceDestroyed = function (reason) { //FIXME: use Session Terminated from translation, but // 'reason' text comes from XMPP packet and is not translated - const title - = APP.translation.generateTranslationHTML("dialog.sessTerminated"); - messageHandler.openDialog(title, reason, true, {}, () => false); + messageHandler.openDialog( + "dialog.sessTerminated", reason, true, {}, () => false); }; /** @@ -750,8 +744,6 @@ UI.connectionIndicatorShowMore = function(id) { // FIXME check if someone user this UI.showLoginPopup = function(callback) { console.log('password is required'); - let titleKey = "dialog.passwordRequired"; - let titleString = APP.translation.translateString(titleKey); let message = ( ` false); + messageHandler.openDialog("dialog.error", message, true, {}, () => false); }; @@ -924,13 +912,10 @@ UI.notifyConnectionFailed = function (stropheErrorMsg) { * Notify user that maximum users limit has been reached. */ UI.notifyMaxUsersLimitReached = function () { - var title = APP.translation.generateTranslationHTML( - "dialog.error"); - var message = APP.translation.generateTranslationHTML( "dialog.maxUsersLimitReached"); - messageHandler.openDialog(title, message, true, {}, () => false); + messageHandler.openDialog("dialog.error", message, true, {}, () => false); }; /** @@ -1046,7 +1031,7 @@ UI.updateDTMFSupport = function (isDTMFSupported) { * @returns {Promise} Resolved with value - false if the dialog is enabled and * resolved with true if the dialog is disabled or the feedback was already * submitted. Rejected if another dialog is already displayed. This values are - * used to display or not display the thank you dialog from + * used to display or not display the thank you dialog from * conference.maybeRedirectToWelcomePage method. */ UI.requestFeedbackOnHangup = function () { @@ -1191,10 +1176,8 @@ UI.getLargeVideo = function () { UI.showExtensionRequiredDialog = function (url) { messageHandler.openMessageDialog( "dialog.extensionRequired", - null, - null, - APP.translation.generateTranslationHTML( - "dialog.firefoxExtensionPrompt", {url: url})); + "dialog.firefoxExtensionPrompt", + {url: url}); }; /** @@ -1216,9 +1199,7 @@ UI.showExtensionExternalInstallationDialog = function (url) { messageHandler.openTwoButtonDialog({ titleKey: 'dialog.externalInstallationTitle', - titleString: '', msgKey: 'dialog.externalInstallationMsg', - msgString: '', leftButtonKey: 'dialog.goToStore', submitFunction, loadedFunction: $.noop, @@ -1263,8 +1244,6 @@ UI.showDeviceErrorDialog = function (micError, cameraError) { } } - let title = getTitleKey(); - let titleMsg = ``; let cameraJitsiTrackErrorMsg = cameraError ? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[cameraError.name] : undefined; @@ -1318,7 +1297,7 @@ UI.showDeviceErrorDialog = function (micError, cameraError) { deviceErrorDialog && deviceErrorDialog.close(); deviceErrorDialog = messageHandler.openDialog( - titleMsg, + getTitleKey(), message, false, {Ok: true}, @@ -1342,8 +1321,6 @@ UI.showDeviceErrorDialog = function (micError, cameraError) { } ); - APP.translation.translateElement($(".jqibox")); - function getTitleKey() { let title = "dialog.error"; @@ -1369,9 +1346,7 @@ UI.showTrackNotWorkingDialog = function (stream) { messageHandler.openMessageDialog( "dialog.error", stream.isAudioTrack()? "dialog.micNotSendingData" : - "dialog.cameraNotSendingData", - null, - null); + "dialog.cameraNotSendingData"); }; UI.updateDevicesAvailability = function (id, devices) { @@ -1483,12 +1458,11 @@ UI.hideUserMediaPermissionsGuidanceOverlay = function () { */ UI.toggleKeyboardShortcutsPanel = function() { if (!messageHandler.isDialogOpened()) { - let titleKey = 'keyboardShortcuts.keyboardShortcuts'; - let title = APP.translation.translateString(titleKey); let msg = $('#keyboard-shortcuts').html(); let buttons = { Close: true }; - messageHandler.openDialog(title, msg, true, buttons); + messageHandler.openDialog( + 'keyboardShortcuts.keyboardShortcuts', msg, true, buttons); } else { messageHandler.closeDialog(); } diff --git a/modules/UI/authentication/AuthHandler.js b/modules/UI/authentication/AuthHandler.js index 8ce8246ba..ec224211c 100644 --- a/modules/UI/authentication/AuthHandler.js +++ b/modules/UI/authentication/AuthHandler.js @@ -146,16 +146,13 @@ function doXmppAuth (room, lockPassword) { room.getName(), APP.conference._getConferenceOptions() ); - loginDialog.displayConnectionStatus( - APP.translation.translateString('connection.FETCH_SESSION_ID') - ); + loginDialog.displayConnectionStatus('connection.FETCH_SESSION_ID'); newRoom.room.moderator.authenticate().then(function () { connection.disconnect(); loginDialog.displayConnectionStatus( - APP.translation.translateString('connection.GOT_SESSION_ID') - ); + 'connection.GOT_SESSION_ID'); // authenticate conference on the fly room.join(lockPassword); @@ -166,11 +163,8 @@ function doXmppAuth (room, lockPassword) { console.error('Auth on the fly failed', error); - let errorMsg = APP.translation.translateString( - 'connection.GET_SESSION_ID_ERROR' - ); - - loginDialog.displayError(errorMsg + code); + loginDialog.displayError( + 'connection.GET_SESSION_ID_ERROR', {code: code}); }); }, function (err) { loginDialog.displayError(err); diff --git a/modules/UI/authentication/LoginDialog.js b/modules/UI/authentication/LoginDialog.js index 6ee3606f3..8eb01d22f 100644 --- a/modules/UI/authentication/LoginDialog.js +++ b/modules/UI/authentication/LoginDialog.js @@ -1,4 +1,4 @@ -/* global APP, config */ +/* global $, APP, config */ /** * Build html for "password required" dialog. @@ -15,8 +15,7 @@ function getPasswordInputHtml() { placeholder=${placeholder} autofocus> `; + data-i18n="[placeholder]dialog.userPassword">`; } /** @@ -67,7 +66,7 @@ function LoginDialog(successCallback, cancelCallback) { value: true }]; let finishedButtons = [{ - title: APP.translation.translateString('dialog.retry'), + title: APP.translation.generateTranslationHTML('dialog.retry'), value: 'retry' }]; @@ -79,7 +78,7 @@ function LoginDialog(successCallback, cancelCallback) { const states = { login: { - title: APP.translation.translateString('dialog.passwordRequired'), + titleKey: 'dialog.passwordRequired', html: getPasswordInputHtml(), buttons: loginButtons, focus: ':input:first', @@ -99,13 +98,13 @@ function LoginDialog(successCallback, cancelCallback) { } }, connecting: { - title: APP.translation.translateString('dialog.connecting'), + titleKey: 'dialog.connecting', html: '
', buttons: [], defaultButton: 0 }, finished: { - title: APP.translation.translateString('dialog.error'), + titleKey: 'dialog.error', html: '
', buttons: finishedButtons, defaultButton: 0, @@ -128,27 +127,31 @@ function LoginDialog(successCallback, cancelCallback) { /** * Displays error message in 'finished' state which allows either to cancel * or retry. - * @param message the final message to be displayed. + * @param messageKey the key to the message to be displayed. + * @param options the options to the error message (optional) */ - this.displayError = function (message) { + this.displayError = function (messageKey, options) { let finishedState = connDialog.getState('finished'); let errorMessageElem = finishedState.find('#errorMessage'); - errorMessageElem.text(message); + errorMessageElem.attr("data-i18n", messageKey); + + APP.translation.translateElement($(errorMessageElem), options); connDialog.goToState('finished'); }; /** * Show message as connection status. - * @param {string} message + * @param {string} messageKey the key to the message */ - this.displayConnectionStatus = function (message) { + this.displayConnectionStatus = function (messageKey) { let connectingState = connDialog.getState('connecting'); let connectionStatus = connectingState.find('#connectionStatus'); - connectionStatus.text(message); + connectionStatus.attr("data-i18n", messageKey); + APP.translation.translateElement($(connectionStatus)); }; /** @@ -202,9 +205,6 @@ export default { * @returns dialog */ showAuthRequiredDialog: function (roomName, onAuthNow) { - var title = APP.translation.generateTranslationHTML( - "dialog.WaitingForHost" - ); var msg = APP.translation.generateTranslationHTML( "dialog.WaitForHostMsg", {room: roomName} ); @@ -215,7 +215,7 @@ export default { var buttons = [{title: buttonTxt, value: "authNow"}]; return APP.UI.messageHandler.openDialog( - title, + "dialog.WaitingForHost", msg, true, buttons, diff --git a/modules/UI/feedback/FeedbackWindow.js b/modules/UI/feedback/FeedbackWindow.js index 1a2a375f2..f6254557e 100644 --- a/modules/UI/feedback/FeedbackWindow.js +++ b/modules/UI/feedback/FeedbackWindow.js @@ -35,7 +35,6 @@ function toggleStars(starCount) { * @returns {string} the contructed html string */ function createRateFeedbackHTML() { - let feedbackHelp = APP.translation.translateString('dialog.feedbackHelp'); let starClassName = (interfaceConfig.ENABLE_FEEDBACK_ANIMATION) ? "icon-star-full shake-rotate" @@ -67,8 +66,8 @@ function createRateFeedbackHTML() {
- +
`; } @@ -147,10 +146,10 @@ export default class Dialog { this.submitted = false; this.onCloseCallback = function() {}; - this.setDefoulOptions(); + this.setDefaultOptions(); } - setDefoulOptions() { + setDefaultOptions() { var self = this; this.options = { diff --git a/modules/UI/invite/AskForPassword.js b/modules/UI/invite/AskForPassword.js index 47bc5b309..77a27ddf4 100644 --- a/modules/UI/invite/AskForPassword.js +++ b/modules/UI/invite/AskForPassword.js @@ -8,12 +8,10 @@ import UIUtil from '../util/UIUtil'; */ export default function askForPassword () { let titleKey = "dialog.passwordRequired"; - let passMsg = APP.translation.translateString("dialog.password"); let msgString = ` - `; + autofocus>`; return new Promise(function (resolve, reject) { APP.UI.messageHandler.openTwoButtonDialog({ titleKey, diff --git a/modules/UI/invite/Invite.js b/modules/UI/invite/Invite.js index df5774088..23ebd79ef 100644 --- a/modules/UI/invite/Invite.js +++ b/modules/UI/invite/Invite.js @@ -191,8 +191,10 @@ class Invite { * @param isLocked */ setLockedFromElsewhere(isLocked) { - let oldLockState = this.roomLocker.lockedElsewhere; - if (oldLockState !== isLocked) { + // isLocked can be 1, true or false + let newLockState = (isLocked === 1) || isLocked; + let oldLockState = this.roomLocker.isLocked; + if (oldLockState !== newLockState) { this.roomLocker.lockedElsewhere = isLocked; APP.UI.emitEvent(UIEvents.TOGGLE_ROOM_LOCK); this.updateView(); diff --git a/modules/UI/invite/InviteDialogView.js b/modules/UI/invite/InviteDialogView.js index 8a2a10eb5..95ee622b6 100644 --- a/modules/UI/invite/InviteDialogView.js +++ b/modules/UI/invite/InviteDialogView.js @@ -15,20 +15,15 @@ const States = { */ export default class InviteDialogView { constructor(model) { - let InviteAttributesKey = 'inviteUrlDefaultMsg'; - let title = APP.translation.translateString(InviteAttributesKey); - this.unlockHint = "unlockHint"; this.lockHint = "lockHint"; this.model = model; if (this.model.inviteUrl === null) { - this.inviteAttributes = ( - `data-i18n="[value]inviteUrlDefaultMsg" value="${title}"` - ); + this.inviteAttributes = `data-i18n="[value]inviteUrlDefaultMsg"`; } else { - let encodedInviteUrl = this.model.getEncodedInviteUrl(); - this.inviteAttributes = `value="${encodedInviteUrl}"`; + this.inviteAttributes + = `value="${this.model.getEncodedInviteUrl()}"`; } this.initDialog(); @@ -43,11 +38,7 @@ export default class InviteDialogView { dialog.submitFunction = this.submitFunction.bind(this); dialog.loadedFunction = this.loadedFunction.bind(this); - let titleKey = "dialog.shareLink"; - let titleString = APP.translation.generateTranslationHTML(titleKey); - - dialog.titleKey = titleKey; - dialog.titleString = titleString; + dialog.titleKey = "dialog.shareLink"; this.dialog = dialog; this.dialog.states = this.getStates(); @@ -101,21 +92,20 @@ export default class InviteDialogView { */ getStates() { let { - titleString + titleKey } = this.dialog; - let doneKey = 'dialog.done'; - let doneMsg = APP.translation.translateString(doneKey); + let doneMsg = APP.translation.generateTranslationHTML('dialog.done'); let states = {}; let buttons = {}; buttons[`${doneMsg}`] = true; states[States.UNLOCKED] = { - title: titleString, + titleKey, html: this.getShareLinkBlock() + this.getAddPasswordBlock(), buttons }; states[States.LOCKED] = { - title: titleString, + titleKey, html: this.getShareLinkBlock() + this.getPasswordBlock(), buttons }; @@ -128,34 +118,24 @@ export default class InviteDialogView { * @returns {string} */ getShareLinkBlock() { - let copyKey = 'dialog.copy'; - let copyText = APP.translation.translateString(copyKey); - let roomLockDescKey = 'dialog.roomLocked'; - let roomLockDesc = APP.translation.translateString(roomLockDescKey); - let roomUnlockKey = 'roomUnlocked'; - let roomUnlock = APP.translation.translateString(roomUnlockKey); let classes = 'button-control button-control_light copyInviteLink'; return ( `
- +
- +

- ${roomLockDesc} +

- ${roomUnlock} +

` ); @@ -166,27 +146,21 @@ export default class InviteDialogView { * @returns {string} */ getAddPasswordBlock() { - let addPassKey = 'dialog.addPassword'; - let addPassText = APP.translation.translateString(addPassKey); - let addKey = 'dialog.add'; - let addText = APP.translation.translateString(addKey); - let hintKey = 'dialog.createPassword'; - let hintMsg = APP.translation.translateString(hintKey); let html; if (this.model.isModerator) { html = (`
- +
+ type="text" + data-i18n="[placeholder]dialog.createPassword">
@@ -204,22 +178,16 @@ export default class InviteDialogView { */ getPasswordBlock() { let { password, isModerator } = this.model; - let removePassKey = 'dialog.removePassword'; - let removePassText = APP.translation.translateString(removePassKey); - let currentPassKey = 'dialog.currentPassword'; - let currentPassText = APP.translation.translateString(currentPassKey); - let passwordKey = "dialog.passwordLabel"; - let passwordText = APP.translation.translateString(passwordKey); if (isModerator) { return (`
+ data-i18n="dialog.passwordLabel">
-

- ${currentPassText} +

+ ${password} @@ -227,9 +195,7 @@ export default class InviteDialogView {

- ${removePassText} - + data-i18n="dialog.removePassword">
`); @@ -355,10 +321,17 @@ export default class InviteDialogView { */ updateView() { let pass = this.model.getPassword(); - if (!pass) - pass = APP.translation.translateString("passwordSetRemotely"); + if (this.model.getRoomLocker().lockedElsewhere || !pass) + $('#inviteDialogPassword').attr("data-i18n", "passwordSetRemotely"); + else + $('#inviteDialogPassword').text(pass); + + // if we are not moderator we cannot remove password + if (APP.conference.isModerator) + $('#inviteDialogRemovePassword').show(); + else + $('#inviteDialogRemovePassword').hide(); - $('#inviteDialogPassword').text(pass); $('#newPasswordInput').val(''); this.disableAddPassIfInputEmpty(); diff --git a/modules/UI/recording/Recording.js b/modules/UI/recording/Recording.js index 2b82c326b..4b3de1488 100644 --- a/modules/UI/recording/Recording.js +++ b/modules/UI/recording/Recording.js @@ -41,8 +41,6 @@ function _isRecordingButtonEnabled() { * @returns {Promise} */ function _requestLiveStreamId() { - const msg = APP.translation.generateTranslationHTML("dialog.liveStreaming"); - const token = APP.translation.translateString("dialog.streamKey"); const cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel"); const backButton = APP.translation.generateTranslationHTML("dialog.Back"); @@ -55,11 +53,11 @@ function _requestLiveStreamId() { return new Promise(function (resolve, reject) { dialog = APP.UI.messageHandler.openDialogWithStates({ state0: { - title: msg, + titleKey: "dialog.liveStreaming", html: ``, + autofocus>`, persistent: false, buttons: [ {title: cancelButton, value: false}, @@ -89,7 +87,7 @@ function _requestLiveStreamId() { }, state1: { - title: msg, + titleKey: "dialog.liveStreaming", html: streamIdRequired, persistent: false, buttons: [ @@ -122,11 +120,10 @@ function _requestLiveStreamId() { */ function _requestRecordingToken () { let titleKey = "dialog.recordingToken"; - let token = APP.translation.translateString("dialog.token"); let messageString = ( `` + autofocus>` ); return new Promise(function (resolve, reject) { dialog = APP.UI.messageHandler.openTwoButtonDialog({ @@ -297,8 +294,7 @@ var Recording = { selector.addClass(this.baseClass); selector.attr("data-i18n", "[content]" + this.recordingButtonTooltip); - selector.attr("content", - APP.translation.translateString(this.recordingButtonTooltip)); + APP.translation.translateElement(selector); var self = this; selector.click(function () { @@ -365,7 +361,7 @@ var Recording = { dialog = APP.UI.messageHandler.openMessageDialog( self.recordingTitle, self.recordingBusy, - null, null, + null, function () { dialog = null; } @@ -376,7 +372,7 @@ var Recording = { dialog = APP.UI.messageHandler.openMessageDialog( self.recordingTitle, self.recordingUnavailable, - null, null, + null, function () { dialog = null; } @@ -505,7 +501,7 @@ var Recording = { moveToCorner(labelSelector, !isCentered); labelTextSelector.attr("data-i18n", textKey); - labelTextSelector.text(APP.translation.translateString(textKey)); + APP.translation.translateElement(labelSelector); }, /** diff --git a/modules/UI/reload_overlay/PageReloadOverlay.js b/modules/UI/reload_overlay/PageReloadOverlay.js index 4529ab478..bcc225491 100644 --- a/modules/UI/reload_overlay/PageReloadOverlay.js +++ b/modules/UI/reload_overlay/PageReloadOverlay.js @@ -40,7 +40,9 @@ class PageReloadOverlayImpl extends Overlay{
- + `; } @@ -50,11 +52,8 @@ class PageReloadOverlayImpl extends Overlay{ */ updateDisplay() { - const timeLeftTxt - = APP.translation.translateString( - "dialog.conferenceReloadTimeLeft", - { seconds: this.timeLeft }); - $("#reloadSecRemaining").text(timeLeftTxt); + APP.translation.translateElement( + $("#reloadSecRemaining"), { seconds: this.timeLeft }); const ratio = (this.timeout - this.timeLeft) / this.timeout; AJS.progressBars.update("#reloadProgressBar", ratio); diff --git a/modules/UI/shared_video/SharedVideo.js b/modules/UI/shared_video/SharedVideo.js index 36482e63e..064e2f0c5 100644 --- a/modules/UI/shared_video/SharedVideo.js +++ b/modules/UI/shared_video/SharedVideo.js @@ -93,7 +93,7 @@ export default class SharedVideoManager { dialog = APP.UI.messageHandler.openMessageDialog( "dialog.shareVideoTitle", "dialog.alreadySharedVideoMsg", - null, null, + null, function () { dialog = null; } @@ -750,24 +750,19 @@ function showStopVideoPropmpt() { */ function requestVideoLink() { let i18n = APP.translation; - const title = i18n.generateTranslationHTML("dialog.shareVideoTitle"); const cancelButton = i18n.generateTranslationHTML("dialog.Cancel"); const shareButton = i18n.generateTranslationHTML("dialog.Share"); const backButton = i18n.generateTranslationHTML("dialog.Back"); const linkError = i18n.generateTranslationHTML("dialog.shareVideoLinkError"); - const i18nOptions = {url: defaultSharedVideoLink}; - const defaultUrl = i18n.translateString("defaultLink", i18nOptions); return new Promise(function (resolve, reject) { dialog = APP.UI.messageHandler.openDialogWithStates({ state0: { - title: title, + titleKey: "dialog.shareVideoTitle", html: ` `, persistent: false, buttons: [ @@ -802,7 +797,7 @@ function requestVideoLink() { }, state1: { - title: title, + titleKey: "dialog.shareVideoTitle", html: linkError, persistent: false, buttons: [ @@ -825,7 +820,8 @@ function requestVideoLink() { close: function () { dialog = null; } + }, { + url: defaultSharedVideoLink }); - }); } diff --git a/modules/UI/side_pannels/contactlist/ContactListView.js b/modules/UI/side_pannels/contactlist/ContactListView.js index f44828dae..573c9e839 100644 --- a/modules/UI/side_pannels/contactlist/ContactListView.js +++ b/modules/UI/side_pannels/contactlist/ContactListView.js @@ -21,9 +21,8 @@ function updateNumberOfParticipants(delta) { $("#numberOfParticipants").text(numberOfContacts); - $("#contacts_container>div.title").text( - APP.translation.translateString("contactlist") - + ' (' + numberOfContacts + ')'); + APP.translation.translateElement( + $("#contacts_container>div.title"), {pcount: numberOfContacts}); } /** @@ -50,7 +49,6 @@ function createDisplayNameParagraph(key, displayName) { p.innerHTML = displayName; } else if(key) { p.setAttribute("data-i18n",key); - p.innerHTML = APP.translation.translateString(key); } return p; @@ -82,10 +80,11 @@ var ContactListView = { */ addInviteButton() { let container = document.getElementById('contacts_container'); - let title = container.firstElementChild; - let htmlLayout = this.getInviteButtonLayout(); - title.insertAdjacentHTML('afterend', htmlLayout); + container.firstElementChild // this is the title + .insertAdjacentHTML('afterend', this.getInviteButtonLayout()); + + APP.translation.translateElement($(container)); $(document).on('click', '#addParticipantsBtn', () => { APP.UI.emitEvent(UIEvents.INVITE_CLICKED); }); @@ -97,32 +96,26 @@ var ContactListView = { let classes = 'button-control button-control_primary'; classes += ' button-control_full-width'; let key = 'addParticipants'; - let text = APP.translation.translateString(key); let lockedHtml = this.getLockDescriptionLayout(this.lockKey); let unlockedHtml = this.getLockDescriptionLayout(this.unlockKey); - let html = ( + return ( `
+ class="${classes}">
${lockedHtml} ${unlockedHtml}
`); - - return html; }, /** * Adds layout for lock description */ getLockDescriptionLayout(key) { let classes = "input-control__hint input-control_full-width"; - let description = APP.translation.translateString(key); let padlockSuffix = ''; if (key === this.lockKey) { padlockSuffix = '-locked'; @@ -130,7 +123,7 @@ var ContactListView = { return `

- ${description} +

`; }, /** @@ -198,6 +191,7 @@ var ContactListView = { createDisplayNameParagraph( isLocal ? interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME : null, isLocal ? null : interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME)); + APP.translation.translateElement($(newContact)); if (APP.conference.isLocalId(id)) { contactlist.prepend(newContact); diff --git a/modules/UI/toolbars/Toolbar.js b/modules/UI/toolbars/Toolbar.js index 01c02b31f..05f1fe92c 100644 --- a/modules/UI/toolbars/Toolbar.js +++ b/modules/UI/toolbars/Toolbar.js @@ -279,7 +279,6 @@ function showSipNumberInput () { ? config.defaultSipNumber : ''; let titleKey = "dialog.sipMsg"; - let sipMsg = APP.translation.generateTranslationHTML("dialog.sipMsg"); let msgString = (` @@ -287,7 +286,6 @@ function showSipNumberInput () { APP.UI.messageHandler.openTwoButtonDialog({ titleKey, - titleString: sipMsg, msgString, leftButtonKey: "dialog.Dial", submitFunction: function (e, v, m, f) { diff --git a/modules/UI/util/JitsiPopover.js b/modules/UI/util/JitsiPopover.js index 6331c647a..f49c4ea50 100644 --- a/modules/UI/util/JitsiPopover.js +++ b/modules/UI/util/JitsiPopover.js @@ -1,22 +1,26 @@ /* global $ */ var JitsiPopover = (function () { + /** + * The default options + */ + const defaultOptions = { + skin: 'white', + content: '', + hasArrow: true, + onBeforePosition: undefined + }; + /** * Constructs new JitsiPopover and attaches it to the element * @param element jquery selector * @param options the options for the popover. + * - {Function} onBeforePosition - function executed just before + * positioning the popover. Useful for translation. * @constructor */ function JitsiPopover(element, options) { - let { skin, content, hasArrow } = options; - this.options = {}; - this.options.skin = skin || 'white'; - this.options.content = content || ''; - this.options.hasArrow = true; - - if (typeof(hasArrow) !== 'undefined') { - this.options.hasArrow = false; - } + this.options = Object.assign({}, defaultOptions, options); this.elementIsHovered = false; this.popoverIsHovered = false; @@ -86,7 +90,11 @@ var JitsiPopover = (function () { */ JitsiPopover.prototype.createPopover = function () { $("body").append(this.template); - $(".jitsipopover > .jitsipopover__content").html(this.options.content); + let popoverElem = $(".jitsipopover > .jitsipopover__content"); + popoverElem.html(this.options.content); + if(typeof this.options.onBeforePosition === "function") { + this.options.onBeforePosition($(".jitsipopover")); + } var self = this; $(".jitsipopover").on("mouseenter", function () { self.popoverIsHovered = true; @@ -136,4 +144,4 @@ var JitsiPopover = (function () { return JitsiPopover; })(); -module.exports = JitsiPopover; \ No newline at end of file +module.exports = JitsiPopover; diff --git a/modules/UI/util/MessageHandler.js b/modules/UI/util/MessageHandler.js index 0a478a599..fcc3b008e 100644 --- a/modules/UI/util/MessageHandler.js +++ b/modules/UI/util/MessageHandler.js @@ -29,30 +29,20 @@ var messageHandler = { * * @param titleKey the key used to find the translation of the title of the * message, if a message title is not provided. - * @param messageKey the key used to find the translation of the message, - * if a message is not provided. - * @param title the title of the message. If a falsy value is provided, - * titleKey will be used to get a title via the translation API. - * @param message the message to show. If a falsy value is provided, - * messageKey will be used to get a message via the translation API. + * @param messageKey the key used to find the translation of the message + * @param i18nOptions the i18n options (optional) * @param closeFunction function to be called after * the prompt is closed (optional) * @return the prompt that was created, or null */ - openMessageDialog: function(titleKey, messageKey, title, message, - closeFunction) { + openMessageDialog: + function(titleKey, messageKey, i18nOptions, closeFunction) { if (!popupEnabled) return null; - if (!title) { - title = APP.translation.generateTranslationHTML(titleKey); - } - if (!message) { - message = APP.translation.generateTranslationHTML(messageKey); - } - - return $.prompt(message, { - title: this._getFormattedTitleString(title), + let dialog = $.prompt( + APP.translation.generateTranslationHTML(messageKey, i18nOptions), { + title: this._getFormattedTitleString(titleKey), persistent: false, promptspeed: 0, classes: this._getDialogClasses(), @@ -61,12 +51,14 @@ var messageHandler = { closeFunction(e, v, m, f); } }); + APP.translation.translateElement(dialog, i18nOptions); + return dialog; }, /** * Shows a message to the user with two buttons: first is given as a * parameter and the second is Cancel. * - * @param titleString the title of the message + * @param titleKey the key for the title of the message * @param msgString the text of the message * @param persistent boolean value which determines whether the message is * persistent or not @@ -84,7 +76,6 @@ var messageHandler = { openTwoButtonDialog: function(options) { let { titleKey, - titleString, msgKey, msgString, leftButtonKey, @@ -112,10 +103,7 @@ var messageHandler = { = APP.translation.generateTranslationHTML("dialog.Cancel"); buttons.push({title: cancelButton, value: false}); - var message = msgString, title = titleString; - if (titleKey) { - title = APP.translation.generateTranslationHTML(titleKey); - } + var message = msgString; if (msgKey) { message = APP.translation.generateTranslationHTML(msgKey); } @@ -125,7 +113,7 @@ var messageHandler = { } twoButtonDialog = $.prompt(message, { - title: this._getFormattedTitleString(title), + title: this._getFormattedTitleString(titleKey), persistent: false, buttons: buttons, defaultButton: defaultButton, @@ -147,6 +135,7 @@ var messageHandler = { } } }); + APP.translation.translateElement(twoButtonDialog); return twoButtonDialog; }, @@ -154,7 +143,7 @@ var messageHandler = { * Shows a message to the user with two buttons: first is given as a * parameter and the second is Cancel. * - * @param titleString the title of the message + * @param titleKey the key for the title of the message * @param msgString the text of the message * @param persistent boolean value which determines whether the message is * persistent or not @@ -166,13 +155,13 @@ var messageHandler = { * loaded * @param closeFunction function to be called on dialog close */ - openDialog: function (titleString, msgString, persistent, buttons, + openDialog: function (titleKey, msgString, persistent, buttons, submitFunction, loadedFunction, closeFunction) { if (!popupEnabled) return; let args = { - title: this._getFormattedTitleString(titleString), + title: this._getFormattedTitleString(titleKey), persistent: persistent, buttons: buttons, defaultButton: 1, @@ -187,7 +176,9 @@ var messageHandler = { args.closeText = ''; } - return new Impromptu(msgString, args); + let dialog = new Impromptu(msgString, args); + APP.translation.translateElement(dialog.getPrompt()); + return dialog; }, /** @@ -195,13 +186,11 @@ var messageHandler = { * * @return the title string formatted as a div. */ - _getFormattedTitleString(titleString) { + _getFormattedTitleString(titleKey) { let $titleString = $('

'); $titleString.addClass('aui-dialog2-header-main'); - $titleString.append(titleString); - titleString = $('
').append($titleString).html(); - - return titleString; + $titleString.attr('data-i18n',titleKey); + return $('
').append($titleString).html(); }, /** @@ -235,8 +224,10 @@ var messageHandler = { * Shows a dialog with different states to the user. * * @param statesObject object containing all the states of the dialog. + * @param options impromptu options + * @param translateOptions options passed to translation */ - openDialogWithStates: function (statesObject, options) { + openDialogWithStates: function (statesObject, options, translateOptions) { if (!popupEnabled) return; let { classes, size } = options; @@ -246,12 +237,14 @@ var messageHandler = { for (let state in statesObject) { let currentState = statesObject[state]; - if(currentState.title) { - let title = currentState.title; - currentState.title = this._getFormattedTitleString(title); + if(currentState.titleKey) { + currentState.title + = this._getFormattedTitleString(currentState.titleKey); } } - return new Impromptu(statesObject, options); + let dialog = new Impromptu(statesObject, options); + APP.translation.translateElement(dialog.getPrompt(), translateOptions); + return dialog; }, /** @@ -340,20 +333,18 @@ var messageHandler = { if (displayName) { displayNameSpan += ">" + UIUtil.escapeHtml(displayName); } else { - displayNameSpan += "data-i18n='" + displayNameKey + - "'>" + APP.translation.translateString(displayNameKey); + displayNameSpan += "data-i18n='" + displayNameKey + "'>"; } displayNameSpan += ""; - return toastr.info( + let element = toastr.info( displayNameSpan + '
' + '" + - APP.translation.translateString(messageKey, - messageArguments) + - '', null, options); + " data-i18n-options='" + + JSON.stringify(messageArguments) + "'" + : "") + ">", null, options); + APP.translation.translateElement(element); + return element; }, /** diff --git a/modules/UI/videolayout/ConnectionIndicator.js b/modules/UI/videolayout/ConnectionIndicator.js index f3de53b1e..d8312cb1e 100644 --- a/modules/UI/videolayout/ConnectionIndicator.js +++ b/modules/UI/videolayout/ConnectionIndicator.js @@ -1,4 +1,4 @@ -/* global APP, $, config */ +/* global $, APP, config */ /* jshint -W101 */ import JitsiPopover from "../util/JitsiPopover"; import VideoLayout from "./VideoLayout"; @@ -63,8 +63,6 @@ ConnectionIndicator.getStringFromArray = function (array) { ConnectionIndicator.prototype.generateText = function () { var downloadBitrate, uploadBitrate, packetLoss, i; - var translate = APP.translation.translateString; - if(this.bitrate === null) { downloadBitrate = "N/A"; uploadBitrate = "N/A"; @@ -99,9 +97,7 @@ ConnectionIndicator.prototype.generateText = function () { `" + "" + + "data-i18n='connectionindicator.address'>" + ""; } else { var data = {remoteIP: [], localIP:[], remotePort:[], localPort:[]}; @@ -190,18 +179,15 @@ ConnectionIndicator.prototype.generateText = function () { var localTransport = ""; transport = ""; @@ -212,16 +198,14 @@ ConnectionIndicator.prototype.generateText = function () { "" + ""; transport +="" + "" + + "" + ""; } @@ -239,8 +223,7 @@ ConnectionIndicator.prototype.generateText = function () { result += "
- - ${translate("connectionindicator.bitrate")} - + ${downloadBitrate} @@ -110,17 +106,13 @@ ConnectionIndicator.prototype.generateText = function () {
- - ${translate("connectionindicator.packetloss")} - + ${packetLoss}
- - ${translate("connectionindicator.resolution")} - + ${resolutionStr} @@ -134,9 +126,7 @@ ConnectionIndicator.prototype.generateText = function () { // FIXME: we do not know local id when this text is generated //this.id + "')\" data-i18n='connectionindicator." + "local')\" data-i18n='connectionindicator." + - (this.showMoreValue ? "less" : "more") + "'>" + - translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) + - ""; + (this.showMoreValue ? "less" : "more") + "'>"; } if (this.showMoreValue) { @@ -156,8 +146,7 @@ ConnectionIndicator.prototype.generateText = function () { if (!this.transport || this.transport.length === 0) { transport = "
" + - translate("connectionindicator.address") + " N/A
" + - translate(local_address_key, {count: data.localIP.length}) + - " " + + JSON.stringify({count: data.localIP.length}) + + "'> " + ConnectionIndicator.getStringFromArray(data.localIP) + "
" + - translate(remote_address_key, - {count: data.remoteIP.length}) + - " " + + JSON.stringify({count: data.remoteIP.length}) + + "'> " + ConnectionIndicator.getStringFromArray(data.remoteIP) + "
" + "" + - translate(key_remote, {count: this.transport.length}) + - ""; + JSON.stringify({count: this.transport.length}) + + "'>"; localTransport += "
" + "" + - translate(key_local, {count: this.transport.length}) + - ""; + JSON.stringify({count: this.transport.length}) + + "'>"; transport += ConnectionIndicator.getStringFromArray(data.remotePort); @@ -231,7 +215,7 @@ ConnectionIndicator.prototype.generateText = function () { transport += localTransport + "
" + - translate("connectionindicator.transport") + "" + this.transport[0].type + "
" + "" + "
" + - "" + - translate("connectionindicator.bandwidth") + "" + + "" + "" + "" + downloadBandwidth + @@ -282,10 +265,12 @@ ConnectionIndicator.prototype.create = function () { this.videoContainer.container.appendChild( this.connectionIndicatorContainer); this.popover = new JitsiPopover( - $("#" + this.videoContainer.videoSpanId + " > .connectionindicator"), - {content: "
" + - APP.translation.translateString("connectionindicator.na") + "
", - skin: "black"}); + $("#" + this.videoContainer.videoSpanId + " > .connectionindicator"), { + content: "
", + skin: "black", + onBeforePosition: el => APP.translation.translateElement(el) + }); // override popover show method to make sure we will update the content // before showing the popover @@ -398,7 +383,6 @@ ConnectionIndicator.prototype.updatePopoverData = function (force) { this.popover.updateContent( `
${this.generateText()}
` ); - APP.translation.translateElement($(".connection-info")); } }; diff --git a/modules/UI/videolayout/LargeVideoManager.js b/modules/UI/videolayout/LargeVideoManager.js index 4b2217512..2ce6fa080 100644 --- a/modules/UI/videolayout/LargeVideoManager.js +++ b/modules/UI/videolayout/LargeVideoManager.js @@ -379,9 +379,10 @@ export default class LargeVideoManager { */ _setRemoteConnectionMessage (msgKey, msgOptions) { if (msgKey) { - let text = APP.translation.translateString(msgKey, msgOptions); $('#remoteConnectionMessage') - .attr("data-i18n", msgKey).text(text); + .attr("data-i18n", msgKey) + .attr("data-i18n-options", JSON.stringify(msgOptions)); + APP.translation.translateElement($('#remoteConnectionMessage')); } this.videoContainer.positionRemoteConnectionMessage(); @@ -400,7 +401,8 @@ export default class LargeVideoManager { _setLocalConnectionMessage (msgKey, msgOptions) { $('#localConnectionMessage') .attr("data-i18n", msgKey) - .text(APP.translation.translateString(msgKey, msgOptions)); + .attr("data-i18n-options", JSON.stringify(msgOptions)); + APP.translation.translateElement($('#localConnectionMessage')); } /** diff --git a/modules/UI/videolayout/LocalVideo.js b/modules/UI/videolayout/LocalVideo.js index 9cb7456cc..0aadf0e81 100644 --- a/modules/UI/videolayout/LocalVideo.js +++ b/modules/UI/videolayout/LocalVideo.js @@ -96,14 +96,12 @@ LocalVideo.prototype.setDisplayName = function(displayName) { editableText.value = displayName; } - var defaultNickname = APP.translation.translateString( - "defaultNickname", {name: "Jane Pink"}); editableText.setAttribute('style', 'display:none;'); - editableText.setAttribute('data-18n', + editableText.setAttribute('data-i18n', '[placeholder]defaultNickname'); editableText.setAttribute("data-i18n-options", JSON.stringify({name: "Jane Pink"})); - editableText.setAttribute("placeholder", defaultNickname); + APP.translation.translateElement($(editableText)); this.container .querySelector('.videocontainer__toolbar') @@ -253,7 +251,8 @@ LocalVideo.prototype._buildContextMenu = function () { events: { show : function(options){ options.items.flip.name = - APP.translation.translateString("videothumbnail.flip"); + APP.translation.generateTranslationHTML( + "videothumbnail.flip"); } } }); diff --git a/modules/UI/videolayout/RemoteVideo.js b/modules/UI/videolayout/RemoteVideo.js index a51b97cbd..3a1163c68 100644 --- a/modules/UI/videolayout/RemoteVideo.js +++ b/modules/UI/videolayout/RemoteVideo.js @@ -78,7 +78,8 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) { let options = { content: popupMenuElement.outerHTML, skin: "black", - hasArrow: false + hasArrow: false, + onBeforePosition: el => APP.translation.translateElement(el) }; let element = $("#" + this.videoSpanId + " .remotevideomenu"); this.popover = new JitsiPopover(element, options); @@ -112,16 +113,10 @@ RemoteVideo.prototype._generatePopupContent = function () { var mutedIndicator = ""; var doMuteHTML = mutedIndicator + - "
" + - APP.translation.translateString("videothumbnail.domute") + - "
"; + "
"; var mutedHTML = mutedIndicator + - "
" + - APP.translation.translateString("videothumbnail.muted") + - "
"; + "
"; muteLinkItem.id = "mutelink_" + this.id; @@ -153,10 +148,7 @@ RemoteVideo.prototype._generatePopupContent = function () { var ejectMenuItem = document.createElement('li'); var ejectLinkItem = document.createElement('a'); - var ejectText = "
" + - APP.translation.translateString("videothumbnail.kick") + - "
"; + var ejectText = "
"; ejectLinkItem.className = 'ejectlink'; ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText; @@ -170,6 +162,8 @@ RemoteVideo.prototype._generatePopupContent = function () { ejectMenuItem.appendChild(ejectLinkItem); popupmenuElement.appendChild(ejectMenuItem); + APP.translation.translateElement($(popupmenuElement)); + return popupmenuElement; }; diff --git a/modules/keyboardshortcut/keyboardshortcut.js b/modules/keyboardshortcut/keyboardshortcut.js index 6918c32f6..8b51774a3 100644 --- a/modules/keyboardshortcut/keyboardshortcut.js +++ b/modules/keyboardshortcut/keyboardshortcut.js @@ -190,8 +190,7 @@ var KeyboardShortcut = { let descriptionClass = "shortcuts-list__description"; descriptionElement.className = descriptionClass; descriptionElement.setAttribute("data-i18n", shortcutDescriptionKey); - descriptionElement.innerHTML - = APP.translation.translateString(shortcutDescriptionKey); + APP.translation.translateElement($(descriptionElement)); listElement.appendChild(spanElement); listElement.appendChild(descriptionElement); diff --git a/modules/translation/translation.js b/modules/translation/translation.js index b799e5086..5a39f83b6 100644 --- a/modules/translation/translation.js +++ b/modules/translation/translation.js @@ -89,9 +89,6 @@ module.exports = { i18n.init(options, initCompleted); }, - translateString: function (key, options) { - return i18n.t(key, options); - }, setLanguage: function (lang) { if(!lang) lang = DEFAULT_LANG; @@ -100,16 +97,19 @@ module.exports = { getCurrentLanguage: function () { return i18n.lng(); }, - translateElement: function (selector) { - selector.i18n(); + translateElement: function (selector, options) { + // i18next expects undefined if options are missing, check if its null + selector.i18n( + options === null ? undefined : options); }, generateTranslationHTML: function (key, options) { var str = "