From b5daba90261df41b8d39d98966a48c0a113b416e Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 22 Aug 2019 18:17:08 +0100 Subject: [PATCH 1/3] Iterate over all instances of variable/tag for _t substitutions Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/languageHandler.js | 78 +++++++++++++++----------- test/i18n-test/languageHandler-test.js | 11 ++++ 2 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/languageHandler.js b/src/languageHandler.js index 474cd2b3cd..9e354cee9e 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,58 @@ 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 + if (match) { + const startIndex = prevMatch.index + prevMatch[0].length; + parts.push(inputText.substr(startIndex, match.index - startIndex)); + } else { + parts.push(inputText.substr(prevMatch.index + prevMatch[0].length)); + } } - 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..07e3f2cb8b 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._t(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._t(text, {}, { 'a': (sub) => `x${sub}x` })) + .toBe('xClick herex to join the discussion! xor herex'); + }); }); From 310457059b32b0503c8575d8a6093599000e4ade Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 22 Aug 2019 18:31:02 +0100 Subject: [PATCH 2/3] [i18n] only append tail if it is actually needed Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/languageHandler.js | 12 ++++++++++-- test/i18n-test/languageHandler-test.js | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/languageHandler.js b/src/languageHandler.js index 9e354cee9e..e5656e5f69 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -177,6 +177,10 @@ export function replaceByRegexes(text, mapping) { // 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; + if (text === "You are now ignoring %(userId)s") { + debugger; + } + for (const regexpString in mapping) { // TODO: Cache regexps const regexp = new RegExp(regexpString, "g"); @@ -233,11 +237,15 @@ export function replaceByRegexes(text, mapping) { // 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; - parts.push(inputText.substr(startIndex, match.index - startIndex)); + tail = inputText.substr(startIndex, match.index - startIndex); } else { - parts.push(inputText.substr(prevMatch.index + prevMatch[0].length)); + tail = inputText.substr(prevMatch.index + prevMatch[0].length); + } + if (tail) { + parts.push(tail); } } diff --git a/test/i18n-test/languageHandler-test.js b/test/i18n-test/languageHandler-test.js index 07e3f2cb8b..0d96bc15ab 100644 --- a/test/i18n-test/languageHandler-test.js +++ b/test/i18n-test/languageHandler-test.js @@ -73,12 +73,12 @@ describe('languageHandler', function() { it('multiple replacements of the same variable', function() { const text = '%(var1)s %(var1)s'; - expect(languageHandler._t(text, { var1: 'val1' })).toBe('val1 val1'); + 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._t(text, {}, { 'a': (sub) => `x${sub}x` })) + expect(languageHandler.substitute(text, {}, { 'a': (sub) => `x${sub}x` })) .toBe('xClick herex to join the discussion! xor herex'); }); }); From 7d511fbbc5d0513f8575464cd58d4e0c65bea9f4 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 22 Aug 2019 18:34:26 +0100 Subject: [PATCH 3/3] remove leftover debugger =) Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- src/languageHandler.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/languageHandler.js b/src/languageHandler.js index e5656e5f69..179bb2d1d0 100644 --- a/src/languageHandler.js +++ b/src/languageHandler.js @@ -177,10 +177,6 @@ export function replaceByRegexes(text, mapping) { // 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; - if (text === "You are now ignoring %(userId)s") { - debugger; - } - for (const regexpString in mapping) { // TODO: Cache regexps const regexp = new RegExp(regexpString, "g");