diff --git a/scripts/gen-i18n.js b/scripts/gen-i18n.js index dd990b5210..fa9ccc8ed7 100755 --- a/scripts/gen-i18n.js +++ b/scripts/gen-i18n.js @@ -32,7 +32,7 @@ const walk = require('walk'); const flowParser = require('flow-parser'); const estreeWalker = require('estree-walker'); -const TRANSLATIONS_FUNCS = ['_t', '_td', '_tJsx']; +const TRANSLATIONS_FUNCS = ['_t', '_td']; const INPUT_TRANSLATIONS_FILE = 'src/i18n/strings/en_EN.json'; const OUTPUT_FILE = 'src/i18n/strings/en_EN.json'; @@ -126,7 +126,7 @@ function getTranslationsJs(file) { if (tKey === null) return; // check the format string against the args - // We only check _t: _tJsx is much more complex and _td has no args + // We only check _t: _td has no args if (node.callee.name === '_t') { try { const placeholders = getFormatStrings(tKey); @@ -139,6 +139,22 @@ function getTranslationsJs(file) { throw new Error(`No value found for placeholder '${placeholder}'`); } } + + // Validate tag replacements + if (node.arguments.length > 2) { + const tagMap = node.arguments[2]; + for (const prop of tagMap.properties) { + if (prop.key.type === 'Literal') { + const tag = prop.key.value; + // RegExp same as in src/languageHandler.js + const regexp = new RegExp(`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`); + if (!tKey.match(regexp)) { + throw new Error(`No match for ${regexp} in ${tKey}`); + } + } + } + } + } catch (e) { console.log(); console.error(`ERROR: ${file}:${node.loc.start.line} ${tKey}`); diff --git a/src/components/structures/FilePanel.js b/src/components/structures/FilePanel.js index 23feb4cf30..ffa5e45249 100644 --- a/src/components/structures/FilePanel.js +++ b/src/components/structures/FilePanel.js @@ -19,7 +19,7 @@ import React from 'react'; import Matrix from 'matrix-js-sdk'; import sdk from '../../index'; import MatrixClientPeg from '../../MatrixClientPeg'; -import { _t, _tJsx } from '../../languageHandler'; +import { _t } from '../../languageHandler'; /* * Component which shows the filtered file using a TimelinePanel @@ -92,7 +92,10 @@ const FilePanel = React.createClass({ if (MatrixClientPeg.get().isGuest()) { return
- { _tJsx( + { _t( "Otherwise, click here to send a bug report.", - /(.*?)<\/a>/, (sub) => { sub }, + {}, + { 'a': (sub) => { sub } }, ) }
); diff --git a/src/components/views/dialogs/SetMxIdDialog.js b/src/components/views/dialogs/SetMxIdDialog.js index 057609b344..6fc1d77682 100644 --- a/src/components/views/dialogs/SetMxIdDialog.js +++ b/src/components/views/dialogs/SetMxIdDialog.js @@ -21,7 +21,7 @@ import sdk from '../../../index'; import MatrixClientPeg from '../../../MatrixClientPeg'; import classnames from 'classnames'; import KeyCode from '../../../KeyCode'; -import { _t, _tJsx } from '../../../languageHandler'; +import { _t } from '../../../languageHandler'; // The amount of time to wait for further changes to the input username before // sending a request to the server @@ -267,24 +267,21 @@ export default React.createClass({ { usernameIndicator }- { _tJsx( + { _t( 'This will be your account name on the ' + 'homeserver, or you can pick a different server.', - [ - /<\/span>/, - /(.*?)<\/a>/, - ], - [ - (sub) => { this.props.homeserverUrl }, - (sub) => { sub }, - ], + {}, + { + 'span': { this.props.homeserverUrl }, + 'a': (sub) => { sub }, + }, ) }
- { _tJsx( + { _t( 'If you already have a Matrix account you can log in instead.', - /(.*?)<\/a>/, - [(sub) => { sub }], + {}, + { 'a': (sub) => { sub } }, ) }
{ auth } diff --git a/src/components/views/login/CaptchaForm.js b/src/components/views/login/CaptchaForm.js index cf814b0a6e..21e5094b28 100644 --- a/src/components/views/login/CaptchaForm.js +++ b/src/components/views/login/CaptchaForm.js @@ -18,7 +18,7 @@ limitations under the License. import React from 'react'; import ReactDOM from 'react-dom'; -import { _t, _tJsx } from '../../../languageHandler'; +import { _t } from '../../../languageHandler'; const DIV_ID = 'mx_recaptcha'; @@ -67,10 +67,10 @@ module.exports = React.createClass({ // * jumping straight to a hosted captcha page (but we don't support that yet) // * embedding the captcha in an iframe (if that works) // * using a better captcha lib - ReactDOM.render(_tJsx( + ReactDOM.render(_t( "Robot check is currently unavailable on desktop - please use a web browser", - /(.*?)<\/a>/, - (sub) => { return { sub }; }), warning); + {}, + { 'a': (sub) => { return { sub }; }}), warning); this.refs.recaptchaContainer.appendChild(warning); } else { const scriptTag = document.createElement('script'); diff --git a/src/components/views/login/InteractiveAuthEntryComponents.js b/src/components/views/login/InteractiveAuthEntryComponents.js index 5f5a74ccd1..d0b6c8decb 100644 --- a/src/components/views/login/InteractiveAuthEntryComponents.js +++ b/src/components/views/login/InteractiveAuthEntryComponents.js @@ -20,7 +20,7 @@ import url from 'url'; import classnames from 'classnames'; import sdk from '../../../index'; -import { _t, _tJsx } from '../../../languageHandler'; +import { _t } from '../../../languageHandler'; /* This file contains a collection of components which are used by the * InteractiveAuth to prompt the user to enter the information needed @@ -256,7 +256,10 @@ export const EmailIdentityAuthEntry = React.createClass({ } else { return ({ _tJsx("An email has been sent to %(emailAddress)s", /%\(emailAddress\)s/, (sub) => {this.props.inputs.emailAddress}) }
+{ _t("An email has been sent to %(emailAddress)s", + { emailAddress: (sub) => { this.props.inputs.emailAddress } }, + ) } +
{ _t("Please check your email to continue registration.") }
{ _tJsx("A text message has been sent to %(msisdn)s", /%\(msisdn\)s/, (sub) => {this._msisdn}) }
+{ _t("A text message has been sent to %(msisdn)s", + { msisdn: this._msisdn }, + ) } +
{ _t("Please enter the code it contains:") }
{ event_type }
);
+ else label = _t("To send events of type { event_type }
});
return (