Refactor i18n stuff a bit
This commit is contained in:
parent
b3ddec4131
commit
3afbaf61e7
17 changed files with 225 additions and 199 deletions
|
@ -19,7 +19,7 @@ import React from 'react';
|
||||||
import Matrix from 'matrix-js-sdk';
|
import Matrix from 'matrix-js-sdk';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import { _t, _tJsx } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Component which shows the filtered file using a TimelinePanel
|
* Component which shows the filtered file using a TimelinePanel
|
||||||
|
@ -92,7 +92,10 @@ const FilePanel = React.createClass({
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
return <div className="mx_FilePanel mx_RoomView_messageListWrapper">
|
||||||
<div className="mx_RoomView_empty">
|
<div className="mx_RoomView_empty">
|
||||||
{ _tJsx("You must <a>register</a> to use this functionality", /<a>(.*?)<\/a>/, (sub) => <a href="#/register" key="sub">{ sub }</a>) }
|
{ _t("You must <a>register</a> to use this functionality",
|
||||||
|
{},
|
||||||
|
{ 'a': (sub) => <a href="#/register" key="sub">{ sub }</a> })
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
} else if (this.noRoom) {
|
} else if (this.noRoom) {
|
||||||
|
|
|
@ -22,7 +22,7 @@ import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
import { sanitizedHtmlNode } from '../../HtmlUtils';
|
import { sanitizedHtmlNode } from '../../HtmlUtils';
|
||||||
import { _t, _td, _tJsx } from '../../languageHandler';
|
import { _t, _td } from '../../languageHandler';
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
import Modal from '../../Modal';
|
import Modal from '../../Modal';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
@ -932,12 +932,12 @@ export default React.createClass({
|
||||||
className="mx_GroupView_groupDesc_placeholder"
|
className="mx_GroupView_groupDesc_placeholder"
|
||||||
onClick={this._onEditClick}
|
onClick={this._onEditClick}
|
||||||
>
|
>
|
||||||
{ _tJsx(
|
{ _t(
|
||||||
'Your community hasn\'t got a Long Description, a HTML page to show to community members.<br />' +
|
'Your community hasn\'t got a Long Description, a HTML page to show to community members.<br />' +
|
||||||
'Click here to open settings and give it one!',
|
'Click here to open settings and give it one!',
|
||||||
[/<br \/>/],
|
{},
|
||||||
[(sub) => <br />])
|
{ 'br': () => <br /> },
|
||||||
}
|
) }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
const groupDescEditingClasses = classnames({
|
const groupDescEditingClasses = classnames({
|
||||||
|
|
|
@ -19,7 +19,7 @@ import PropTypes from 'prop-types';
|
||||||
import GeminiScrollbar from 'react-gemini-scrollbar';
|
import GeminiScrollbar from 'react-gemini-scrollbar';
|
||||||
import {MatrixClient} from 'matrix-js-sdk';
|
import {MatrixClient} from 'matrix-js-sdk';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import { _t, _tJsx } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import withMatrixClient from '../../wrappers/withMatrixClient';
|
import withMatrixClient from '../../wrappers/withMatrixClient';
|
||||||
import AccessibleButton from '../views/elements/AccessibleButton';
|
import AccessibleButton from '../views/elements/AccessibleButton';
|
||||||
import dis from '../../dispatcher';
|
import dis from '../../dispatcher';
|
||||||
|
@ -165,13 +165,13 @@ export default withMatrixClient(React.createClass({
|
||||||
<div className="mx_MyGroups_headerCard_header">
|
<div className="mx_MyGroups_headerCard_header">
|
||||||
{ _t('Join an existing community') }
|
{ _t('Join an existing community') }
|
||||||
</div>
|
</div>
|
||||||
{ _tJsx(
|
{ _t(
|
||||||
'To join an existing community you\'ll have to '+
|
'To join an existing community you\'ll have to '+
|
||||||
'know its community identifier; this will look '+
|
'know its community identifier; this will look '+
|
||||||
'something like <i>+example:matrix.org</i>.',
|
'something like <i>+example:matrix.org</i>.',
|
||||||
/<i>(.*)<\/i>/,
|
{},
|
||||||
(sub) => <i>{ sub }</i>,
|
{ 'i': (sub) => <i>{ sub }</i> })
|
||||||
) }
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,13 +15,12 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t, _tJsx } from '../../languageHandler';
|
import { _t } from '../../languageHandler';
|
||||||
import sdk from '../../index';
|
import sdk from '../../index';
|
||||||
import WhoIsTyping from '../../WhoIsTyping';
|
import WhoIsTyping from '../../WhoIsTyping';
|
||||||
import MatrixClientPeg from '../../MatrixClientPeg';
|
import MatrixClientPeg from '../../MatrixClientPeg';
|
||||||
import MemberAvatar from '../views/avatars/MemberAvatar';
|
import MemberAvatar from '../views/avatars/MemberAvatar';
|
||||||
|
|
||||||
const HIDE_DEBOUNCE_MS = 10000;
|
|
||||||
const STATUS_BAR_HIDDEN = 0;
|
const STATUS_BAR_HIDDEN = 0;
|
||||||
const STATUS_BAR_EXPANDED = 1;
|
const STATUS_BAR_EXPANDED = 1;
|
||||||
const STATUS_BAR_EXPANDED_LARGE = 2;
|
const STATUS_BAR_EXPANDED_LARGE = 2;
|
||||||
|
@ -272,12 +271,16 @@ module.exports = React.createClass({
|
||||||
{ this.props.unsentMessageError }
|
{ this.props.unsentMessageError }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomStatusBar_connectionLostBar_desc">
|
<div className="mx_RoomStatusBar_connectionLostBar_desc">
|
||||||
{ _tJsx("<a>Resend all</a> or <a>cancel all</a> now. You can also select individual messages to resend or cancel.",
|
{
|
||||||
[/<a>(.*?)<\/a>/, /<a>(.*?)<\/a>/],
|
_t("<resendText>Resend all</resendText> or <cancelText>cancel all</cancelText> now. " +
|
||||||
[
|
"You can also select individual messages to resend or cancel.",
|
||||||
(sub) => <a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this.props.onResendAllClick}>{ sub }</a>,
|
{},
|
||||||
(sub) => <a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this.props.onCancelAllClick}>{ sub }</a>,
|
{
|
||||||
],
|
'resendText': (sub) =>
|
||||||
|
<a className="mx_RoomStatusBar_resend_link" key="resend" onClick={this.props.onResendAllClick}>{ sub }</a>,
|
||||||
|
'cancelText': (sub) =>
|
||||||
|
<a className="mx_RoomStatusBar_resend_link" key="cancel" onClick={this.props.onCancelAllClick}>{ sub }</a>,
|
||||||
|
},
|
||||||
) }
|
) }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -322,12 +325,15 @@ module.exports = React.createClass({
|
||||||
if (this.props.sentMessageAndIsAlone) {
|
if (this.props.sentMessageAndIsAlone) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomStatusBar_isAlone">
|
<div className="mx_RoomStatusBar_isAlone">
|
||||||
{ _tJsx("There's no one else here! Would you like to <a>invite others</a> or <a>stop warning about the empty room</a>?",
|
{ _t("There's no one else here! Would you like to <inviteText>invite others</inviteText> " +
|
||||||
[/<a>(.*?)<\/a>/, /<a>(.*?)<\/a>/],
|
"or <nowarnText>stop warning about the empty room</nowarnText>?",
|
||||||
[
|
{},
|
||||||
(sub) => <a className="mx_RoomStatusBar_resend_link" key="invite" onClick={this.props.onInviteClick}>{ sub }</a>,
|
{
|
||||||
(sub) => <a className="mx_RoomStatusBar_resend_link" key="nowarn" onClick={this.props.onStopWarningClick}>{ sub }</a>,
|
'inviteText': (sub) =>
|
||||||
],
|
<a className="mx_RoomStatusBar_resend_link" key="invite" onClick={this.props.onInviteClick}>{ sub }</a>,
|
||||||
|
'nowarnText': (sub) =>
|
||||||
|
<a className="mx_RoomStatusBar_resend_link" key="nowarn" onClick={this.props.onStopWarningClick}>{ sub }</a>,
|
||||||
|
},
|
||||||
) }
|
) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t, _tJsx } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import * as languageHandler from '../../../languageHandler';
|
import * as languageHandler from '../../../languageHandler';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Login from '../../../Login';
|
import Login from '../../../Login';
|
||||||
|
@ -256,17 +256,19 @@ module.exports = React.createClass({
|
||||||
!this.state.enteredHomeserverUrl.startsWith("http"))
|
!this.state.enteredHomeserverUrl.startsWith("http"))
|
||||||
) {
|
) {
|
||||||
errorText = <span>
|
errorText = <span>
|
||||||
{ _tJsx("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
|
{
|
||||||
|
_t("Can't connect to homeserver via HTTP when an HTTPS URL is in your browser bar. " +
|
||||||
"Either use HTTPS or <a>enable unsafe scripts</a>.",
|
"Either use HTTPS or <a>enable unsafe scripts</a>.",
|
||||||
/<a>(.*?)<\/a>/,
|
{},
|
||||||
(sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; },
|
{ 'a': (sub) => { return <a href="https://www.google.com/search?&q=enable%20unsafe%20scripts">{ sub }</a>; } },
|
||||||
) }
|
) }
|
||||||
</span>;
|
</span>;
|
||||||
} else {
|
} else {
|
||||||
errorText = <span>
|
errorText = <span>
|
||||||
{ _tJsx("Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.",
|
{
|
||||||
/<a>(.*?)<\/a>/,
|
_t("Can't connect to homeserver - please check your connectivity, ensure your <a>homeserver's SSL certificate</a> is trusted, and that a browser extension is not blocking requests.",
|
||||||
(sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; },
|
{},
|
||||||
|
{ 'a': (sub) => { return <a href={this.state.enteredHomeserverUrl}>{ sub }</a>; } },
|
||||||
) }
|
) }
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
|
@ -277,7 +279,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
componentForStep: function(step) {
|
componentForStep: function(step) {
|
||||||
switch (step) {
|
switch (step) {
|
||||||
case 'm.login.password':
|
case 'm.login.password': {
|
||||||
const PasswordLogin = sdk.getComponent('login.PasswordLogin');
|
const PasswordLogin = sdk.getComponent('login.PasswordLogin');
|
||||||
return (
|
return (
|
||||||
<PasswordLogin
|
<PasswordLogin
|
||||||
|
@ -293,12 +295,14 @@ module.exports = React.createClass({
|
||||||
hsUrl={this.state.enteredHomeserverUrl}
|
hsUrl={this.state.enteredHomeserverUrl}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case 'm.login.cas':
|
}
|
||||||
|
case 'm.login.cas': {
|
||||||
const CasLogin = sdk.getComponent('login.CasLogin');
|
const CasLogin = sdk.getComponent('login.CasLogin');
|
||||||
return (
|
return (
|
||||||
<CasLogin onSubmit={this.onCasLogin} />
|
<CasLogin onSubmit={this.onCasLogin} />
|
||||||
);
|
);
|
||||||
default:
|
}
|
||||||
|
default: {
|
||||||
if (!step) {
|
if (!step) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -307,6 +311,7 @@ module.exports = React.createClass({
|
||||||
{ _t('Sorry, this homeserver is using a login which is not recognised ') }({ step })
|
{ _t('Sorry, this homeserver is using a login which is not recognised ') }({ step })
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import SdkConfig from '../../../SdkConfig';
|
import SdkConfig from '../../../SdkConfig';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import { _t, _tJsx } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
|
||||||
export default React.createClass({
|
export default React.createClass({
|
||||||
|
@ -45,9 +45,10 @@ export default React.createClass({
|
||||||
if (SdkConfig.get().bug_report_endpoint_url) {
|
if (SdkConfig.get().bug_report_endpoint_url) {
|
||||||
bugreport = (
|
bugreport = (
|
||||||
<p>
|
<p>
|
||||||
{ _tJsx(
|
{ _t(
|
||||||
"Otherwise, <a>click here</a> to send a bug report.",
|
"Otherwise, <a>click here</a> to send a bug report.",
|
||||||
/<a>(.*?)<\/a>/, (sub) => <a onClick={this._sendBugReport} key="bugreport" href='#'>{ sub }</a>,
|
{},
|
||||||
|
{ 'a': (sub) => <a onClick={this._sendBugReport} key="bugreport" href='#'>{ sub }</a> },
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,7 +21,7 @@ import sdk from '../../../index';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import KeyCode from '../../../KeyCode';
|
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
|
// The amount of time to wait for further changes to the input username before
|
||||||
// sending a request to the server
|
// sending a request to the server
|
||||||
|
@ -267,24 +267,21 @@ export default React.createClass({
|
||||||
</div>
|
</div>
|
||||||
{ usernameIndicator }
|
{ usernameIndicator }
|
||||||
<p>
|
<p>
|
||||||
{ _tJsx(
|
{ _t(
|
||||||
'This will be your account name on the <span></span> ' +
|
'This will be your account name on the <span></span> ' +
|
||||||
'homeserver, or you can pick a <a>different server</a>.',
|
'homeserver, or you can pick a <a>different server</a>.',
|
||||||
[
|
{},
|
||||||
/<span><\/span>/,
|
{
|
||||||
/<a>(.*?)<\/a>/,
|
'span': () => <span>{ this.props.homeserverUrl }</span>,
|
||||||
],
|
'a': (sub) => <a href="#" onClick={this.props.onDifferentServerClicked}>{ sub }</a>,
|
||||||
[
|
},
|
||||||
(sub) => <span>{ this.props.homeserverUrl }</span>,
|
|
||||||
(sub) => <a href="#" onClick={this.props.onDifferentServerClicked}>{ sub }</a>,
|
|
||||||
],
|
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{ _tJsx(
|
{ _t(
|
||||||
'If you already have a Matrix account you can <a>log in</a> instead.',
|
'If you already have a Matrix account you can <a>log in</a> instead.',
|
||||||
/<a>(.*?)<\/a>/,
|
{},
|
||||||
[(sub) => <a href="#" onClick={this.props.onLoginClick}>{ sub }</a>],
|
{ 'a': (sub) => <a href="#" onClick={this.props.onLoginClick}>{ sub }</a> },
|
||||||
) }
|
) }
|
||||||
</p>
|
</p>
|
||||||
{ auth }
|
{ auth }
|
||||||
|
|
|
@ -18,7 +18,7 @@ limitations under the License.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { _t, _tJsx } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
const DIV_ID = 'mx_recaptcha';
|
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)
|
// * jumping straight to a hosted captcha page (but we don't support that yet)
|
||||||
// * embedding the captcha in an iframe (if that works)
|
// * embedding the captcha in an iframe (if that works)
|
||||||
// * using a better captcha lib
|
// * using a better captcha lib
|
||||||
ReactDOM.render(_tJsx(
|
ReactDOM.render(_t(
|
||||||
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
|
"Robot check is currently unavailable on desktop - please use a <a>web browser</a>",
|
||||||
/<a>(.*?)<\/a>/,
|
{},
|
||||||
(sub) => { return <a href='https://riot.im/app'>{ sub }</a>; }), warning);
|
{ 'a': (sub) => { return <a href='https://riot.im/app'>{ sub }</a>; }}), warning);
|
||||||
this.refs.recaptchaContainer.appendChild(warning);
|
this.refs.recaptchaContainer.appendChild(warning);
|
||||||
} else {
|
} else {
|
||||||
const scriptTag = document.createElement('script');
|
const scriptTag = document.createElement('script');
|
||||||
|
|
|
@ -20,7 +20,7 @@ import url from 'url';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import sdk from '../../../index';
|
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
|
/* This file contains a collection of components which are used by the
|
||||||
* InteractiveAuth to prompt the user to enter the information needed
|
* InteractiveAuth to prompt the user to enter the information needed
|
||||||
|
@ -256,7 +256,10 @@ export const EmailIdentityAuthEntry = React.createClass({
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{ _tJsx("An email has been sent to %(emailAddress)s", /%\(emailAddress\)s/, (sub) => <i>{this.props.inputs.emailAddress}</i>) }</p>
|
<p>{ _t("An email has been sent to %(emailAddress)s",
|
||||||
|
{ emailAddress: (sub) => <i>{ this.props.inputs.emailAddress }</i> },
|
||||||
|
) }
|
||||||
|
</p>
|
||||||
<p>{ _t("Please check your email to continue registration.") }</p>
|
<p>{ _t("Please check your email to continue registration.") }</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -370,7 +373,10 @@ export const MsisdnAuthEntry = React.createClass({
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p>{ _tJsx("A text message has been sent to %(msisdn)s", /%\(msisdn\)s/, (sub) => <i>{this._msisdn}</i>) }</p>
|
<p>{ _t("A text message has been sent to %(msisdn)s",
|
||||||
|
{ msisdn: () => <i>this._msisdn</i> },
|
||||||
|
) }
|
||||||
|
</p>
|
||||||
<p>{ _t("Please enter the code it contains:") }</p>
|
<p>{ _t("Please enter the code it contains:") }</p>
|
||||||
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
<div className="mx_InteractiveAuthEntryComponents_msisdnWrapper">
|
||||||
<form onSubmit={this._onFormSubmit}>
|
<form onSubmit={this._onFormSubmit}>
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import { ContentRepo } from 'matrix-js-sdk';
|
import { ContentRepo } from 'matrix-js-sdk';
|
||||||
import { _t, _tJsx } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import AccessibleButton from '../elements/AccessibleButton';
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
@ -67,24 +67,17 @@ module.exports = React.createClass({
|
||||||
'crop',
|
'crop',
|
||||||
);
|
);
|
||||||
|
|
||||||
// it sucks that _tJsx doesn't support normal _t substitutions :((
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomAvatarEvent">
|
<div className="mx_RoomAvatarEvent">
|
||||||
{ _tJsx('%(senderDisplayName)s changed the room avatar to <img/>',
|
{ _t('%(senderDisplayName)s changed the room avatar to <img/>',
|
||||||
[
|
{ senderDisplayName: senderDisplayName },
|
||||||
/%\(senderDisplayName\)s/,
|
{
|
||||||
/<img\/>/,
|
'img': () =>
|
||||||
],
|
<AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
|
||||||
[
|
onClick={this.onAvatarClick.bind(this, name)}>
|
||||||
(sub) => senderDisplayName,
|
<BaseAvatar width={14} height={14} url={url} name={name} />
|
||||||
(sub) =>
|
</AccessibleButton>,
|
||||||
<AccessibleButton key="avatar" className="mx_RoomAvatarEvent_avatar"
|
})
|
||||||
onClick={this.onAvatarClick.bind(this, name)}>
|
|
||||||
<BaseAvatar width={14} height={14} url={url}
|
|
||||||
name={name} />
|
|
||||||
</AccessibleButton>,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Flair from '../elements/Flair.js';
|
import Flair from '../elements/Flair.js';
|
||||||
import { _tJsx } from '../../../languageHandler';
|
import { _t, substitute } from '../../../languageHandler';
|
||||||
|
|
||||||
export default function SenderProfile(props) {
|
export default function SenderProfile(props) {
|
||||||
const EmojiText = sdk.getComponent('elements.EmojiText');
|
const EmojiText = sdk.getComponent('elements.EmojiText');
|
||||||
|
@ -42,22 +42,19 @@ export default function SenderProfile(props) {
|
||||||
: null,
|
: null,
|
||||||
];
|
];
|
||||||
|
|
||||||
let content = '';
|
let content;
|
||||||
|
|
||||||
if(props.text) {
|
if(props.text) {
|
||||||
// Replace senderName, and wrap surrounding text in spans with the right class
|
content = _t(props.text, { senderName: () => nameElem });
|
||||||
content = _tJsx(props.text, /^(.*)\%\(senderName\)s(.*)$/m, (p1, p2) => [
|
|
||||||
p1 ? <span className='mx_SenderProfile_aux'>{ p1 }</span> : null,
|
|
||||||
nameElem,
|
|
||||||
p2 ? <span className='mx_SenderProfile_aux'>{ p2 }</span> : null,
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
content = nameElem;
|
// There is nothing to translate here, so call substitute() instead
|
||||||
|
content = substitute('%(senderName)s', { senderName: () => nameElem });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx_SenderProfile" dir="auto" onClick={props.onClick}>
|
<div className="mx_SenderProfile" dir="auto" onClick={props.onClick}>
|
||||||
{ content }
|
{ content.props.children[0] ? <span className='mx_SenderProfile_aux'>{ content.props.children[0] }</span> : '' }
|
||||||
|
{ content.props.children[1] }
|
||||||
|
{ content.props.children[2] ? <span className='mx_SenderProfile_aux'>{ content.props.children[2] }</span> : '' }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,10 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
|
||||||
const React = require('react');
|
const React = require('react');
|
||||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
const sdk = require("../../../index");
|
|
||||||
const Modal = require("../../../Modal");
|
|
||||||
const UserSettingsStore = require('../../../UserSettingsStore');
|
const UserSettingsStore = require('../../../UserSettingsStore');
|
||||||
import { _t, _tJsx } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
|
@ -31,9 +28,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
const cli = MatrixClientPeg.get();
|
|
||||||
const roomState = this.props.room.currentState;
|
|
||||||
|
|
||||||
const roomPreviewUrls = this.props.room.currentState.getStateEvents('org.matrix.room.preview_urls', '');
|
const roomPreviewUrls = this.props.room.currentState.getStateEvents('org.matrix.room.preview_urls', '');
|
||||||
const userPreviewUrls = this.props.room.getAccountData("org.matrix.room.preview_urls");
|
const userPreviewUrls = this.props.room.getAccountData("org.matrix.room.preview_urls");
|
||||||
|
|
||||||
|
@ -109,7 +103,6 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
const self = this;
|
|
||||||
const roomState = this.props.room.currentState;
|
const roomState = this.props.room.currentState;
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
|
|
||||||
|
@ -133,11 +126,11 @@ module.exports = React.createClass({
|
||||||
let urlPreviewText = null;
|
let urlPreviewText = null;
|
||||||
if (UserSettingsStore.getUrlPreviewsDisabled()) {
|
if (UserSettingsStore.getUrlPreviewsDisabled()) {
|
||||||
urlPreviewText = (
|
urlPreviewText = (
|
||||||
_tJsx("You have <a>disabled</a> URL previews by default.", /<a>(.*?)<\/a>/, (sub)=><a href="#/settings">{ sub }</a>)
|
_t("You have <a>disabled</a> URL previews by default.", {}, { 'a': (sub)=><a href="#/settings">{ sub }</a> })
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
urlPreviewText = (
|
urlPreviewText = (
|
||||||
_tJsx("You have <a>enabled</a> URL previews by default.", /<a>(.*?)<\/a>/, (sub)=><a href="#/settings">{ sub }</a>)
|
_t("You have <a>enabled</a> URL previews by default.", {}, { 'a': (sub)=><a href="#/settings">{ sub }</a> })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,7 @@ import sdk from '../../../index';
|
||||||
import dis from "../../../dispatcher";
|
import dis from "../../../dispatcher";
|
||||||
import ObjectUtils from '../../../ObjectUtils';
|
import ObjectUtils from '../../../ObjectUtils';
|
||||||
import AppsDrawer from './AppsDrawer';
|
import AppsDrawer from './AppsDrawer';
|
||||||
import { _t, _tJsx} from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
import UserSettingsStore from '../../../UserSettingsStore';
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'AuxPanel',
|
displayName: 'AuxPanel',
|
||||||
|
@ -100,13 +98,13 @@ module.exports = React.createClass({
|
||||||
supportedText = _t(" (unsupported)");
|
supportedText = _t(" (unsupported)");
|
||||||
} else {
|
} else {
|
||||||
joinNode = (<span>
|
joinNode = (<span>
|
||||||
{ _tJsx(
|
{ _t(
|
||||||
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
"Join as <voiceText>voice</voiceText> or <videoText>video</videoText>.",
|
||||||
[/<voiceText>(.*?)<\/voiceText>/, /<videoText>(.*?)<\/videoText>/],
|
{},
|
||||||
[
|
{
|
||||||
(sub) => <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice');}} href="#">{ sub }</a>,
|
'voiceText': (sub) => <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'voice');}} href="#">{ sub }</a>,
|
||||||
(sub) => <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video');}} href="#">{ sub }</a>,
|
'videoText': (sub) => <a onClick={(event)=>{ this.onConferenceNotificationClick(event, 'video');}} href="#">{ sub }</a>,
|
||||||
],
|
},
|
||||||
) }
|
) }
|
||||||
</span>);
|
</span>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,12 +18,10 @@ limitations under the License.
|
||||||
'use strict';
|
'use strict';
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
const ReactDOM = require("react-dom");
|
const ReactDOM = require("react-dom");
|
||||||
import { _t, _tJsx } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
const GeminiScrollbar = require('react-gemini-scrollbar');
|
const GeminiScrollbar = require('react-gemini-scrollbar');
|
||||||
const MatrixClientPeg = require("../../../MatrixClientPeg");
|
const MatrixClientPeg = require("../../../MatrixClientPeg");
|
||||||
const CallHandler = require('../../../CallHandler');
|
const CallHandler = require('../../../CallHandler');
|
||||||
const RoomListSorter = require("../../../RoomListSorter");
|
|
||||||
const Unread = require('../../../Unread');
|
|
||||||
const dis = require("../../../dispatcher");
|
const dis = require("../../../dispatcher");
|
||||||
const sdk = require('../../../index');
|
const sdk = require('../../../index');
|
||||||
const rate_limited_func = require('../../../ratelimitedfunc');
|
const rate_limited_func = require('../../../ratelimitedfunc');
|
||||||
|
@ -486,28 +484,25 @@ module.exports = React.createClass({
|
||||||
const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
|
const RoomDirectoryButton = sdk.getComponent('elements.RoomDirectoryButton');
|
||||||
const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
|
const CreateRoomButton = sdk.getComponent('elements.CreateRoomButton');
|
||||||
|
|
||||||
const TintableSvg = sdk.getComponent('elements.TintableSvg');
|
|
||||||
switch (section) {
|
switch (section) {
|
||||||
case 'im.vector.fake.direct':
|
case 'im.vector.fake.direct':
|
||||||
return <div className="mx_RoomList_emptySubListTip">
|
return <div className="mx_RoomList_emptySubListTip">
|
||||||
{ _tJsx(
|
{ _t(
|
||||||
"Press <StartChatButton> to start a chat with someone",
|
"Press <StartChatButton> to start a chat with someone",
|
||||||
[/<StartChatButton>/],
|
{},
|
||||||
[
|
{ 'StartChatButton': () => <StartChatButton size="16" callout={true} /> },
|
||||||
(sub) => <StartChatButton size="16" callout={true} />,
|
|
||||||
],
|
|
||||||
) }
|
) }
|
||||||
</div>;
|
</div>;
|
||||||
case 'im.vector.fake.recent':
|
case 'im.vector.fake.recent':
|
||||||
return <div className="mx_RoomList_emptySubListTip">
|
return <div className="mx_RoomList_emptySubListTip">
|
||||||
{ _tJsx(
|
{ _t(
|
||||||
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or"+
|
"You're not in any rooms yet! Press <CreateRoomButton> to make a room or"+
|
||||||
" <RoomDirectoryButton> to browse the directory",
|
" <RoomDirectoryButton> to browse the directory",
|
||||||
[/<CreateRoomButton>/, /<RoomDirectoryButton>/],
|
{},
|
||||||
[
|
{
|
||||||
(sub) => <CreateRoomButton size="16" callout={true} />,
|
'CreateRoomButton': () => <CreateRoomButton size="16" callout={true} />,
|
||||||
(sub) => <RoomDirectoryButton size="16" callout={true} />,
|
'RoomDirectoryButton': () => <RoomDirectoryButton size="16" callout={true} />,
|
||||||
],
|
},
|
||||||
) }
|
) }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ const React = require('react');
|
||||||
const sdk = require('../../../index');
|
const sdk = require('../../../index');
|
||||||
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
const MatrixClientPeg = require('../../../MatrixClientPeg');
|
||||||
|
|
||||||
import { _t, _tJsx } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'RoomPreviewBar',
|
displayName: 'RoomPreviewBar',
|
||||||
|
@ -135,13 +135,13 @@ module.exports = React.createClass({
|
||||||
{ _t('You have been invited to join this room by %(inviterName)s', {inviterName: this.props.inviterName}) }
|
{ _t('You have been invited to join this room by %(inviterName)s', {inviterName: this.props.inviterName}) }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomPreviewBar_join_text">
|
<div className="mx_RoomPreviewBar_join_text">
|
||||||
{ _tJsx(
|
{ _t(
|
||||||
'Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?',
|
'Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?',
|
||||||
[/<acceptText>(.*?)<\/acceptText>/, /<declineText>(.*?)<\/declineText>/],
|
{},
|
||||||
[
|
{
|
||||||
(sub) => <a onClick={this.props.onJoinClick}>{ sub }</a>,
|
'acceptText': (sub) => <a onClick={this.props.onJoinClick}>{ sub }</a>,
|
||||||
(sub) => <a onClick={this.props.onRejectClick}>{ sub }</a>,
|
'declineText': (sub) => <a onClick={this.props.onRejectClick}>{ sub }</a>,
|
||||||
],
|
},
|
||||||
) }
|
) }
|
||||||
</div>
|
</div>
|
||||||
{ emailMatchBlock }
|
{ emailMatchBlock }
|
||||||
|
@ -211,9 +211,9 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomPreviewBar_join_text">
|
<div className="mx_RoomPreviewBar_join_text">
|
||||||
{ name ? _t('You are trying to access %(roomName)s.', {roomName: name}) : _t('You are trying to access a room.') }
|
{ name ? _t('You are trying to access %(roomName)s.', {roomName: name}) : _t('You are trying to access a room.') }
|
||||||
<br />
|
<br />
|
||||||
{ _tJsx("<a>Click here</a> to join the discussion!",
|
{ _t("<a>Click here</a> to join the discussion!",
|
||||||
/<a>(.*?)<\/a>/,
|
{},
|
||||||
(sub) => <a onClick={this.props.onJoinClick}><b>{ sub }</b></a>,
|
{ 'a': (sub) => <a onClick={this.props.onJoinClick}><b>{ sub }</b></a> },
|
||||||
) }
|
) }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
|
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { _t, _tJsx, _td } from '../../../languageHandler';
|
import { _t, _td } from '../../../languageHandler';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import sdk from '../../../index';
|
import sdk from '../../../index';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
|
@ -637,9 +637,7 @@ module.exports = React.createClass({
|
||||||
const ColorSettings = sdk.getComponent("room_settings.ColorSettings");
|
const ColorSettings = sdk.getComponent("room_settings.ColorSettings");
|
||||||
const UrlPreviewSettings = sdk.getComponent("room_settings.UrlPreviewSettings");
|
const UrlPreviewSettings = sdk.getComponent("room_settings.UrlPreviewSettings");
|
||||||
const RelatedGroupSettings = sdk.getComponent("room_settings.RelatedGroupSettings");
|
const RelatedGroupSettings = sdk.getComponent("room_settings.RelatedGroupSettings");
|
||||||
const EditableText = sdk.getComponent('elements.EditableText');
|
|
||||||
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||||
const Loader = sdk.getComponent("elements.Spinner");
|
|
||||||
|
|
||||||
const cli = MatrixClientPeg.get();
|
const cli = MatrixClientPeg.get();
|
||||||
const roomState = this.props.room.currentState;
|
const roomState = this.props.room.currentState;
|
||||||
|
@ -760,7 +758,7 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
var tagsSection = null;
|
var tagsSection = null;
|
||||||
if (canSetTag || self.state.tags) {
|
if (canSetTag || self.state.tags) {
|
||||||
var tagsSection =
|
tagsSection =
|
||||||
<div className="mx_RoomSettings_tags">
|
<div className="mx_RoomSettings_tags">
|
||||||
{ _t("Tagged as: ") }{ canSetTag ?
|
{ _t("Tagged as: ") }{ canSetTag ?
|
||||||
(tags.map(function(tag, i) {
|
(tags.map(function(tag, i) {
|
||||||
|
@ -790,10 +788,10 @@ module.exports = React.createClass({
|
||||||
if (this.state.join_rule === "public" && aliasCount == 0) {
|
if (this.state.join_rule === "public" && aliasCount == 0) {
|
||||||
addressWarning =
|
addressWarning =
|
||||||
<div className="mx_RoomSettings_warning">
|
<div className="mx_RoomSettings_warning">
|
||||||
{ _tJsx(
|
{ _t(
|
||||||
'To link to a room it must have <a>an address</a>.',
|
'To link to a room it must have <a>an address</a>.',
|
||||||
/<a>(.*?)<\/a>/,
|
{},
|
||||||
(sub) => <a href="#addresses">{ sub }</a>,
|
{ 'a': (sub) => <a href="#addresses">{ sub }</a> },
|
||||||
) }
|
) }
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@ -940,7 +938,7 @@ module.exports = React.createClass({
|
||||||
{ Object.keys(events_levels).map(function(event_type, i) {
|
{ Object.keys(events_levels).map(function(event_type, i) {
|
||||||
let label = plEventsToLabels[event_type];
|
let label = plEventsToLabels[event_type];
|
||||||
if (label) label = _t(label);
|
if (label) label = _t(label);
|
||||||
else label = _tJsx("To send events of type <eventType/>, you must be a", /<eventType\/>/, () => <code>{ event_type }</code>);
|
else label = _t("To send events of type <eventType/>, you must be a", {}, { 'eventType': () => <code>{ event_type }</code> });
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomSettings_powerLevel" key={event_type}>
|
<div className="mx_RoomSettings_powerLevel" key={event_type}>
|
||||||
<span className="mx_RoomSettings_powerLevelKey">{ label } </span>
|
<span className="mx_RoomSettings_powerLevelKey">{ label } </span>
|
||||||
|
|
|
@ -35,12 +35,9 @@ export function _td(s) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The translation function. This is just a simple wrapper to counterpart,
|
// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly
|
||||||
// but exists mostly because we must use the same counterpart instance
|
//Takes the same arguments as counterpart.translate()
|
||||||
// between modules (ie. here (react-sdk) and the app (riot-web), and if we
|
function safe_counterpart_translate(...args) {
|
||||||
// just import counterpart and use it directly, we end up using a different
|
|
||||||
// instance.
|
|
||||||
export function _t(...args) {
|
|
||||||
// Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191
|
// Horrible hack to avoid https://github.com/vector-im/riot-web/issues/4191
|
||||||
// The interpolation library that counterpart uses does not support undefined/null
|
// The interpolation library that counterpart uses does not support undefined/null
|
||||||
// values and instead will throw an error. This is a problem since everywhere else
|
// values and instead will throw an error. This is a problem since everywhere else
|
||||||
|
@ -51,11 +48,11 @@ export function _t(...args) {
|
||||||
if (args[1] && typeof args[1] === 'object') {
|
if (args[1] && typeof args[1] === 'object') {
|
||||||
Object.keys(args[1]).forEach((k) => {
|
Object.keys(args[1]).forEach((k) => {
|
||||||
if (args[1][k] === undefined) {
|
if (args[1][k] === undefined) {
|
||||||
console.warn("_t called with undefined interpolation name: " + k);
|
console.warn("safe_counterpart_translate called with undefined interpolation name: " + k);
|
||||||
args[1][k] = 'undefined';
|
args[1][k] = 'undefined';
|
||||||
}
|
}
|
||||||
if (args[1][k] === null) {
|
if (args[1][k] === null) {
|
||||||
console.warn("_t called with null interpolation name: " + k);
|
console.warn("safe_counterpart_translate called with null interpolation name: " + k);
|
||||||
args[1][k] = 'null';
|
args[1][k] = 'null';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -64,75 +61,112 @@ export function _t(...args) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Translates stringified JSX into translated JSX. E.g
|
* Translates text and optionally also replaces XML-ish elements in the text with e.g. React components
|
||||||
* _tJsx(
|
* @param {string} text The untranslated text, e.g "click <a>here</a> now to %(foo)s".
|
||||||
* "click <a href=''>here</a> now",
|
* @param {object} variables Variable substitutions, e.g { foo: 'bar' }
|
||||||
* /<a href=''>(.*?)<\/a>/,
|
* @param {object} tags Tag substitutions e.g. { 'a': (sub) => <a>{sub}</a> }
|
||||||
* (sub) => { return <a href=''>{ sub }</a>; }
|
|
||||||
* );
|
|
||||||
*
|
*
|
||||||
* @param {string} jsxText The untranslated stringified JSX e.g "click <a href=''>here</a> now".
|
* The values to substitute with can be either simple strings, or functions that return the value to use in
|
||||||
* This will be translated by passing the string through to _t(...)
|
* the substitution (e.g. return a React component). In case of a tag replacement, the function receives as
|
||||||
|
* the argument the text inside the element corresponding to the tag.
|
||||||
*
|
*
|
||||||
* @param {RegExp|RegExp[]} patterns A regexp to match against the translated text.
|
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
|
||||||
* The captured groups from the regexp will be fed to 'sub'.
|
|
||||||
* Only the captured groups will be included in the output, the match itself is discarded.
|
|
||||||
* If multiple RegExps are provided, the function at the same position will be called. The
|
|
||||||
* match will always be done from left to right, so the 2nd RegExp will be matched against the
|
|
||||||
* remaining text from the first RegExp.
|
|
||||||
*
|
|
||||||
* @param {Function|Function[]} subs A function which will be called
|
|
||||||
* with multiple args, each arg representing a captured group of the matching regexp.
|
|
||||||
* This function must return a JSX node.
|
|
||||||
*
|
|
||||||
* @return a React <span> component containing the generated text
|
|
||||||
*/
|
*/
|
||||||
export function _tJsx(jsxText, patterns, subs) {
|
export function _t(text, variables, tags) {
|
||||||
// convert everything to arrays
|
// Don't do subsitutions in counterpart. We hanle it ourselves so we can replace with React components
|
||||||
if (patterns instanceof RegExp) {
|
const args = Object.assign({ interpolate: false }, variables);
|
||||||
patterns = [patterns];
|
|
||||||
}
|
|
||||||
if (subs instanceof Function) {
|
|
||||||
subs = [subs];
|
|
||||||
}
|
|
||||||
// sanity checks
|
|
||||||
if (subs.length !== patterns.length || subs.length < 1) {
|
|
||||||
throw new Error(`_tJsx: programmer error. expected number of RegExps == number of Functions: ${subs.length} != ${patterns.length}`);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < subs.length; i++) {
|
|
||||||
if (!(patterns[i] instanceof RegExp)) {
|
|
||||||
throw new Error(`_tJsx: programmer error. expected RegExp for text: ${jsxText}`);
|
|
||||||
}
|
|
||||||
if (!(subs[i] instanceof Function)) {
|
|
||||||
throw new Error(`_tJsx: programmer error. expected Function for text: ${jsxText}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
|
// The translation returns text so there's no XSS vector here (no unsafe HTML, no code execution)
|
||||||
const tJsxText = _t(jsxText, {interpolate: false});
|
const translated = safe_counterpart_translate(text, args);
|
||||||
const output = [tJsxText];
|
|
||||||
|
return substitute(translated, variables, tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Similar to _t(), except only does substitutions, and no translation
|
||||||
|
* @param {string} text The text, e.g "click <a>here</a> now to %(foo)s".
|
||||||
|
* @param {object} variables Variable substitutions, e.g { foo: 'bar' }
|
||||||
|
* @param {object} tags Tag substitutions e.g. { 'a': (sub) => <a>{sub}</a> }
|
||||||
|
*
|
||||||
|
* The values to substitute with can be either simple strings, or functions that return the value to use in
|
||||||
|
* the substitution (e.g. return a React component). In case of a tag replacement, the function receives as
|
||||||
|
* the argument the text inside the element corresponding to the tag.
|
||||||
|
*
|
||||||
|
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
|
||||||
|
*/
|
||||||
|
export function substitute(text, variables, tags) {
|
||||||
|
const regexpMapping = {};
|
||||||
|
|
||||||
|
if(variables !== undefined) {
|
||||||
|
for (const variable in variables) {
|
||||||
|
regexpMapping[`%\\(${variable}\\)s`] = variables[variable];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(tags !== undefined) {
|
||||||
|
for (const tag in tags) {
|
||||||
|
regexpMapping[`(<${tag}>(.*?)<\\/${tag}>|<${tag}>|<${tag}\\s*\\/>)`] = tags[tag];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return replaceByRegexes(text, regexpMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Replace parts of a text using regular expressions
|
||||||
|
* @param {string} text The text on which to perform substitutions
|
||||||
|
* @param {object} mapping A mapping from regular expressions in string form to replacement string or a
|
||||||
|
* function which will receive as the argument the capture groups defined in the regexp. E.g.
|
||||||
|
* { 'Hello (.?) World': (sub) => sub.toUpperCase() }
|
||||||
|
*
|
||||||
|
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string
|
||||||
|
*/
|
||||||
|
export function replaceByRegexes(text, mapping) {
|
||||||
|
const output = [text];
|
||||||
|
|
||||||
|
let wrap = false; // Remember if the output needs to be wrapped later
|
||||||
|
for (const regexpString in mapping) {
|
||||||
|
const regexp = new RegExp(regexpString);
|
||||||
|
|
||||||
for (let i = 0; i < patterns.length; i++) {
|
|
||||||
// convert the last element in 'output' into 3 elements (pre-text, sub function, post-text).
|
// convert the last element in 'output' into 3 elements (pre-text, sub function, post-text).
|
||||||
// Rinse and repeat for other patterns (using post-text).
|
// Rinse and repeat for other patterns (using post-text).
|
||||||
const inputText = output.pop();
|
const inputText = output.pop();
|
||||||
const match = inputText.match(patterns[i]);
|
const match = inputText.match(regexp);
|
||||||
if (!match) {
|
if(!match) {
|
||||||
throw new Error(`_tJsx: translator error. expected translation to match regexp: ${patterns[i]}`);
|
output.push(inputText); // Push back input
|
||||||
|
continue; // Missing matches is entirely possible, because translation might change things
|
||||||
}
|
}
|
||||||
const capturedGroups = match.slice(1);
|
const capturedGroups = match.slice(2);
|
||||||
|
|
||||||
// Return the raw translation before the *match* followed by the return value of sub() followed
|
// Return the raw translation before the *match* followed by the return value of sub() followed
|
||||||
// by the raw translation after the *match* (not captured group).
|
// by the raw translation after the *match* (not captured group).
|
||||||
output.push(inputText.substr(0, match.index));
|
output.push(inputText.substr(0, match.index));
|
||||||
output.push(subs[i].apply(null, capturedGroups));
|
|
||||||
|
let toPush;
|
||||||
|
// If substitution is a function, call it
|
||||||
|
if(mapping[regexpString] instanceof Function) {
|
||||||
|
toPush = mapping[regexpString].apply(null, capturedGroups);
|
||||||
|
} else {
|
||||||
|
toPush = mapping[regexpString];
|
||||||
|
}
|
||||||
|
|
||||||
|
output.push(toPush);
|
||||||
|
|
||||||
|
// Check if we need to wrap the output into a span at the end
|
||||||
|
if(typeof toPush === 'object') {
|
||||||
|
wrap = true;
|
||||||
|
}
|
||||||
|
|
||||||
output.push(inputText.substr(match.index + match[0].length));
|
output.push(inputText.substr(match.index + match[0].length));
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is a bit of a fudge to avoid the 'Each child in an array or iterator
|
if(wrap) {
|
||||||
// should have a unique "key" prop' error: we explicitly pass the generated
|
// this is a bit of a fudge to avoid the 'Each child in an array or iterator
|
||||||
// nodes into React.createElement as children of a <span>.
|
// should have a unique "key" prop' error: we explicitly pass the generated
|
||||||
return React.createElement('span', null, ...output);
|
// nodes into React.createElement as children of a <span>.
|
||||||
|
return React.createElement('span', null, ...output);
|
||||||
|
} else {
|
||||||
|
return output.join('');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow overriding the text displayed when no translation exists
|
// Allow overriding the text displayed when no translation exists
|
||||||
|
|
Loading…
Reference in a new issue