diff --git a/src/languageHandler.js b/src/languageHandler.js index 474cd2b3cd..179bb2d1d0 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -179,12 +179,12 @@ export function replaceByRegexes(text, mapping) { for (const regexpString in mapping) { // TODO: Cache regexps - const regexp = new RegExp(regexpString); + const regexp = new RegExp(regexpString, "g"); // Loop over what output we have so far and perform replacements // We look for matches: if we find one, we get three parts: everything before the match, the replaced part, // and everything after the match. Insert all three into the output. We need to do this because we can insert objects. - // Otherwise there would be no need for the splitting and we could do simple replcement. + // Otherwise there would be no need for the splitting and we could do simple replacement. let matchFoundSomewhere = false; // If we don't find a match anywhere we want to log it for (const outputIndex in output) { const inputText = output[outputIndex]; @@ -192,44 +192,62 @@ export function replaceByRegexes(text, mapping) { continue; } - const match = inputText.match(regexp); - if (!match) { - continue; - } + // process every match in the string + // starting with the first + let match = regexp.exec(inputText); + + if (!match) continue; matchFoundSomewhere = true; - const capturedGroups = match.slice(2); - - // The textual part before the match + // The textual part before the first match const head = inputText.substr(0, match.index); - // The textual part after the match - const tail = inputText.substr(match.index + match[0].length); + const parts = []; + // keep track of prevMatch + let prevMatch; + while (match) { + // store prevMatch + prevMatch = match; + const capturedGroups = match.slice(2); - let replaced; - // If substitution is a function, call it - if (mapping[regexpString] instanceof Function) { - replaced = mapping[regexpString].apply(null, capturedGroups); - } else { - replaced = mapping[regexpString]; + let replaced; + // If substitution is a function, call it + if (mapping[regexpString] instanceof Function) { + replaced = mapping[regexpString].apply(null, capturedGroups); + } else { + replaced = mapping[regexpString]; + } + + if (typeof replaced === 'object') { + shouldWrapInSpan = true; + } + + // Here we also need to check that it actually is a string before comparing against one + // The head and tail are always strings + if (typeof replaced !== 'string' || replaced !== '') { + parts.push(replaced); + } + + // try the next match + match = regexp.exec(inputText); + + // add the text between prevMatch and this one + // or the end of the string if prevMatch is the last match + let tail; + if (match) { + const startIndex = prevMatch.index + prevMatch[0].length; + tail = inputText.substr(startIndex, match.index - startIndex); + } else { + tail = inputText.substr(prevMatch.index + prevMatch[0].length); + } + if (tail) { + parts.push(tail); + } } - if (typeof replaced === 'object') { - shouldWrapInSpan = true; - } - - output.splice(outputIndex, 1); // Remove old element - // Insert in reverse order as splice does insert-before and this way we get the final order correct - if (tail !== '') { - output.splice(outputIndex, 0, tail); - } - - // Here we also need to check that it actually is a string before comparing against one - // The head and tail are always strings - if (typeof replaced !== 'string' || replaced !== '') { - output.splice(outputIndex, 0, replaced); - } + // remove the old element at the same time + output.splice(outputIndex, 1, ...parts); if (head !== '') { // Don't push empty nodes, they are of no use output.splice(outputIndex, 0, head); diff --git a/test/i18n-test/languageHandler-test.js b/test/i18n-test/languageHandler-test.js index ce9f8e1684..0d96bc15ab 100644 --- a/test/i18n-test/languageHandler-test.js +++ b/test/i18n-test/languageHandler-test.js @@ -70,4 +70,15 @@ describe('languageHandler', function() { const text = '%(var1)s %(var2)s'; expect(languageHandler._t(text, { var2: 'val2', var1: 'val1' })).toBe('val1 val2'); }); + + it('multiple replacements of the same variable', function() { + const text = '%(var1)s %(var1)s'; + expect(languageHandler.substitute(text, { var1: 'val1' })).toBe('val1 val1'); + }); + + it('multiple replacements of the same tag', function() { + const text = 'Click here to join the discussion! or here'; + expect(languageHandler.substitute(text, {}, { 'a': (sub) => `x${sub}x` })) + .toBe('xClick herex to join the discussion! xor herex'); + }); });