Skip to content

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.

json
{
    "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
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
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.

js
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:

sh
$ 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:

sh
$ 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
sh
# 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:

sh
$ 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.

MIT Licensed | GJS, A GNOME Project