Repository: lingui/js-lingui
Pull request #779 (2020-10-25 14:01:32)

fix: refactored stats logs 
I'm not sure what's the correct way of handling this, I think the problem of users using lingui in a monorepo where "en" appears by each catalog it's solved in this way, we log for every catalog his stats

Feel free to rquest any changes or some improvements, i'm still pretty noobie about core aspects.

vercel[bot]: This pull request is being automatically deployed with Vercel ([learn more](https://vercel.link/github-learn-more)).  
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: [https://vercel.com/lingui-js/js-lingui/oj5hwkhao](https://vercel.com/lingui-js/js-lingui/oj5hwkhao)  
✅ Preview: *In Progress*



Patch:
diff --git a/packages/cli/src/api/stats.ts b/packages/cli/src/api/stats.ts
index 3b0cc342..29435e09 100644
--- a/packages/cli/src/api/stats.ts
+++ b/packages/cli/src/api/stats.ts
@@ -34,5 +34,5 @@ export function printStats(config: LinguiConfig, catalogs: AllCatalogsType) {
     }
   })
 
-  console.log(table.toString())
+  return table
 }
diff --git a/packages/cli/src/lingui-extract.ts b/packages/cli/src/lingui-extract.ts
index f9d04f9e..fa3deaf0 100644
--- a/packages/cli/src/lingui-extract.ts
+++ b/packages/cli/src/lingui-extract.ts
@@ -4,9 +4,10 @@ import program from "commander"
 import { getConfig, LinguiConfig } from "@lingui/conf"
 
 import { getCatalogs } from "./api/catalog"
-import { extract } from "./api/extract"
+import { printStats } from "./api/stats"
 import { detect } from "./api/detect"
 import { helpRun } from "./api/help"
