diff --git a/app/assets/javascripts/locale/sprintf.js b/app/assets/javascripts/locale/sprintf.js
index c8c6b51f374ec4d528d4d4a3e1fdf0db255122c2..12df67670f9bc012f4a12f1c6445b304d5913764 100644
--- a/app/assets/javascripts/locale/sprintf.js
+++ b/app/assets/javascripts/locale/sprintf.js
@@ -22,7 +22,9 @@ export default (input, parameters, escapeParameters = true) => {
     mappedParameters.forEach((key, parameterName) => {
       const parameterValue = mappedParameters.get(parameterName);
       const escapedParameterValue = escapeParameters ? escape(parameterValue) : parameterValue;
-      output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), escapedParameterValue);
+      // Pass the param value as a function to ignore special replacement patterns like $` and $'.
+      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#syntax
+      output = output.replace(new RegExp(`%{${parameterName}}`, 'g'), () => escapedParameterValue);
     });
   }
 
diff --git a/spec/frontend/locale/sprintf_spec.js b/spec/frontend/locale/sprintf_spec.js
index e0d0e117ea433f6219a8e0951c326ff4fecc5df5..a7e245e2b7864febfbb29bfe5163d6d596641a90 100644
--- a/spec/frontend/locale/sprintf_spec.js
+++ b/spec/frontend/locale/sprintf_spec.js
@@ -84,5 +84,16 @@ describe('locale', () => {
         expect(output).toBe('contains duplicated 15%');
       });
     });
+
+    describe('ignores special replacements in the input', () => {
+      it.each(['$$', '$&', '$`', `$'`])('replacement "%s" is ignored', (replacement) => {
+        const input = 'My odd %{replacement} is preserved';
+
+        const parameters = { replacement };
+
+        const output = sprintf(input, parameters, false);
+        expect(output).toBe(`My odd ${replacement} is preserved`);
+      });
+    });
   });
 });