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:
- Usage of JSON files for translations
- Flat translation structure (or update the script to handle nesting)
- NPM/Yarn based project
- 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...
yarn add husky -W --save-dev
In an npm project...
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:
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
- 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. -
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 yourpackage.json
is. This piece is crucial, as it amends the developers commit to include the sanitized translation file automatically. -
a.toLowerCase().localeCompare(b.toLowerCase())
forces the keys to be in a consistent order, following casing (i.e:item
comes beforeItem
).
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:
"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:
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
{
"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
{
"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"
}
Top comments (0)