+import { AllCatalogsType } from "./api/types"
 
 export type CliExtractOptions = {
   verbose: boolean
@@ -17,7 +18,7 @@ export type CliExtractOptions = {
 
 export default function command(
   config: LinguiConfig,
-  options: CliExtractOptions
+  options: Partial<CliExtractOptions>
 ): boolean {
   // `react-app` babel plugin used by CRA requires either BABEL_ENV or NODE_ENV to be
   // set. We're setting it here, because lingui macros are going to use them as well.
@@ -32,6 +33,7 @@ export default function command(
 
   options.verbose && console.error("Extracting messages from source files…")
   const catalogs = getCatalogs(config)
+  const catalogStats: { [path: string]: AllCatalogsType }  = {}
   catalogs.forEach((catalog) => {
     catalog.make({
       ...options,
@@ -42,11 +44,15 @@ export default function command(
       //   catalog.addLocale(pseudoLocale)
       // }
     })
+
+    catalogStats[catalog.path] = catalog.readAll()
   })
 
-  // console.log("Catalog statistics:")
-  // printStats(config, catalogs)
-  // console.log()
+  Object.entries(catalogStats).forEach(([key, value]) => {
+    console.log(`Catalog statistics for ${key}: `)
+    console.log(printStats(config, value).toString())
+    console.log()
+  })
 
   console.error(
     `(use "${chalk.yellow(
diff --git a/packages/conf/index.d.ts b/packages/conf/index.d.ts
index 39d27e94..62569b42 100644
--- a/packages/conf/index.d.ts
+++ b/packages/conf/index.d.ts
@@ -15,7 +15,9 @@ export declare type LinguiConfig = {
     extractBabelOptions: Object;
     fallbackLocale: string;
     format: CatalogFormat;
+    prevFormat: CatalogFormat;
     formatOptions: CatalogFormatOptions;
+    localeDir: string;
     locales: string[];
     catalogsMergePath?: string;
     orderBy: OrderBy;
diff --git a/packages/jest-mocks/index.ts b/packages/jest-mocks/index.ts
index 9677eede..321c1f2c 100644
--- a/packages/jest-mocks/index.ts
+++ b/packages/jest-mocks/index.ts
@@ -1,6 +1,6 @@
-import { defaultConfig } from "@lingui/conf"
+import { defaultConfig, LinguiConfig } from "@lingui/conf"
 
-export function mockConfig(config = {}) {
+export function mockConfig(config: Partial<LinguiConfig> = {}) {
   return {
     ...defaultConfig,
     ...config,


Test patch:
diff --git a/packages/cli/src/api/__snapshots__/stats.test.ts.snap b/packages/cli/src/api/__snapshots__/stats.test.ts.snap
new file mode 100644
index 00000000..5e0995ed
--- /dev/null
+++ b/packages/cli/src/api/__snapshots__/stats.test.ts.snap
@@ -0,0 +1,387 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Catalog collect should extract messages from source files 1`] = `
+Object {
+  Hello World: Object {
+    origin: Array [
+      Array [
+        collect/componentA/componentA.js,
+        1,
+      ],
+      Array [
+        collect/componentA/index.js,
+        1,
+      ],
+    ],
+  },
+}
+`;
+
+exports[`Catalog collect should handle errors 1`] = `Object {}`;
+
+exports[`Catalog make should collect and write catalogs 1`] = `
+Object {
+  cs: null,
+  en: null,
+}
+`;
+
+exports[`Catalog make should collect and write catalogs 2`] = `
+Object {
+  cs: Object {
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [
+        Array [
+          collect/componentA/componentA.js,
+          1,
+        ],
+        Array [
+          collect/componentA/index.js,
+          1,
+        ],
+      ],
+      translation: ,
+    },
+  },
+  en: Object {
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [
+        Array [
+          collect/componentA/componentA.js,
+          1,
+        ],
+        Array [
+          collect/componentA/index.js,
+          1,
+        ],
+      ],
+      translation: ,
+    },
+  },
+}
+`;
+
+exports[`Catalog make should merge with existing catalogs 1`] = `
+Object {
+  cs: Object {
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [],
+      translation: Ahoj Brno,
+    },
+  },
+  en: Object {
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [],
+      translation: Ahoj Brno,
+    },
+  },
+}
+`;
+
+exports[`Catalog make should merge with existing catalogs 2`] = `
+Object {
+  cs: Object {
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [
+        Array [
+          collect/componentA/componentA.js,
+          1,
+        ],
+        Array [
+          collect/componentA/index.js,
+          1,
+        ],
+      ],
+      translation: Ahoj Brno,
+    },
+  },
+  en: Object {
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [
+        Array [
+          collect/componentA/componentA.js,
+          1,
+        ],
+        Array [
+          collect/componentA/index.js,
+          1,
+        ],
+      ],
+      translation: Ahoj Brno,
+    },
+  },
+}
+`;
+
+exports[`Catalog read should read file in given format 1`] = `
+Object {
+  obsolete: Object {
+    comment: undefined,
+    comments: Array [],
+    flags: Array [],
+    obsolete: true,
+    origin: Array [],
+    translation: Is marked as obsolete,
+  },
+  static: Object {
+    comment: undefined,
+    comments: Array [],
+    flags: Array [],
+    obsolete: false,
+    origin: Array [],
+    translation: Static message,
+  },
+  veryLongString: Object {
+    comment: undefined,
+    comments: Array [],
+    flags: Array [],
+    obsolete: false,
+    origin: Array [],
+    translation: One morning, when Gregor Samsa woke from troubled dreams, he found himself transformed in his bed into a horrible vermin. He lay on his armour-like back, and if he lifted his head a little he could see his brown belly, slightly domed and divided by arches into stiff sections. The bedding was hardly able to cover it and seemed ready to slide off any moment. His many legs, pitifully thin compared with the size of the rest of him, waved about helplessly as he looked. "What's happened to me?" he thought. It wasn't a dream. His room, a proper human,
+  },
+  withComments: Object {
+    comment: undefined,
+    comments: Array [
+      Translator comment,
+      This one might come from developer,
+    ],
+    flags: Array [],
+    obsolete: false,
+    origin: Array [],
+    translation: Support translator comments separately,
+  },
+  withDescription: Object {
+    comment: Description is comment from developers to translators,
+    comments: Array [],
+    flags: Array [],
+    obsolete: false,
+    origin: Array [],
+    translation: Message with description,
+  },
+  withFlags: Object {
+    comment: undefined,
+    comments: Array [],
+    flags: Array [
+      fuzzy,
+      otherFlag,
+    ],
+    obsolete: false,
+    origin: Array [],
+    translation: Keeps any flags that are defined,
+  },
+  withMultipleOrigins: Object {
+    comment: undefined,
+    comments: Array [],
+    flags: Array [],
+    obsolete: false,
+    origin: Array [
+      Array [
+        src/App.js,
+        4,
+      ],
+      Array [
+        src/Component.js,
+        2,
+      ],
+    ],
+    translation: Message with multiple origin,
+  },
+  withOrigin: Object {
+    comment: undefined,
+    comments: Array [],
+    flags: Array [],
+    obsolete: false,
+    origin: Array [
+      Array [
+        src/App.js,
+        4,
+      ],
+    ],
+    translation: Message with origin,
+  },
+}
+`;
+
+exports[`Catalog read should read file in previous format 1`] = `null`;
+
+exports[`Catalog readAll should read existing catalogs for all locales 1`] = `
+Object {
+  cs: Object {
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [],
+      translation: Ahoj Brno,
+    },
+  },
+  en: Object {
+    Hello World: Object {
+      comment: undefined,
+      comments: Array [],
+      flags: Array [],
+      obsolete: false,
+      origin: Array [],
+      translation: Hello World,
+    },
+  },
+}
+`;
+
+exports[`cleanObsolete should remove obsolete messages from catalog 1`] = `
+Object {
+  Label: Object {
+    obsolete: false,
+    origin: Array [
+      Array [
+        catalog.test.ts,
+        1,
+      ],
+    ],
+    translation: Label,
+  },
+}
+`;
+
+exports[`getCatalogs should warn about missing {name} pattern in catalog path 1`] = `Catalog with path "./locales/{locale}" doesn't have a {name} pattern in it, but one of source directories uses it: "{name}/". Either add {name} pattern to "./locales/{locale}" or remove it from all source directories.`;
+
+exports[`getCatalogs should warn if catalogPath is a directory 1`] = `Remove trailing slash from "./locales/{locale}/". Catalog path isn't a directory, but translation file without extension. For example, catalog path "./locales/{locale}" results in translation file "./locales/en.po".`;
+
+exports[`getCatalogs should warn if catalogPath is a directory 2`] = `Remove trailing slash from "./locales/{locale}/". Catalog path isn't a directory, but translation file without extension. For example, catalog path "./locales/{locale}" results in translation file "./locales/cs.json".`;
+
+exports[`order should order messages alphabetically 1`] = `
+Object {
+  LabelA: Object {
+    obsolete: false,
+    origin: Array [
+      Array [
+        catalog.test.ts,
+        1,
+      ],
+    ],
+    translation: A,
+  },
+  LabelB: Object {
+    obsolete: false,
+    origin: Array [
+      Array [
+        catalog.test.ts,
+        1,
+      ],
+    ],
+    translation: B,
+  },
+  LabelC: Object {
+    obsolete: false,
+    origin: Array [
+      Array [
+        catalog.test.ts,
+        1,
+      ],
+    ],
+    translation: C,
+  },
+  LabelD: Object {
+    obsolete: false,
+    origin: Array [
+      Array [
+        catalog.test.ts,
+        1,
+      ],
+    ],
+    translation: D,
+  },
+}
+`;
+
+exports[`order should order messages alphabetically 2`] = `
+Array [
+  LabelA,
+  LabelB,
+  LabelC,
+  LabelD,
+]
+`;
+
+exports[`order should order messages by origin 1`] = `
+Object {
+  LabelA: Object {
+    obsolete: false,
+    origin: Array [
+      Array [
+        file2.js,
+        3,
+      ],
+    ],
+    translation: A,
+  },
+  LabelB: Object {
+    obsolete: false,
+    origin: Array [
+      Array [
+        file1.js,
+        2,
+      ],
+      Array [
+        file2.js,
+        2,
+      ],
+    ],
+    translation: B,
+  },
+  LabelC: Object {
+    obsolete: false,
+    origin: Array [
+      Array [
+        file1.js,
+        1,
+      ],
+    ],
+    translation: C,
+  },
+  LabelD: Object {
+    obsolete: false,
+    origin: Array [
+      Array [
+        file2.js,
+        100,
+      ],
+    ],
+    translation: D,
+  },
+}
+`;
+
+exports[`order should order messages by origin 2`] = `
+Array [
+  LabelC,
+  LabelB,
+  LabelA,
+  LabelD,
+]
+`;
diff --git a/packages/cli/src/api/catalog.test.ts b/packages/cli/src/api/catalog.test.ts
index 64c8310a..ce375a14 100644
--- a/packages/cli/src/api/catalog.test.ts
+++ b/packages/cli/src/api/catalog.test.ts
@@ -26,10 +26,22 @@ const defaultMakeOptions: MakeOptions = {
   orderBy: "messageId",
 }
 
-const defaultMergeOptions: MergeOptions = {
+export const defaultMergeOptions: MergeOptions = {
   overwrite: false,
 }
 
+export const makeCatalog = (config = {}) => {
+  return new Catalog(
+    {
+      name: "messages",
+      path: "{locale}/messages",
+      include: [],
+      exclude: [],
+    },
+    mockConfig(config)
+  )
+}
+
 function makePrevMessage(message = {}): MessageType {
   return {
     translation: "",
@@ -37,7 +49,7 @@ function makePrevMessage(message = {}): MessageType {
   }
 }
 
-function makeNextMessage(message = {}): ExtractedMessageType {
+export function makeNextMessage(message = {}): ExtractedMessageType {
   return {
     origin: [["catalog.test.ts", 1]],
     obsolete: false,
@@ -157,17 +169,6 @@ describe("Catalog", function () {
     - all other languages: translation is kept empty
     */
 
-    const makeCatalog = (config = {}) =>
-      new Catalog(
-        {
-          name: "messages",
-          path: "{locale}/messages",
-          include: [],
-          exclude: [],
-        },
-        mockConfig(config)
-      )
-
     it("should initialize catalog", function () {
       const prevCatalogs = { en: null, cs: null }
       const nextCatalog = {
@@ -523,7 +524,7 @@ describe("getCatalogs", function () {
       catalogs: [
         {
           path: "./src/locales/{locale}",
-          include: "./src/",
+          include: ["./src/"],
         },
       ],
     })
diff --git a/packages/cli/src/api/stats.test.ts b/packages/cli/src/api/stats.test.ts
new file mode 100644
index 00000000..2b43c8d1
--- /dev/null
+++ b/packages/cli/src/api/stats.test.ts
@@ -0,0 +1,34 @@
+import mockFs from "mock-fs"
+import { mockConfig } from "@lingui/jest-mocks"
+import { makeNextMessage, defaultMergeOptions, makeCatalog } from "./catalog.test"
+import { printStats } from "./stats"
+
+describe("PrintStats", () => {
+  afterEach(() => {
+    mockFs.restore();
+  });
+
+  it("should print correct stats for a basic setup", () => {
+    const config = mockConfig({
+      locales: ["en", "cs"],
+    })
+
+    const prevCatalogs = { en: null, cs: null }
+    const nextCatalog = {
+      "custom.id": makeNextMessage({
+        message: "Message with custom ID",
+      }),
+      "Message with <0>auto-generated</0> ID": makeNextMessage(),
+    }
+
+    const catalogs = makeCatalog({ sourceLocale: "en" }).merge(
+      prevCatalogs,
+      nextCatalog,
+      defaultMergeOptions
+    )
+
+    const { options, ...table } = printStats(config, catalogs);
+    expect(options.head).toEqual([ 'Language', 'Total count', 'Missing' ])
+    expect(Object.values(table)).toStrictEqual([{"en": [2, 0]}, {"cs": [2, 2]}, 2])
+  })
+})