Translations
WARNING
This documentation is for GNOME 45 and later. Please see the Legacy Documentation for previous versions.
Preparing your extension for translation into other languages makes it available to more users, and potentially more contributors. Inviting others to submit translations is a great way to get people involved in your project.
Gettext is a localization framework for writing multi-lingual applications that is also used by GNOME Shell extensions.
See Also
Preparing an Extension
Initializing Translations
The recommended method for initializing translations is by defining the gettext-domain
key in metadata.json
. This allows GNOME Shell to automatically initialize translations when your extension is loaded.
{
"uuid": "example@gjs.guide",
"name": "Example Extension",
"description": "An example extension with translations",
"shell-version": [ "45" ],
"url": "https://gjs.guide/extensions",
"gettext-domain": "example@gjs.guide"
}
Otherwise, you should call ExtensionBase.prototype.initTranslations()
in the constructor()
of your Extension
and ExtensionPreferences
subclasses.
initTranslations()
in extension.js
import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
export default class ExampleExtension extends Extension {
constructor(metadata) {
super(metadata);
this.initTranslations('example@gjs.guide');
}
enable() {
console.debug(_('Enabling %s').format(this.metadata.name));
}
disable() {
console.debug(_('Disabling %s').format(this.metadata.name));
}
}
initTranslations()
in prefs.js
import Adw from 'gi://Adw';
import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js';
export default class ExamplePreferences extends ExtensionPreferences {
constructor(metadata) {
super(metadata);
this.initTranslations('example@gjs.guide');
}
fillPreferencesWindow(window) {
const page = new Adw.PreferencesPage({
title: _('General'),
icon_name: 'dialog-information-symbolic',
});
window.add(page);
}
}
Marking Strings for Translation
TIP
The format()
function (see printf
) is available for all strings, but should only be used with gettext functions. In all other cases you should use JavaScript's Template Literals.
There are three Gettext functions used by extensions. These functions are used during run-time to retrieve the translation for string, but also identify strings for the xgettext
scanner.
gettext()
This function is the most commonly used function, and is passed a single string. It is usually aliased to
_()
.ngettext()
This function is meant for strings that may or may not be plural like "1 Apple" and "2 Apples". This is important, because different languages handle plural forms in unique ways.
pgettext()
This function is used when the translator may require context for the string. For example, irregular verbs like "Read" in English, or two elements like a window title and a button which use the same word (e.g. "Restart").
When translatable strings have interpolated values, like %s
or %d
, extensions should use the String.prototype.format()
method. This method is applied to the prototype of String
by GNOME Shell, and is the appropriate method for translations.
The translation functions are imported from the same module as the Extension
base class, and gettext()
can be aliased to _()
at the same time.
import St from 'gi://St';
import {Extension, gettext as _, ngettext, pgettext} from 'resource:///org/gnome/shell/extensions/extension.js';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
export default class ExampleExtension extends Extension {
enable() {
// Create a panel button
this._indicator = new PanelMenu.Button(0.0, this.metadata.name, false);
// Add an icon
const icon = new St.Icon({
icon_name: 'face-laugh-symbolic',
style_class: 'system-status-icon',
});
this._indicator.add_child(icon);
// Add the indicator to the panel
Main.panel.addToStatusArea(this.uuid, this._indicator);
// A string needing more context is marked with `pgettext()`
this._indicator.menu.addAction(pgettext('menu item', 'Notify'), () => {
this._count += 1;
// A regular translatable string is marked with the `_()` function
const title = _('Notification');
// A "countable" string is marked with the `ngettext()` function
const body = ngettext('You have been notified %d time',
'You have been notified %d times',
this._count).format(this._count);
Main.notify(title, body);
});
this._count = 0;
}
disable() {
this._indicator?.destroy();
this._indicator = null;
}
}
Preparing Translations
Your extension will provide a template file (e.g. example@gjs.guide.pot
) that contains a list of all the translatable strings in your project. Translators will use this template to create a translation file (e.g. fr.po
for French).
Start by creating a po/
subdirectory to hold the translation source files:
$ mdkir -p ~/.local/share/gnome-shell/extensions/example@gjs.guide/po
Scanning for Translatable Strings
TIP
Whenever translatable strings are added or removed from a project, you must regenerate the POT file.
Gettext uses a POT file (portable object template) to store a list of all the translatable strings. You can generate the POT file by scanning your extension's source code with xgettext
:
$ cd ~/.local/share/gnome-shell/extensions/example@gjs.guide
$ xgettext --from-code=UTF-8 --output=po/example@gjs.guide.pot *.js
Generated example@gjs.guide.pot
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-08-10 19:00-0700\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
#: extension.js:24
msgctxt "menu item"
msgid "Notify"
msgstr ""
#: extension.js:28
msgid "Notification"
msgstr ""
#: extension.js:31
#, javascript-format
msgid "You have been notified %d time"
msgid_plural "You have been notified %d times"
msgstr[0] ""
msgstr[1] ""
Translators can use the .pot
file to create a .po
file translated for their language with a program like Gtranslator or POEdit.
Compiling Translations
Using the gnome-extensions
tool makes it easy to compile and include the translations with your extension. Simply pass the relative directory po
to the --podir
option when packing your extension:
$ gnome-extensions pack --podir=po example@gjs.guide
Next Steps
While developing the user interface, keep in mind that your extension may now be used in a language written from left-to-right or right-to-left. You may also want to consider registering your project with a translation service like Weblate or Crowdin.
Next you can create Preferences for your extension, allowing users to configure the appearance and behavior of the extension.