Repository: lingui/js-lingui (ts)

Issue:
Escape bracket gets escaped in messages
**Describe the bug**
In linguijs v2, I was able to escape back ticks using `\`, however now in linguijs v3 the escape brackets are being escaped and thus breaking the translations when it tries to go and find the doubly escaped `\\`.

**To Reproduce**
```js
i18n._(t`Passing \`${argSet}\` in \`${fieldSet}\` is not supported.`)
```
Produces:
```
msgid "Passing \\`${argSet}\\` in \\`${fieldSet}\\` is not supported. "
msgstr "Passing \\`${argSet}\\` in \\`${fieldSet}\\` is not supported. "
```

**Expected behavior**
Previously in v2 running extract on the original string would produce
```
msgid "Passing `${argSet}` in `${fieldSet}` is not supported. "
msgstr "Passing `${argSet}` in `${fieldSet}` is not supported. "
```

- jsLingui version ``@lingui/cli@3.2.3``
- Babel version ``@babel/core@7.12.9``
- Your Babel config: Marcos Plugin, and ``@babel/preset-env, targets: node``


Pull request description:
fix: scaped literals double backslash formatting
Will close #897 

Patch:
diff --git a/docs/ref/conf.rst b/docs/ref/conf.rst
index b47fbb8c..de8b628d 100644
--- a/docs/ref/conf.rst
+++ b/docs/ref/conf.rst
@@ -416,6 +416,20 @@ You may use a different named export:
 
 .. config:: sourceLocale
 
+In some advanced cases you may also need to change the module from which
+`Trans` is imported. To do that, pass an object to `runtimeConfigModule`:
+
+.. code-block:: jsx
+
+   // If you import `i18n` object from custom module like this:
+   import { Trans, i18n } from "./custom-config"
+
+   // ... then add following line to Lingui configuration:
+   // "runtimeConfigModule": {
+   //   i18n: ["./custom-config", "i18n"],
+   //   Trans: ["./custom-config", "Trans"]
+   // }
+
 sourceLocale
 ------------
 
diff --git a/packages/conf/src/index.ts b/packages/conf/src/index.ts
index 9c09082b..335f3fb0 100644
--- a/packages/conf/src/index.ts
+++ b/packages/conf/src/index.ts
@@ -27,6 +27,8 @@ type DefaultLocaleObject = {
 }
 export type FallbackLocales = LocaleObject | DefaultLocaleObject | false
 
+type ModuleSource = [string, string?];
+
 export type LinguiConfig = {
   catalogs: CatalogConfig[]
   compileNamespace: string
@@ -39,7 +41,7 @@ export type LinguiConfig = {
   orderBy: OrderBy
   pseudoLocale: string
   rootDir: string
-  runtimeConfigModule: [string, string?]
+  runtimeConfigModule: ModuleSource | {[symbolName: string]: ModuleSource},
   sourceLocale: string
 }
 
diff --git a/packages/macro/src/index.ts b/packages/macro/src/index.ts
index 8d59470b..5163c7f6 100644
--- a/packages/macro/src/index.ts
+++ b/packages/macro/src/index.ts
@@ -5,7 +5,27 @@ import MacroJS from "./macroJs"
 import MacroJSX from "./macroJsx"
 
 const config = getConfig({ configPath: process.env.LINGUI_CONFIG })
-const [i18nImportModule, i18nImportName = "i18n"] = config.runtimeConfigModule
+
+const getSymbolSource = (name: string) => {
+  if (Array.isArray(config.runtimeConfigModule)) {
+    if (name === "i18n") {
+      return config.runtimeConfigModule
+    } else {
+      return ["@lingui/react", name]
+    }
+  } else {
+    if (
+      Object.prototype.hasOwnProperty.call(config.runtimeConfigModule, name)
+    ) {
+      return config.runtimeConfigModule[name]
+    } else {
+      return ["@lingui/react", name]
+    }
+  }
+}
+
+const [i18nImportModule, i18nImportName = "i18n"] = getSymbolSource("i18n")
+const [TransImportModule, TransImportName = "Trans"] = getSymbolSource("Trans")
 
 function macro({ references, state, babel }) {
   const jsxNodes = []
@@ -47,7 +67,7 @@ function macro({ references, state, babel }) {
   }
 
   if (jsxNodes.length) {
-    addImport(babel, state, "@lingui/react", "Trans")
+    addImport(babel, state, TransImportModule, TransImportName)
   }
 
   if (process.env.LINGUI_EXTRACT === "1") {
@@ -61,7 +81,8 @@ function addImport(babel, state, module, importName) {
   const { types: t } = babel
 
   const linguiImport = state.file.path.node.body.find(
-    (importNode) =>t.isImportDeclaration(importNode) &&
+    (importNode) =>
+      t.isImportDeclaration(importNode) &&
       importNode.source.value === module &&
       // https://github.com/lingui/js-lingui/issues/777
       importNode.importKind !== "type"
diff --git a/packages/macro/src/macroJs.ts b/packages/macro/src/macroJs.ts
index 1640a9ee..eb130b47 100644
--- a/packages/macro/src/macroJs.ts
+++ b/packages/macro/src/macroJs.ts
@@ -8,6 +8,7 @@ import { COMMENT, ID, MESSAGE, EXTRACT_MARK } from "./constants"
 
 const keepSpaceRe = /(?:\\(?:\r\n|\r|\n))+\s+/g
 const keepNewLineRe = /(?:\r\n|\r|\n)+\s+/g
+const removeExtraScapedLiterals = /(?:\\(.))/
 
 function normalizeWhitespace(text) {
   return text.replace(keepSpaceRe, " ").replace(keepNewLineRe, "\n").trim()
@@ -269,7 +270,7 @@ export default class MacroJs {
 
           return {
             type: "text",
-            value,
+            value: this.clearBackslashes(value),
           }
         }),
         expressions: R.map((exp: babelTypes.Expression) =>
@@ -279,7 +280,6 @@ export default class MacroJs {
         ),
       }),
       (exp) => zip(exp.quasis, exp.expressions),
-      // @ts-ignore
       R.flatten,
       R.filter(Boolean)
     )
@@ -356,6 +356,13 @@ export default class MacroJs {
     }
   }
 
+  /**
+   * We clean '//\` ' to just '`'
+   */
+  clearBackslashes(value: string) {
+    return value.replace(removeExtraScapedLiterals, "`")
+  }
+
   /**
    * Custom matchers
    */
