Repository: lingui/js-lingui (ts)

Issue:
@lingui/cli v3 extract extracts no messages
**Describe the bug**
When I run `yarn lingui extract` the po files gets created in the correct location, but they contain no messages, only header lines. 

**To Reproduce**
* This is a Expo (react-native) project. 
* Using `@lingui/react` for `useLingui` hook only. 
* Using only macros, no `@lingui/react` components.

```
import { Trans, t } from "@lingui/macro"

export default function App() {
   return (<View>
     <Trans>Tohle přelož!</Trans>
     <Button title=t`Zavřít` />
   </View>)
}
```
Running command `yarn lingui extract` with following output: 
```
yarn run v1.22.4
$ /<my project dir>/node_modules/.bin/lingui extract
(use "yarn lingui extract" to update catalogs with new messages)
(use "yarn lingui compile" to compile catalogs for production)
Done in 0.74s.
```
Po files are created, but contain only header lines as follows: 
```
$ cat assets/locale/cs.po 
msgid ""
msgstr ""
"POT-Creation-Date: 2020-06-19 15:20+0200\n"
"Mime-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: @lingui/cli\n"
"Language: cs\n"
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"Plural-Forms: \n"
```

**Expected behavior**
Messages should be extracted into po files. Extraction worked with v2 cli. 

**Additional context**
- jsLingui version `yarn lingui --version`
```3.0.0-13```

- Babel version `npm list @babel/core`
```
├── @babel/core@7.10.2 
└─┬ babel-preset-expo@8.2.1
  ├─┬ @babel/preset-env@7.9.6
  │ ├─┬ @babel/plugin-proposal-nullish-coalescing-operator@7.8.3
  │ │ └── @babel/core@7.9.6  extraneous
  │ ├─┬ @babel/plugin-proposal-object-rest-spread@7.9.6
  │ │ └── @babel/core@7.9.6  extraneous
  │ ├─┬ @babel/plugin-proposal-optional-catch-binding@7.8.3
  │ │ └── @babel/core@7.9.6  extraneous
  │ ├─┬ @babel/plugin-proposal-optional-chaining@7.9.0
  │ │ └── @babel/core@7.9.6  extraneous
  │ ├─┬ @babel/plugin-transform-async-to-generator@7.8.3
  │ │ └── @babel/core@7.9.6  extraneous
  │ └─┬ @babel/plugin-transform-modules-commonjs@7.9.6
  │   └── @babel/core@7.9.6  extraneous
  └─┬ metro-react-native-babel-preset@0.56.4
    ├─┬ @babel/plugin-proposal-class-properties@7.8.3
    │ └── @babel/core@7.9.6  extraneous
    └─┬ @babel/plugin-transform-flow-strip-types@7.9.0
      └── @babel/core@7.9.6  extraneous
```

- Babel config (e.g. `.babelrc`) or framework you use (Create React App, Meteor, etc.)
```
$ cat babel.config.js 
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['macros'],
  };
}
```

- This is my `.linguirc`
```
{
   "locales": [ "cs", "en", "de", "pl" ],
   "catalogs": [
    {
      "path": "assets/locale/{locale}",
      "include": [ "js", "App.js" ],
    },
   ],
   "format": "po",
   "sourceLocale": "cs"
}
```

- my lingui stuff versions: 
```yarn list --pattern @lingui
yarn list v1.22.4
├─ @lingui/babel-plugin-extract-messages@3.0.0-13
├─ @lingui/cli@3.0.0-13
├─ @lingui/conf@3.0.0-13
├─ @lingui/core@3.0.0-13
├─ @lingui/macro@3.0.0-13
└─ @lingui/react@3.0.0-13
```

I am aware v3 is not in production. Does that mean extraction don't work yet? https://github.com/lingui/js-lingui/issues/334

I have seen this bug, but it seems to be related to v2 and react components. 
https://github.com/lingui/js-lingui/issues/415

Thanks in advance

Pull request description:
Allow multiple paths in catalog.include
Closes #709 

