Automatically Alphabetize Translation JSON Files
Tags: typescript, tutorial, javascript
Published: 2020-02-02 07:36:19

If you are rapidly developing an application that supports multiple languages, it's safe to assume your translation files are constantly being updated. In a large team this can cause numerous merge conflicts, rebasing and slow-down to delivering features. 

Last week, our team wanted a convenient way to keep our translations managed to aid with PR throughput as well as keep our translation files "organized" to quickly browse through available keys. As a result, we built a simple post-commit hook that automatically alphabetizes our translation files and keeps them organized for each PR - without requiring the developer to manually alphabetize the translation file or have to commit a fix as part of a PR checklist. 

Implementing this efficiency requires the following project conditions:
1. Usage of JSON files for translations
2. Flat translation structure (or update the script to handle nesting)
3. NPM/Yarn based project
4. Using Git

## Getting Started

You will need to install an npm package called `husky` that allows you invoke scripts as part of the git process (pre-commit or post-commit).

In a yarn workspace...

```bash
yarn add husky -W --save-dev

```

In an npm project...
```bash
npm install husky --save-dev
```

## Translation Script

Create a TypeScript file for the translation script. You can name this file anything and include it anywhere that is within a targeted `tsconfig.json`. In our example, we use a Yarn NX mono-repo and have our script located in: `libs/i18n/scripts/auto-order-translations.ts`.

The contents of the script will include:
```ts
const fs = require('fs');
const path = require('path');
const english = require('../src/lib/en.json');
const exec = require('child_process').exec;

const orderedEnglish = {};
Object.keys(english).sort(function (a, b) {
    return a.toLowerCase().localeCompare(b.toLowerCase());
}).forEach(key => {
    orderedEnglish[key] = english[key];
});

fs.writeFile(path.resolve(__dirname, '../src/lib/en.json'), JSON.stringify(orderedEnglish, null, '\t'), function (err) {
    if (err) {
        throw err;
    }
    exec('HUSKY_SKIP_HOOKS=1 git add libs/i18n/src/lib/en.json && HUSKY_SKIP_HOOKS=1 git commit --amend --no-edit --allow-empty', function (err) {
        if (err) {
            throw err;
        }
    })
});
```

#### Important Pieces

1. You will need to update the path of `../src/lib/en.json` to match the _relative_ location of where your translation file is located. For our team, we only need to maintain the English translation - as a 3rd party will supply us the translated output targets.
2. `git add libs/i18n/src/lib/en.json` is the location of the translation file, from the *root* location of your project. This will be the same starting location as where your `package.json` is. This piece is crucial, as it amends the developers commit to include the sanitized translation file automatically.
3. `a.toLowerCase().localeCompare(b.toLowerCase())` forces the keys to be in a consistent order, following casing (i.e: `item` comes before `Item`).


_Misc_:
- To prevent an infinite loop of Husky detecting a commit hook as we amend the previous commit, we pass the flag `HUSKY_SKIP_HOOKS=1` to ignore Husky from executing during the current process of our script.

## Post-Commit Hook

With husky installed and our script ready to go, the last piece is executing the script when a developer is about to make a commit.

In your `package.json` add the following block:
```json

"husky": {
    "hooks": {
        "post-commit": "node libs/i18n/scripts/auto-order-translations.ts"
    }
}
``` 

_Note_: Update the path `libs/...` to match the location where your script file is located.

To confirm the script is working, have your translation file keys in a random order and commit a new change. If you want to test without manually changing a file, I use this command to push a commit with no changes:

```bash
git commit --allow-empty -m "Trigger notification"
```

If you use GitKraken or another Git Client, you should see messages regarding the post-commit process ignoring Husky after our script executes.

The translation file should be sorted correctly on your local and remote branches. 

#### Before

``` json
{
    "Your Team": "Your Team",
    "Name": "Name",
    "Are you sure?": "Are you sure?",
    "Thumbnail": "Thumbnail",
    "People": "People",
    "Clear": "Clear",
    "This group is locked and cannot be deleted.": "This group is locked and cannot be deleted.",
    "Email": "Email"
}
```

#### After

```json
{
    "Are you sure?": "Are you sure?",
    "Clear": "Clear",
    "Email": "Email",
    "Name": "Name",
    "People": "People",
    "This group is locked and cannot be deleted.": "This group is locked and cannot be deleted.",
    "Thumbnail": "Thumbnail",
    "Your Team": "Your Team"
}
```