Repository: lingui/js-lingui (ts)

Issue:
Function `t` macros do not extract template strings
**Describe the bug**
You cannot use template strings with the t function:
```
t({message: `template`})
```
results in
```
msgid "undefined"
msgstr "undefined"
```

**Expected behavior**
```
msgid "template"
msgstr "template"
```

**Additional context**

- jsLingui version 3.2.1
- Babel version 7.12.3
- Your Babel config: Create React App


Pull request:
fix: parse template strings in t and defineMessage macros
Closes #861

Patch:
diff --git a/.github/workflows/deploy-main-docs.yml b/.github/workflows/deploy-main-docs.yml
index 9f34d47d..d81b2f61 100644
--- a/.github/workflows/deploy-main-docs.yml
+++ b/.github/workflows/deploy-main-docs.yml
@@ -41,4 +41,4 @@ jobs:
         run: |
           cd docs/
           ./vercel-build.sh
-          npx vercel --public  --prod --token ${{secrets.NOW_TOKEN}} --name lingui-docs _build/html
+          npx vercel --public --yes --prod --token ${{secrets.NOW_TOKEN}} --name lingui-docs _build/html
diff --git a/.gitignore b/.gitignore
index 2eda5b51..f3d1a007 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ node_modules/
 build/packages/
 .yalc/
 yalc.lock
+packages/**/build
 docs/_build/
 
 npm-debug.log
diff --git a/docs/releases/migration-3.rst b/docs/releases/migration-3.rst
index fcb7ae8b..7e3bc621 100644
--- a/docs/releases/migration-3.rst
+++ b/docs/releases/migration-3.rst
@@ -282,3 +282,8 @@ New features
 
 Event ``change`` is fired anytime new catalogs are loaded or when locale
 is activated.
