Repository: lingui/js-lingui
Pull request #563 (2019-09-06 12:28:21)

Add optional sort by origin/filename
This is a proposal to add a config option `sorting` to expose a new sort by origin feature.

Unfortunately, `master` is in a rather bad state with a bunch of failing tests. Let me know what I can do to help.

Context https://github.com/lingui/js-lingui/issues/562

now[bot]: 
This pull request is [automatically deployed](https://zeit.co/docs/v2/integrations/now-for-github?utm_source=automated&utm_medium=github&utm_campaign=now_bot) with [Now](https://zeit.co/now?utm_source=automated&utm_medium=github&utm_campaign=now_bot).
To access deployments, click Details below or on the icon next to each push.
	
now[bot]: 
Deployment failed with the following error:

~~~
Your `package.json` file is missing a `build` property inside the `script` property.
~~~
			
skovhus: To try this out on a project using yarn, you can add a `resolutions` section to your package.json:
```
"resolutions": {
  "@lingui/cli": "https://github.com/skovhus/js-lingui#lingui-cli-v2.8.3-gitpkg",
  "@lingui/conf": "https://github.com/skovhus/js-lingui#lingui-conf-v2.8.3-gitpkg"
}
```


Patch:
diff --git a/docs/ref/conf.rst b/docs/ref/conf.rst
index c3646475..ac64e094 100644
--- a/docs/ref/conf.rst
+++ b/docs/ref/conf.rst
@@ -193,3 +193,21 @@ localeDir
 Default: ``<rootDir>/locale``
 
 Directory where message catalogs should be saved.
+
+
+sorting
+---------
+
+Default: ``messageId``
+
+Sorting order for catalog files. Possible values are:
+
+messageId
+^^^^^^
+
+Sort by the message ID.
+
+origin
+^^^^^^^
+
+Sort by file origin of the message.
diff --git a/packages/cli/src/api/extract.js b/packages/cli/src/api/extract.js
index c87ede91..4e51c366 100644
--- a/packages/cli/src/api/extract.js
+++ b/packages/cli/src/api/extract.js
@@ -9,7 +9,7 @@ import { prettyOrigin } from "./utils"
 
 import * as extractors from "./extractors"
 import type { ExtractorType } from "./extractors"
-import type { AllCatalogsType } from "./types"
+import type { AllCatalogsType, Sorting } from "./types"
 
 type ExtractOptions = {
   ignore?: Array<string>,
@@ -104,8 +104,16 @@ export function cleanObsolete(catalogs: AllCatalogsType) {
   return R.map(R.filter(message => !message.obsolete), catalogs)
 }
 
-export function order(catalogs: AllCatalogsType) {
-  return R.map(orderByMessageId, catalogs)
+export function order(catalogs: AllCatalogsType, by: Sorting) {
+  if (by === "messageId") {
+    return R.map(orderByMessageId, catalogs)
+  }
+
+  if (by === "origin") {
+    return R.map(orderByOrigin, catalogs)
+  }
+
+  throw new Error("Invalid order by")
 }
 
 function orderByMessageId(messages) {
@@ -118,3 +126,32 @@ function orderByMessageId(messages) {
 
   return orderedMessages
 }
+
+function orderByOrigin(messages) {
+  function getFirstOrigin(messageKey) {
+    const sortedOrigins = messages[messageKey].origin.sort((a, b) => {
+      if (a[0] < b[0]) return -1
+      if (a[0] > b[0]) return 1
+      return 0
+    })
+    return sortedOrigins[0]
+  }
+
+  return Object.keys(messages)
+    .sort(function(a, b) {
+      const [aFile, aLineNumber] = getFirstOrigin(a)
+      const [bFile, bLineNumber] = getFirstOrigin(b)
+
+      if (aFile < bFile) return -1
+      if (aFile > bFile) return 1
+
+      if (aLineNumber < bLineNumber) return -1
+      if (aLineNumber > bLineNumber) return 1
+
+      return 0
+    })
+    .reduce((acc, key) => {
+      acc[key] = messages[key]
+      return acc
+    }, {})
+}
diff --git a/packages/cli/src/api/types.js b/packages/cli/src/api/types.js
index f0c8194c..5a7d3164 100644
--- a/packages/cli/src/api/types.js
+++ b/packages/cli/src/api/types.js
@@ -8,9 +8,12 @@ export type LinguiConfig = {|
   pseudoLocale: string,
   srcPathDirs: Array<string>,
   srcPathIgnorePatterns: Array<string>,
-  format: "lingui" | "minimal" | "po"
+  format: "lingui" | "minimal" | "po",
+  sorting: Sorting
 |}
 
+export type Sorting = "messageId" | "origin"
+
 export type IdempotentResult<T> = [boolean, ?T] // [ created, result ]
 
 export type MessageType = {
diff --git a/packages/cli/src/lingui-extract.js b/packages/cli/src/lingui-extract.js
index 31715419..d6fc6d95 100644
--- a/packages/cli/src/lingui-extract.js
+++ b/packages/cli/src/lingui-extract.js
@@ -91,7 +91,8 @@ export default function command(
       catalog.merge(prevCatalogs, nextCatalog, {
         overwrite: options.overwrite
       })
-    )
+    ),
+    config.sorting
   )
   options.verbose && console.log()
 
diff --git a/packages/conf/src/index.js b/packages/conf/src/index.js
index aa51670a..72dbc383 100644
--- a/packages/conf/src/index.js
+++ b/packages/conf/src/index.js
@@ -33,6 +33,7 @@ export const defaultConfig = {
   pseudoLocale: "",
   srcPathDirs: ["<rootDir>"],
   srcPathIgnorePatterns: [NODE_MODULES],
+  sorting: "messageId",
   format: "lingui",
   rootDir: ".",
   extractBabelOptions: {
@@ -57,14 +58,14 @@ const deprecatedConfig = {
     ` Option ${chalk.bold("fallbackLanguage")} was replaced by ${chalk.bold(
       "fallbackLocale"
     )}
-    
+
     @lingui/cli now treats your current configuration as:
     {
       ${chalk.bold('"fallbackLocale"')}: ${chalk.bold(
       `"${config.fallbackLanguage}"`
     )}
     }
-    
+
     Please update your configuration.
     `
 }


Test patch:
diff --git a/packages/cli/src/api/__snapshots__/extract.test.js.snap b/packages/cli/src/api/__snapshots__/extract.test.js.snap
index 0fdbf9a1..5f841271 100644
--- a/packages/cli/src/api/__snapshots__/extract.test.js.snap
+++ b/packages/cli/src/api/__snapshots__/extract.test.js.snap
@@ -124,3 +124,61 @@ Object {
   ],
 }
 `;
+
+exports[`order should order messages by origin 1`] = `
+Object {
+  en: Object {
+    LabelA: Object {
+      origin: Array [
+        Array [
+          file2.js,
+          3,
+        ],
+      ],
+      translation: A,
+    },
+    LabelB: Object {
+      origin: Array [
+        Array [
+          file1.js,
+          2,
+        ],
+        Array [
+          file2.js,
+          2,
+        ],
+      ],
+      translation: B,
+    },
+    LabelC: Object {
+      origin: Array [
+        Array [
+          file1.js,
+          1,
+        ],
+      ],
+      translation: C,
+    },
+    LabelD: Object {
+      origin: Array [
+        Array [
+          file2.js,
+          100,
+        ],
+      ],
+      translation: D,
+    },
+  },
+}
+`;
+
+exports[`order should order messages by origin 2`] = `
+Object {
+  en: Array [
+    LabelC,
+    LabelB,
+    LabelA,
+    LabelD,
+  ],
+}
+`;
diff --git a/packages/cli/src/api/extract.test.js b/packages/cli/src/api/extract.test.js
index 0076d82d..4d7c6329 100644
--- a/packages/cli/src/api/extract.test.js
+++ b/packages/cli/src/api/extract.test.js
@@ -249,7 +249,7 @@ describe("order", function() {
       }
     }
 
-    const orderedCatalogs = order(catalogs)
+    const orderedCatalogs = order(catalogs, "messageId")
 
     // Test that the message content is the same as before
     expect(orderedCatalogs).toMatchSnapshot()
@@ -260,4 +260,39 @@ describe("order", function() {
       fr: Object.keys(orderedCatalogs.fr)
     }).toMatchSnapshot()
   })
+
+  it("should order messages by origin", function() {
+    const { order } = require("./extract")
+
+    const catalogs = {
+      en: {
+        LabelB: {
+          translation: "B",
+          origin: [["file2.js", 2], ["file1.js", 2]]
+        },
+        LabelA: {
+          translation: "A",
+          origin: [["file2.js", 3]]
+        },
+        LabelD: {
+          translation: "D",
+          origin: [["file2.js", 100]]
+        },
+        LabelC: {
+          translation: "C",
+          origin: [["file1.js", 1]]
+        }
+      }
+    }
+
+    const orderedCatalogs = order(catalogs, "origin")
+
+    // Test that the message content is the same as before
+    expect(orderedCatalogs).toMatchSnapshot()
+
+    // Jest snapshot order the keys automatically, so test that the key order explicitly
+    expect({
+      en: Object.keys(orderedCatalogs.en)
+    }).toMatchSnapshot()
+  })
 })