Patch:
diff --git a/packages/cli/src/api/catalog.ts b/packages/cli/src/api/catalog.ts
index 31e5096f..bc797b3d 100644
--- a/packages/cli/src/api/catalog.ts
+++ b/packages/cli/src/api/catalog.ts
@@ -281,10 +281,13 @@ export class Catalog {
 
   get sourcePaths() {
     const includeGlobs = this.include.map(
-      (includePath) => `${includePath}${PATHSEP}**${PATHSEP}*.*`
+      (includePath) =>
+        includePath.endsWith(PATHSEP)
+          ? [includePath, "**", "*.*"].join(PATHSEP) // directory
+          : includePath // file
     )
     const patterns =
-      includeGlobs.length > 1 ? `{${includeGlobs.join("|")}` : includeGlobs[0]
+      includeGlobs.length > 1 ? `{${includeGlobs.join(",")}}` : includeGlobs[0]
     return glob.sync(patterns, { ignore: this.exclude })
   }
 
@@ -489,15 +492,14 @@ const ensureArray = <T>(value: Array<T> | T | null | undefined): Array<T> => {
  * Preserve absolute paths:    /absolute/path => /absolute/path
  */
 export function normalizeRelativePath(sourcePath: string): string {
-  const sourcePathPosix = normalize(sourcePath)
-
-  // absolute path, do nothing
-  if (path.isAbsolute(sourcePathPosix)) return sourcePathPosix
+  if (path.isAbsolute(sourcePath)) {
+    // absolute path
+    return normalize(sourcePath, false)
+  }
 
-  // preserve trailing slash for directories
   const isDir = normalize(sourcePath, false).endsWith(PATHSEP)
   return (
-    normalize(path.relative(process.cwd(), path.resolve(sourcePathPosix))) +
+    normalize(path.relative(process.cwd(), path.resolve(sourcePath))) +
     (isDir ? PATHSEP : "")
   )
 }
diff --git a/packages/cli/src/api/fixtures/collect/componentA/componentA.js b/packages/cli/src/api/fixtures/collect/componentA/componentA.js
index b8c091b3..67bd58bc 100644
--- a/packages/cli/src/api/fixtures/collect/componentA/componentA.js
+++ b/packages/cli/src/api/fixtures/collect/componentA/componentA.js
@@ -1,1 +1,1 @@
-/*i18n*/ i18n._("Hello World")
+/*i18n*/ i18n._("Component A")
diff --git a/packages/cli/src/api/fixtures/collect/componentB.js b/packages/cli/src/api/fixtures/collect/componentB.js
new file mode 100644
index 00000000..579c71ef
--- /dev/null
+++ b/packages/cli/src/api/fixtures/collect/componentB.js
@@ -0,0 +1,1 @@
+/*i18n*/ i18n._("Component B")


Test patch:
diff --git a/packages/cli/src/api/__snapshots__/catalog.test.ts.snap b/packages/cli/src/api/__snapshots__/catalog.test.ts.snap
index 44a27d42..1d0cdb67 100644
--- a/packages/cli/src/api/__snapshots__/catalog.test.ts.snap
+++ b/packages/cli/src/api/__snapshots__/catalog.test.ts.snap
@@ -2,12 +2,24 @@
 
 exports[`Catalog collect should extract messages from source files 1`] = `
 Object {
-  Hello World: Object {
+  Component A: Object {
     origin: Array [
       Array [
         collect/componentA/componentA.js,
         1,
       ],
+    ],
+  },
+  Component B: Object {
+    origin: Array [
+      Array [
+        collect/componentB.js,
+        1,
+      ],
+    ],
+  },
+  Hello World: Object {
+    origin: Array [
       Array [
         collect/componentA/index.js,
         1,
@@ -29,7 +41,7 @@ Object {
 exports[`Catalog make should collect and write catalogs 2`] = `
 Object {
   cs: Object {
-    Hello World: Object {
+    Component A: Object {
       comment: undefined,
       comments: Array [],
       flags: Array [],
@@ -39,6 +51,15 @@ Object {
           collect/componentA/componentA.js,
           1,
         ],
+      ],
+      translation: ,
+    },
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [
         Array [
           collect/componentA/index.js,
           1,
@@ -48,7 +69,7 @@ Object {
     },
   },
   en: Object {
-    Hello World: Object {
+    Component A: Object {
       comment: undefined,
       comments: Array [],
       flags: Array [],
@@ -58,6 +79,15 @@ Object {
           collect/componentA/componentA.js,
           1,
         ],
+      ],
+      translation: ,
+    },
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [
         Array [
           collect/componentA/index.js,
           1,
@@ -97,7 +127,7 @@ Object {
 exports[`Catalog make should merge with existing catalogs 2`] = `
 Object {
   cs: Object {
-    Hello World: Object {
+    Component A: Object {
       comment: undefined,
       comments: Array [],
       flags: Array [],
@@ -107,6 +137,28 @@ Object {
           collect/componentA/componentA.js,
           1,
         ],
+      ],
+      translation: ,
+    },
+    Component B: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [
+        Array [
+          collect/componentB.js,
+          1,
+        ],
+      ],
+      translation: ,
+    },
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [
         Array [
           collect/componentA/index.js,
           1,
@@ -116,7 +168,7 @@ Object {
     },
   },
   en: Object {
-    Hello World: Object {
+    Component A: Object {
       comment: undefined,
       comments: Array [],
       flags: Array [],
@@ -126,6 +178,28 @@ Object {
           collect/componentA/componentA.js,
           1,
         ],
+      ],
+      translation: ,
+    },
+    Component B: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [
+        Array [
+          collect/componentB.js,
+          1,
+        ],
+      ],
+      translation: ,
+    },
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [
         Array [
           collect/componentA/index.js,
           1,
diff --git a/packages/cli/src/api/catalog.test.ts b/packages/cli/src/api/catalog.test.ts
index b67be54f..d4f1096e 100644
--- a/packages/cli/src/api/catalog.test.ts
+++ b/packages/cli/src/api/catalog.test.ts
@@ -24,7 +24,9 @@ import {
 } from "../tests"
 
 export const fixture = (...dirs) =>
-  path.resolve(__dirname, path.join("fixtures", ...dirs))
+  path.resolve(__dirname, path.join("fixtures", ...dirs)) +
+  // preserve trailing slash
+  (dirs[dirs.length - 1].endsWith("/") ? "/" : "")
 
 describe("Catalog", function () {
   afterEach(() => {
@@ -38,7 +40,10 @@ describe("Catalog", function () {
         {
           name: "messages",
           path: path.join(localeDir, "{locale}", "messages"),
-          include: [fixture("collect")],
+          include: [
+            fixture("collect/componentA/"),
+            fixture("collect/componentB"),
+          ],
           exclude: [],
         },
         mockConfig({
@@ -59,7 +64,7 @@ describe("Catalog", function () {
         {
           name: "messages",
           path: path.join(localeDir, "{locale}"),
-          include: [fixture("collect")],
+          include: [fixture("collect/")],
           exclude: [],
         },
         mockConfig({
@@ -81,7 +86,7 @@ describe("Catalog", function () {
         {
           name: "messages",
           path: "locales/{locale}",
-          include: [fixture("collect")],
+          include: [fixture("collect/")],
           exclude: [],
         },
         mockConfig()
@@ -96,7 +101,7 @@ describe("Catalog", function () {
         {
           name: "messages",
           path: "locales/{locale}",
-          include: [fixture("collect-invalid")],
+          include: [fixture("collect-invalid/")],
           exclude: [],
         },
         mockConfig()
@@ -859,7 +864,7 @@ describe("order", function () {
 
 describe("writeCompiled", function () {
   it("saves ES modules to .mjs files", function () {
-    const localeDir = copyFixture(fixture("locales", "initial"))
+    const localeDir = copyFixture(fixture("locales", "initial/"))
     const catalog = new Catalog(
       {
         name: "messages",
@@ -879,7 +884,7 @@ describe("writeCompiled", function () {
   })
 
   it("saves anything else than ES modules to .js files", function () {
-    const localeDir = copyFixture(fixture("locales", "initial"))
+    const localeDir = copyFixture(fixture("locales", "initial/"))
     const catalog = new Catalog(
       {
         name: "messages",