While working at REDACTED, I reported the following issue.
We develop in Spain an application in English for Saudi Arabia, how is it that we don’t already have easy to use translations?
Our translations are cumbersome to manage. To add one you have to:
- create a hierarchical key like
some-module.some-part.some-section.some-title
- add its translation in code like
this.title = this.translateService.instant('some-module.some-part.some-section.some-title');
- open a terminal window at the app main directory, then issue
$ npm run translation.export
- open a browser, then navigate to a third party website and authenticate
- search
some-module.some-part.some-section.some-title
- select your key in the results (beware that approximate results are shown too)
- edit its translation in a dialog box
- save the transaltion
- open a terminal window at the app main directory, then issue:
$ npm run translation.import
- reload the page to show the translated title
Why can’t I just add the translated key to a translations file? I guess because the programmers that started developing the app were required to use that third party website for allowing professional translators to do their job. That is a reasonable requirement but there is no need for programmers to continually export and import translations, simply to make them appear on the page they are working on. Translations could automatically be exported and imported at any later time, like when merging changes into the development branch.
It wouldn’t be difficult to write an extraction script to allow coding like this:
// .../src/some-module/some-part/some-section.translations.ts
export const translations = {
"some-title": "..."
};
// .../src/some-module/some-part/some-section.ts
this.title = x('some-title');
There is a problem with extraction scripts, though. They are static text analyzers that read code and extract some.key
from expressions like instant('some.key')
. Thus they can’t extract interpolated keys like instant(`${some}.key`)
, where some
is a string variable whose value will be set later. For example, if some = 'another'
, then ${some}.key
would be another.key
.
Interpolated keys are very useful for compressing many code lines into one cleaner expression. For example, this code
let translation;
switch (some) {
case 'some':
translation = x('some.key');
break;
case 'another':
translation = x('another.key');
break;
case 'yet.another':
translation = x('yet.another.key');
break;
default: // programmer error
throw new Error(`Unexpected value for 'some' variable (got '${some}')`);
}
can be compressed to this one liner:
const translation = x(`${some}.key`);
While it’s true that we can’t have both the independence from run time (static analyzer) and the flexibility of interpolations (dynamic analyzer) in the same tool, nonetheless we can easily have both benefits with a static analyzer and a bit of overhead. In fact, all we need to do is to declare all those interpolated keys in a file that only needs to exist. (no need to use it anywhere)
// interpolated-translation-keys.ts
x('some.key');
x('another.key');
x('yet.another.key');
With a file like that, the static analyzer would find it, eat it, and spit out translatable keys, which could eventually be exported. At the same time, the code would work perfectly with interpolated translation keys.