+
+`Native TypeScript support`
+--------------------------
+
+Lingui now supports TypeScript out of the box, don't forget to remove the `@types/lingui` packages from your project. 
diff --git a/lerna.json b/lerna.json
index 660f8eba..22d112d5 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
 {
-  "version": "3.2.0",
+  "version": "3.2.1",
   "packages": [
     "packages/*"
   ],
diff --git a/package.json b/package.json
index e5af9036..2f9f8416 100644
--- a/package.json
+++ b/package.json
@@ -22,8 +22,8 @@
     "release:test": "node ./scripts/test.js",
     "version:next": "lerna version --no-private --preid next --create-release github --conventional-commits --conventional-prerelease --yes",
     "version:latest": "lerna version --no-private --create-release github --conventional-commits --yes",
-    "release:latest": "lerna publish from-package --dist-tag latest --yes",
-    "release:next": "lerna publish from-package --canary --preid next --pre-dist-tag next --yes",
+    "release:latest": "lerna publish from-package --contents build --dist-tag latest --yes",
+    "release:next": "lerna publish from-package --contents build --canary --preid next --pre-dist-tag next --yes",
     "build:docs": "cd docs && ./vercel-build.sh"
   },
   "engines": {
diff --git a/packages/babel-plugin-extract-messages/package.json b/packages/babel-plugin-extract-messages/package.json
index d5f6d1c9..2e66566c 100644
--- a/packages/babel-plugin-extract-messages/package.json
+++ b/packages/babel-plugin-extract-messages/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lingui/babel-plugin-extract-messages",
-  "version": "3.2.0",
+  "version": "3.2.1",
   "description": "Babel plugin for collecting messages from source code for internationalization",
   "main": "index.js",
   "author": {
diff --git a/packages/cli/package.json b/packages/cli/package.json
index d770cb2c..8a8e9d91 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lingui/cli",
-  "version": "3.2.0",
+  "version": "3.2.1",
   "description": "CLI for working wit message catalogs",
   "keywords": [
     "cli",
diff --git a/packages/conf/package.json b/packages/conf/package.json
index c153d644..4ea22776 100644
--- a/packages/conf/package.json
+++ b/packages/conf/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lingui/conf",
-  "version": "3.2.0",
+  "version": "3.2.1",
   "description": "Get lingui configuration from package.json",
   "keywords": [
     "lingui",
diff --git a/packages/core/package.json b/packages/core/package.json
index b60859bb..70125706 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lingui/core",
-  "version": "3.2.0",
+  "version": "3.2.1",
   "description": "I18n tools for javascript",
   "main": "index.js",
   "types": "cjs/index.d.ts",
diff --git a/packages/detect-locale/package.json b/packages/detect-locale/package.json
index 7f26f65e..39aeff92 100644
--- a/packages/detect-locale/package.json
+++ b/packages/detect-locale/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lingui/detect-locale",
-  "version": "3.2.0",
+  "version": "3.2.1",
   "description": "@Lingui package to help you find the correct browser/server locale",
   "main": "index.js",
   "types": "cjs/index.d.ts",
diff --git a/packages/loader/package.json b/packages/loader/package.json
index c625876b..acb779ed 100644
--- a/packages/loader/package.json
+++ b/packages/loader/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lingui/loader",
-  "version": "3.2.0",
+  "version": "3.2.1",
   "description": "webpack loader for lingui message catalogs",
   "main": "index.js",
   "author": {
diff --git a/packages/macro/package.json b/packages/macro/package.json
index 801c1b57..64e219f3 100644
--- a/packages/macro/package.json
+++ b/packages/macro/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lingui/macro",
-  "version": "3.2.0",
+  "version": "3.2.1",
   "description": "Macro for generating messages in ICU MessageFormat syntax",
   "main": "index.ts",
   "author": {
diff --git a/packages/macro/src/macroJs.ts b/packages/macro/src/macroJs.ts
index e57207db..1640a9ee 100644
--- a/packages/macro/src/macroJs.ts
+++ b/packages/macro/src/macroJs.ts
@@ -193,7 +193,12 @@ export default class MacroJs {
 
     // if there's `message` property, replace macros with formatted message
     const node = descriptor.properties[messageIndex]
-    const tokens = this.tokenizeNode(node.value, true)
+
+    // Inside message descriptor the `t` macro in `message` prop is optional.
+    // Template strings are always processed as if they were wrapped by `t`.
+    const tokens = this.types.isTemplateLiteral(node.value)
+      ? this.tokenizeTemplateLiteral(node.value)
+      : this.tokenizeNode(node.value, true)
 
     let messageNode = node.value
     if (tokens != null) {
@@ -351,7 +356,6 @@ export default class MacroJs {
     }
   }
 
-
   /**
    * Custom matchers
    */
diff --git a/packages/react/package.json b/packages/react/package.json
index 89c9fed6..1285caa6 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@lingui/react",
-  "version": "3.2.0",
+  "version": "3.2.1",
   "description": "React components for translations",
   "main": "index.js",
   "types": "cjs/index.d.ts",
diff --git a/scripts/build/packaging.js b/scripts/build/packaging.js
index 2ee2d08f..95a05cff 100644
--- a/scripts/build/packaging.js
+++ b/scripts/build/packaging.js
@@ -23,13 +23,13 @@ function getBundleOutputPaths(bundleType, filename, packageName) {
   switch (bundleType) {
     case NOOP:
     case NODE:
-      return [`build/packages/${name}/${_filename}`]
+      return [`packages/${name}/build/${_filename}`]
     case UNIVERSAL:
-      return [`build/packages/${name}/cjs/${_filename}`]
+      return [`packages/${name}/build/cjs/${_filename}`]
     // case UMD_DEV:
     // case UMD_PROD:
     //   return [
-    //     `build/packages/${packageName}/umd/${filename}`,
+    //     `packages/build/${packageName}/umd/${filename}`,
     //     `build/dist/${filename}`
     //   ]
     default:
@@ -44,7 +44,7 @@ function getTarOptions(tgzName, packageName) {
   const CONTENTS_FOLDER = "package"
   return {
     src: tgzName,
-    dest: `build/packages/${packageName}`,
+    dest: `packages/${packageName}/build`,
     tar: {
       entries: [CONTENTS_FOLDER],
       map(header) {
@@ -57,34 +57,36 @@ function getTarOptions(tgzName, packageName) {
 }
 
 async function prepareNpmPackage(name) {
+  if (!existsSync(`packages/${name}/build`)) {
+    // We didn't build any npm packages.
+    return
+  }
+
   await Promise.all([
-    asyncCopyTo("LICENSE", `build/packages/${name}/LICENSE`),
+    asyncCopyTo("LICENSE", `packages/${name}/build/LICENSE`),
     asyncCopyTo(
       `packages/${name}/package.json`,
-      `build/packages/${name}/package.json`
+      `packages/${name}/build/package.json`
     ),
     asyncCopyTo(
       `packages/${name}/README.md`,
-      `build/packages/${name}/README.md`
+      `packages/${name}/build/README.md`
     ),
-    asyncCopyTo(`packages/${name}/npm`, `build/packages/${name}`)
+    asyncCopyTo(`packages/${name}/npm`, `packages/${name}/build/`)
   ])
   const tgzName = (await asyncExecuteCommand(
-    `npm pack build/packages/${name}`
+    `npm pack packages/${name}/build`
   )).trim()
-  await asyncRimRaf(`build/packages/${name}`)
+  await asyncRimRaf(`packages/${name}/build/`)
   await asyncExtractTar(getTarOptions(tgzName, name))
   unlinkSync(tgzName)
 }
 
 async function prepareNpmPackages() {
-  if (!existsSync("build/packages")) {
-    // We didn't build any npm packages.
-    return
-  }
-  const builtPackageFolders = readdirSync("build/packages").filter(
+  const builtPackageFolders = readdirSync("packages/").filter(
     dir => dir.charAt(0) !== "."
   )
+
   await Promise.all(builtPackageFolders.map(prepareNpmPackage))
 }
 
diff --git a/scripts/integration.js b/scripts/integration.js
index d28ff071..a1b11ee6 100644
--- a/scripts/integration.js
+++ b/scripts/integration.js
@@ -2,7 +2,7 @@ const fs = require("fs")
 const path = require("path")
 const { execSync } = require("child_process")
 
-const BUILD_DIR = path.resolve("build/packages")
+const BUILD_DIR = path.resolve("packages/")
 const EXAMPLES_DIR = path.resolve("examples")
 
 function listDirs(dir) {
@@ -23,7 +23,7 @@ function linkDependencies(example) {
 }
 
 function linkPackage(packageName) {
-  execSync("yalc publish", { cwd: path.join(BUILD_DIR, packageName) })
+  execSync("yalc publish", { cwd: path.join(BUILD_DIR, packageName, "build") })
 }
 
 const packages = listDirs(BUILD_DIR)
diff --git a/scripts/jest/config.integration.js b/scripts/jest/config.integration.js
index e38f0be4..ef42d6e2 100644
--- a/scripts/jest/config.integration.js
+++ b/scripts/jest/config.integration.js
@@ -26,11 +26,11 @@ packages
   .filter(name => !DEV_PACKAGES.includes(name))
   .forEach(name => {
     // Root entry point
-    moduleNameMapper[`^@lingui/${name}$`] = `<rootDir>/build/packages/${name}`
+    moduleNameMapper[`^@lingui/${name}$`] = `<rootDir>/packages/${name}/build`
     // Named entry points
     moduleNameMapper[
       `^@lingui/${name}/(.*)$`
-    ] = `<rootDir>/build/packages/${name}/$1`
+    ] = `<rootDir>/packages/${name}/build/$1`
   })
 
 module.exports = Object.assign({}, sourceConfig, {
@@ -41,8 +41,12 @@ module.exports = Object.assign({}, sourceConfig, {
   setupFiles: ['set-tz/utc'],
 
   // Exclude the build output from transforms
-  transformIgnorePatterns: ["/node_modules/", "<rootDir>/build/"],
-  modulePathIgnorePatterns: [".yalc/"],
+  transformIgnorePatterns: ["/node_modules/", "<rootDir>/packages/*/build/"],
+  modulePathIgnorePatterns: [".yalc/", "/build"],
 
-  collectCoverage: false
+  collectCoverage: false,
+
+  haste: {
+    throwOnModuleCollision: false,
+  }
 })
diff --git a/scripts/jest/config.unit.js b/scripts/jest/config.unit.js
index 5839b5e6..33acd3a4 100644
--- a/scripts/jest/config.unit.js
+++ b/scripts/jest/config.unit.js
@@ -17,6 +17,7 @@ module.exports = {
     ".*.js.snap$",
   ],
   coverageReporters: ["html", "lcov", "text"],
+  modulePathIgnorePatterns: ["/build"],
 
   reporters: ["default", "jest-junit"],
   setupFiles: ['set-tz/utc'],
diff --git a/scripts/release.js b/scripts/release.js
index d475c45d..b7660829 100644
--- a/scripts/release.js
+++ b/scripts/release.js
@@ -10,7 +10,7 @@ const semver = require("semver")
 const R = require("ramda")
 
 const BUILD_DIR = "build/"
-const PACKAGES_DIR = "build/packages"
+const PACKAGES_DIR = "packages/build"
 
 const npmTagForBranch = {
   main: "latest",


Test patch:
diff --git a/packages/macro/test/js-defineMessage.ts b/packages/macro/test/js-defineMessage.ts
index d44a7455..bf4efa47 100644
--- a/packages/macro/test/js-defineMessage.ts
+++ b/packages/macro/test/js-defineMessage.ts
@@ -36,11 +36,11 @@ export default [
     `,
   },
   {
-    name: "should left string message intact - template literal",
+    name: "should transform template literals",
     input: `
         import { defineMessage } from '@lingui/macro';
         const message = defineMessage({
-          message: \`Message\`
+          message: \`Message \${name}\`
         })
     `,
     expected: `
@@ -48,7 +48,10 @@ export default [
         const message =
           /*i18n*/
           {
-            id: \`Message\`
+            id: "Message {name}",
+            values: {
+              name: name
+            }
           };
     `,
   },
diff --git a/packages/macro/test/js-t.ts b/packages/macro/test/js-t.ts
index 4f41b649..b1bf7666 100644
--- a/packages/macro/test/js-t.ts
+++ b/packages/macro/test/js-t.ts
@@ -82,6 +82,23 @@ export default [
         i18n._("Multiline\\nstring")
       `,
   },
+  {
+    name: "Support template strings in t macro message",
+    input: `
+        import { t } from '@lingui/macro'
+        const msg = t({ message: \`Hello \${name}\` })
+      `,
+    expected: `import { i18n } from "@lingui/core";
+    const msg =
+      i18n._(/*i18n*/
+        {
+          id: "Hello {name}",
+          values: {
+            name: name,
+          },
+        });
+      `,
+  },
   {
     name: "Support id and comment in t macro as callExpression",
     input: `