During the initial development of Trilium Notes, internationalisation was not considered as it was meant to be an English-only product.
As the application and the user base grows, it makes sense to be able to reach out as many people as possible by providing translations in their native language.
The library used is i18next.
What has been implemented so far
Where are the translations?
The translations are formatted as JSON files and they are located in src/public/translations
. For every supported locale, there is a subdirectory in which there is a translation.json
file (e.g. src/public/translations/en/translation.json
).
Message keys
One important aspect is the fact that we are using a key-based approach. This means that each message is identified by an ID rather than a natural-language message (such as the default approach in gettext).
The key-based approach allows a hierarchical structure. For example, a key of about.title
would be added in translation.json
as follows:
{
"about": {
"title": "About TriliumNext Notes"
}
}
Adding a new locale
To add a new locale, go to src/public/translations
with your favorite text editor and copy the en
directory.
Rename the copy to the ISO code (e.g. fr
, ro
) of the language being translated.
Translations with a country-language combination, using their corresponding ISO code (e.g. fr_FR
, fr_BE
), has not been tested yet.
Changing the language
Since the internationalisation process is in its early stages, there is no user-facing way to switch the language.
To change the language manually, edit src/public/app/services/i18n.js
and look for the line containing lng: "en"
. Replace en
with the desired language code (from the ones available in src/public/translations
).
Recommendations
- Use hierarchy whenever appropriate, try to group the messages by:
- Modals (e.g.
about.foo
,jump_to_note.foo
)
- Modals (e.g.
- Don't duplicate messages that are very widely used.
- One such example is
aria-label="Close"
which should go to a single message such asmodal.close
instead of being duplicated in every modal.
- One such example is
- On the other hand, don't overly generalise messages. A
close
message that is used whenever the “Close” word is encountered is not a good approach since it can potentially cause issues due to lack of context. - Use variable interpolation whenever appropriate.
- If you see multiple messages joined together only to apply add a variable such as a user-inputted value, try to join those messages together into a single message containing a variable.
- So instead of
“Number of updates: “ + numUpdates + “.”
use$(t("number_updates", { numUpdates }))
where the message translation would appear asNumber of updates: {{numUpdates}}.
Client-side translations
Component-level translations
Most of the client translations are present in the various widgets and layouts.
Translation support has to be added manually for every file.
The first step is to add the translation import with a relative import. For example, if we are in the src/public/app/widgets/dialogs
directory, the import would look as follows:
import { t } from "../../services/i18n.js";
Afterwards, simply replace the hard-coded message with:
${t("msgid")}
where msgid
is the key of the message being translated.
Template-level translations
Templates are .ejs
files present in src/views
, these are used to prepare the root layout for desktop, mobile applications as well as setup (onboarding) and the shared notes view.
Due to using a different approach, it is not possible yet to translate those files.
Server-side translations
Currently the server-side messages are not translatable. They will be added as a separate step.