Repository: i18next/i18next-parser (js)

Issue:
Changes in order should cause --fail-on-update to fail
<!--
Before you submit an issue we recommend you visit [docs](https://www.i18next.com/) or [docs](https://react.i18next.com/) or [docs](https://github.com/i18next/next-i18next) or [StackOverflow](https://stackoverflow.com/search?q=i18next) or similar and ask any questions you have or mention any problems you've had getting started with i18next.

**Please read this entire template before posting any issue. If you ignore these instructions and post an issue here that does not follow the instructions, your issue might be closed, locked.**
-->

## 🐛 Bug Report

When the keys are complete but in a wrong order calling `i18next --fail-on-update` with `sort: true` in the config does update the files but not fail.

## To Reproduce

1. Have a project with an unsorted translation file and `sort: true` in the config.
2. Run `i18next --fail-on-update 'src/**/*.{ts,tsx,js,jsx}'`

## Expected behavior

Expected the command to fail, since there are updates in the translation file.

## Your Environment

- *runtime version*: node v20
- *i18next-parser version*: 8.3.0
- *os*: Windows


Pull request description:
Sort & failOnUpdate
### Why am I submitting this PR

Exit with exit code 1 when `sort` and `failOnUpdate` are set and keys are not already sorted

### Does it fix an existing ticket?

Fixes #926

### Checklist

- [x] only relevant code is changed (make a diff before you submit the PR)
- [x] tests are included and pass: `yarn test` (see [details here](https://github.com/i18next/i18next-parser/blob/master/docs/development.md#tests))
- [ ] documentation is changed or added


Patch:
diff --git a/src/transform.js b/src/transform.js
index 1545764..408cffb 100644
--- a/src/transform.js
+++ b/src/transform.js
@@ -296,7 +296,20 @@ export default class i18nTransform extends Transform {
             typeof sort === 'function'
               ? sort
               : makeDefaultSort(this.options.pluralSeparator)
-          maybeSortedNewCatalog = sortKeys(newCatalog, { deep: true, compare })
+          let wrappedCompare = this.options.failOnUpdate
+            ? (...args) => {
+                const result = compare(...args)
+                if (result > 0) {
+                  this.parserHadSortUpdate = true
+                  wrappedCompare = compare
+                }
+                return result
+              }
+            : compare
+          maybeSortedNewCatalog = sortKeys(newCatalog, {
+            deep: true,
+            compare: wrappedCompare,
+          })
           maybeSortedOldCatalog = sortKeys(oldCatalog, { deep: true, compare })
         }
 
@@ -319,12 +332,21 @@ export default class i18nTransform extends Transform {
       process.exit(1)
     }
 
-    if (this.options.failOnUpdate && this.parserHadUpdate) {
-      this.emit(
-        'error',
-        'Some translations was updated and failOnUpdate option is enabled. Exiting...'
-      )
-      process.exit(1)
+    if (this.options.failOnUpdate) {
+      if (this.parserHadUpdate) {
+        this.emit(
+          'error',
+          'Some translations was updated and failOnUpdate option is enabled. Exiting...'
+        )
+        process.exit(1)
+      }
+      if (this.parserHadSortUpdate) {
+        this.emit(
+          'error',
+          'Some keys were sorted and failOnUpdate option is enabled. Exiting...'
+        )
+        process.exit(1)
+      }
     }
 
     done()


Test patch:
diff --git a/test/locales/en/test_sort.json b/test/locales/en/test_sort.json
new file mode 100644
index 0000000..95b4334
--- /dev/null
+++ b/test/locales/en/test_sort.json
@@ -0,0 +1,4 @@
+{
+  "second": "second",
+  "first": "first"
+}
diff --git a/test/parser.test.js b/test/parser.test.js
index 9901185..d350cfe 100644
--- a/test/parser.test.js
+++ b/test/parser.test.js
@@ -619,6 +619,36 @@ describe('parser', () => {
     i18nextParser.end(fakeFile)
   })
 
+  it('fails on sort with failOnUpdate option', (done) => {
+    const i18nextParser = new i18nTransform({
+      output: 'test/locales/$LOCALE/$NAMESPACE.json',
+      failOnUpdate: true,
+      locales: ['en'],
+      sort: true,
+    })
+    const fakeFile = new Vinyl({
+      contents: Buffer.from("t('test_sort:first'), t('test_sort:second')"),
+      path: 'file.js',
+    })
+
+    let actualError
+    i18nextParser.once('error', (error) => {
+      actualError = error
+    })
+
+    const realExit = process.exit
+    process.exit = (exitCode) => {
+      process.exit = realExit
+      assert.equal(exitCode, 1)
+      assert.equal(
+        actualError,
+        'Some keys were sorted and failOnUpdate option is enabled. Exiting...'
+      )
+      done()
+    }
+    i18nextParser.end(fakeFile)
+  })
+
   it('restores translations from the old catalog', (done) => {
     const i18nextParser = new i18nTransform({
       output: 'test/locales/$LOCALE/$NAMESPACE.json',