diff --git a/packages/macro/src/macroJsx.ts b/packages/macro/src/macroJsx.ts
index 3dbd8344..3a0d8603 100644
--- a/packages/macro/src/macroJsx.ts
+++ b/packages/macro/src/macroJsx.ts
@@ -7,6 +7,7 @@ import { zip, makeCounter } from "./utils"
 import { ID, COMMENT, MESSAGE } from "./constants"
 
 const pluralRuleRe = /(_[\d\w]+|zero|one|two|few|many|other)/
+const removeExtraScapedLiterals = /(?:\\(.))/
 const jsx2icuExactChoice = (value) =>
   value.replace(/_(\d+)/, "=$1").replace(/_(\w+)/, "$1")
 
@@ -233,7 +234,7 @@ export default class MacroJSX {
               const value = text.value.raw
               if (value === "") return null
 
-              return this.tokenizeText(value)
+              return this.tokenizeText(this.clearBackslashes(value))
             }),
             expressions: R.map((exp: babelTypes.Expression) =>
               this.types.isCallExpression(exp)
@@ -345,6 +346,13 @@ export default class MacroJSX {
     return this.types.isIdentifier(exp) ? exp.name : this.expressionIndex()
   }
 
+  /**
+   * We clean '//\` ' to just '`'
+   */
+  clearBackslashes(value: string) {
+    return value.replace(removeExtraScapedLiterals, "`")
+  }
+
   /**
    * Custom matchers
    */


Test patch:
diff --git a/packages/macro/test/js-t.ts b/packages/macro/test/js-t.ts
index b1bf7666..76ba3c95 100644
--- a/packages/macro/test/js-t.ts
+++ b/packages/macro/test/js-t.ts
@@ -26,6 +26,32 @@ export default [
         })
     `,
   },
+  {
+    name: "Variables with scaped template literals are correctly formatted",
+    input: `
+        import { t } from '@lingui/macro';
+        t\`Variable \\\`\${name}\\\`\`;
+    `,
+    expected: `
+        import { i18n } from "@lingui/core";
+        /*i18n*/
+        i18n._("Variable \`{name}\`", {
+          name: name
+        })
+    `,
+  },
+  {
+    name: "Variables with scaped double quotes are correctly formatted",
+    input: `
+        import { t } from '@lingui/macro';
+        t\`Variable \"name\" \`;
+    `,
+    expected: `
+        import { i18n } from "@lingui/core";
+        /*i18n*/
+        i18n._("Variable \\"name\\"")
+    `,
+  },
   {
     name: "Variables should be deduplicated",
     input: `