Clarifications

pull/21833/head
Stefan Parviainen 2017-11-14 19:55:47 +01:00
parent cdd03dd1c5
commit 788be67c75
3 changed files with 28 additions and 12 deletions

View File

@ -52,6 +52,10 @@ export default function SenderProfile(props) {
return ( return (
<div className="mx_SenderProfile" dir="auto" onClick={props.onClick}> <div className="mx_SenderProfile" dir="auto" onClick={props.onClick}>
// The text surrounding the user name must be wrapped in order for it to have the correct opacity.
// It is not possible to wrap the whole thing, because the user name might contain flair which should
// be shown at full opacity. Sadly CSS does not make it possible to "reset" opacity so we have to do it
// in parts like this. Sometimes CSS makes me a sad panda :-(
{ content.props.children[0] ? { content.props.children[0] ?
<span className='mx_SenderProfile_aux'>{ content.props.children[0] }</span> : '' <span className='mx_SenderProfile_aux'>{ content.props.children[0] }</span> : ''
} }

View File

@ -36,7 +36,7 @@ export function _td(s) {
} }
// Wrapper for counterpart's translation function so that it handles nulls and undefineds properly // Wrapper for counterpart's translation function so that it handles nulls and undefineds properly
//Takes the same arguments as counterpart.translate() // Takes the same arguments as counterpart.translate()
function safe_counterpart_translate(...args) { function safe_counterpart_translate(...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
@ -66,14 +66,20 @@ function safe_counterpart_translate(...args) {
* @param {object} variables Variable substitutions, e.g { foo: 'bar' } * @param {object} variables Variable substitutions, e.g { foo: 'bar' }
* @param {object} tags Tag substitutions e.g. { 'a': (sub) => <a>{sub}</a> } * @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 * In both variables and tags, the values to substitute with can be either simple strings, React components,
* the substitution (e.g. return a React component). In case of a tag replacement, the function receives as * or functions that return the value to use in the substitution (e.g. return a React component). In case of
* the argument the text inside the element corresponding to the tag. * a tag replacement, the function receives as the argument the text inside the element corresponding to the tag.
*
* Use tag substitutions if you need to translate text between tags (e.g. "<a>Click here!</a>"), otherwise
* you will end up with literal "<a>" in your output, rather than HTML. Note that you can also use variable
* substitution to insert React components, but you can't use it to translate text between tags.
* *
* @return a React <span> component if any non-strings were used in substitutions, otherwise a string * @return a React <span> component if any non-strings were used in substitutions, otherwise a string
*/ */
export function _t(text, variables, tags) { export function _t(text, variables, tags) {
// Don't do subsitutions in counterpart. We hanle it ourselves so we can replace with React components // Don't do subsitutions in counterpart. We handle it ourselves so we can replace with React components
// However, still pass the variables to counterpart so that it can choose the correct plural if count is given
// It is enough to pass the count variable, but in the future counterpart might make use of other information too
const args = Object.assign({ interpolate: false }, variables); const args = Object.assign({ interpolate: false }, variables);
// 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)
@ -123,15 +129,18 @@ export function substitute(text, variables, tags) {
export function replaceByRegexes(text, mapping) { export function replaceByRegexes(text, mapping) {
const output = [text]; const output = [text];
let wrap = false; // Remember if the output needs to be wrapped later // If we insert any components we need to wrap the output in a span. React doesn't like just an array of components.
let shouldWrapInSpan = false;
for (const regexpString in mapping) { for (const regexpString in mapping) {
// TODO: Cache regexps
const regexp = new RegExp(regexpString); const regexp = new RegExp(regexpString);
// 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(regexp); const match = inputText.match(regexp);
if(!match) { if (!match) {
output.push(inputText); // Push back input output.push(inputText); // Push back input
continue; // Missing matches is entirely possible, because translation might change things continue; // Missing matches is entirely possible, because translation might change things
} }
@ -160,7 +169,7 @@ export function replaceByRegexes(text, mapping) {
} }
if(typeof replaced === 'object') { if(typeof replaced === 'object') {
wrap = true; shouldWrapInSpan = true;
} }
const tail = inputText.substr(match.index + match[0].length); const tail = inputText.substr(match.index + match[0].length);
@ -169,10 +178,7 @@ export function replaceByRegexes(text, mapping) {
} }
} }
if(wrap) { if(shouldWrapInSpan) {
// this is a bit of a fudge to avoid the 'Each child in an array or iterator
// should have a unique "key" prop' error: we explicitly pass the generated
// nodes into React.createElement as children of a <span>.
return React.createElement('span', null, ...output); return React.createElement('span', null, ...output);
} else { } else {
return output.join(''); return output.join('');

View File

@ -54,6 +54,12 @@ describe('languageHandler', function() {
.toEqual((<span>You are now ignoring <i>foo</i></span>)); .toEqual((<span>You are now ignoring <i>foo</i></span>));
}); });
it('variable substitution with plain React component', function() {
const text = 'You are now ignoring %(userId)s';
expect(languageHandler._t(text, { userId: <i>foo</i> }))
.toEqual((<span>You are now ignoring <i>foo</i></span>));
});
it('tag substitution with React component', function() { it('tag substitution with React component', function() {
const text = 'Press <StartChatButton> to start a chat with someone'; const text = 'Press <StartChatButton> to start a chat with someone';
expect(languageHandler._t(text, {}, { 'StartChatButton': () => <i>foo</i> })) expect(languageHandler._t(text, {}, { 'StartChatButton': () => <i>foo</i> }))