Nova Flow OS
KDE Developer Platform
KDE Developer Platform
  • KDE Developer Platform
    • Getting started
      • Building KDE software
        • KDE software
        • Where to find the development team
        • Learning more
        • Choose what to work on
        • Source code cross-referencing
        • Installing build dependencies
        • Set up a development environment
        • Building KDE software with kdesrc-build
        • Basic troubleshooting
        • Tips and tricks
        • IDE Configuration
          • Setting up an IDE for KDE development
          • Visual Studio Code
          • Qt Creator
          • Kate
          • KDevelop
          • CLion
          • Sublime Text
        • Building KDE software manually
        • Building KDE software with distrobox and podman
      • Kirigami
        • KDE is ours
        • Setting up and getting started
        • Explaining pages
        • Layouts, ListViews, and Cards
        • Adding actions
        • Adding a dialog
        • Using separate files
        • Next steps
        • Colors and themes in Kirigami
        • Typography
        • Actions based components
        • Page rows and page stacks
        • Scrollable pages and list views
        • Cards
        • Drawers
        • Chips
        • Dialog types
        • Controls and interactive elements
        • Form layouts
        • Inline messages
        • Action toolbars
        • Progress bars and indicators
        • List views
        • Understanding CMakeLists
        • Figuring out main.cpp
        • Connect logic to your QML user interface
        • Connect models to your QML user interface
        • About page
        • Introduction to Kirigami Addons
        • FormCard About pages
        • Form delegates in your settings pages
      • KXmlGui
        • Getting started with KXmlGui
        • Hello World!
        • Creating the main window
        • Using actions
        • Saving and loading
        • Command line interface
      • Python with Kirigami
        • Apps with QML and Python
        • Your first Python + Kirigami application
        • Creating a Python package
        • Creating a Flatpak
      • Common programming mistakes
      • Adding a new KDE project
    • Features
      • Icons
      • Configuration
        • The KConfig Framework
        • Introduction to KConfig
        • Using KConfig XT
        • KDE Frameworks 6 porting guide
        • Settings module (KCM) development
        • KConfigDialog
      • D-Bus
        • What is D-Bus practically useful for?
        • Introduction to D-Bus
        • Accessing D-Bus interfaces
        • Intermediate D-Bus
        • Creating D-Bus interfaces
        • Using custom types with D-Bus
        • D-Bus autostart services
      • Create your own mouse cursor theme
      • Session management
      • Archives
      • Desktop file
      • KAuth
        • Privilege Escalation
        • Using actions in your applications
      • KIdleTime
      • Akonadi: personal information management
        • Debugging Akonadi Resources
        • Using Akonadi in applications
      • Concurrent programming
      • Solid
      • Sonnet
    • Plasma themes and plugins
      • Getting started
      • Plasma Widget tutorial
        • How to create a plasmoid
        • Setup
        • Porting Plasmoids to KF6
        • Testing
        • QML
        • Plasma's QML API
        • Widget Properties
        • Configuration
        • Translations / i18n
        • Examples
        • C++ API
      • KWin Effects
      • Plasma Desktop scripting
        • Javascript Interaction With Plasma Shells
        • Templates
        • Examples
        • API documentation
        • Configuration keys
      • Plasma Style tutorial
        • Creating a Plasma Style quickstart
        • Understanding Plasma Styles
        • SVG elements and Inkscape
        • Background SVG format
        • System and accent colors
        • Theme elements reference
        • Porting themes to Plasma 5
        • Porting themes to Plasma 6
      • Aurorae window decorations
      • KWin scripting tutorial
        • Quick start
        • KWin scripting API
      • Wallpapers
      • Plasma comic
        • Tutorial
        • Testing and debugging
        • Examples
      • Create a custom Window Switcher
      • KRunner C++ Plugin
        • Basic Anatomy of a Runner
        • KRunner metadata format
    • Applications
      • Creating sensor faces
      • Dolphin
        • Creating Dolphin service menus
      • Kate
        • Kate plugin tutorial
      • KMines
        • Making a KMines theme
      • Writing tests
        • Appium automation testing
    • Packaging
      • Android
        • KDE on Android
        • Building applications for Android
        • Packaging and publishing applications for Android
        • Publishing on Google Play
          • Introduction
          • Packaging your app
          • Adding your app to Google Play
          • Publishing your app
          • Releasing new versions of old apps
        • Porting applications to Android
          • Basic porting
          • Making applications run well on Android
          • Metadata
      • Windows
        • Packaging and publishing applications for Windows
        • Publish your app in the Microsoft Store
          • Packaging your app for the Microsoft Store
          • Submitting your app to the Microsoft Store
      • Plasma Mobile
        • KDE on mobile devices
        • Porting a new device to Plasma Mobile
        • KDE Telephony stack
          • General Overview
          • Kernel layer
          • System daemons
            • General overview
            • Developing Telephony functionality
            • ModemManager Telephony functions
          • Session daemons
          • QML declarative plugin layer
          • KDE application layer
        • Execute applications
      • Distributing KDE software as Flatpak
        • Your first Flatpak
        • Extending your package
        • Nightly Flatpaks and Flathub
        • Testing your Flatpak
    • System administration
      • Shell scripting with KDE dialogs
      • Kiosk: Simple configuration management for large deployment
        • Abstract
        • Introduction to Kiosk
        • Kiosk keys
    • Contribute to the documentation
    • About
      • Readme
      • License
        • Creative Commons Attribution-ShareAlike 4.0 International
        • GNU General Public License 3.0 or later
Powered by GitBook
On this page
  • ki18n
  • i18n()
  • Variables in i18n()
  • Plural in i18n()
  • Translation Folder Structure
  • Install GetText
  • Generating template.pot
  • Updating template.pot
  • Examining template.pot
  • fr.po
  • Merging updates into fr.po
  • Building .mo
  • Testing our translations
  • Reusing other translations
  1. KDE Developer Platform
  2. Plasma themes and plugins
  3. Plasma Widget tutorial

Translations / i18n

Translate your widget

PreviousConfigurationNextExamples

Last updated 8 months ago

ki18n

(KDE internationalization) is the translation library for KDE. It has a which you can read, but we'll cover the basics here.

i18n()

Translated strings need to be wrapped in the i18n(...) function. Note that single quotes i18n('Test') will be ignored by the tool that parses your code for all the translation strings. Always use double quotes i18n("Test").

contents/ui/configGeneral.qml

import QtQuick 2.0
import QtQuick.Controls 2.5
import org.kde.kirigami 2.4 as Kirigami

Kirigami.FormLayout {
    CheckBox {
        Kirigami.FormData.label: i18n("Feature:")
        text: i18n("Enabled")
    }
}

Variables in i18n()

The i18n(...) is an overloaded function which allows you to pass values into the translation i18n(format, variable1, variable2). Just place %1 where you want the first variable to be substituted, and %2 where the second should go.

contents/ui/main.qml

Item {
    id: showThing
    property int unreadEmailCount: 3
    Plasmoid.toolTipSubText: i18n("%1 unread emails", unreadEmailCount)
}

Plural in i18n()

In English, a translated sentence is different when there's just 1 item from when there is 2 or more items. i18np(...) can be used in such a situation.

i18np("One image in album %2", "%1 images in album %2", numImages, albumName)
i18np("One image in album %2", "More images in album %2", numImages, albumName)

Using i18np(...) can improve our previous example. When unreadEmailCount was 1, the tooltip would have read "1 unread emails".

contents/ui/main.qml

Item {
    id: showThing
    property int unreadEmailCount: 3
    Plasmoid.toolTipSubText: i18np("%1 unread email", "%1 unread emails", unreadEmailCount)
}

Translation Folder Structure

After we've wrapped all the messages in our code with i18n(...) calls, we then need to extract all the messages for our translators into a template.pot file which they can then create a fr.po for their French translations.

We'll place the template.pot file under a translate folder inside the bundled package so that our users can easily translate our widget when they go poking into our code.

We'll also create a merge.sh script which will extract the messages from our code into a template.pot, then update the translated fr.po file with any changes.

Lastly, we'll make a build.sh script to convert the fr.po text files into the binary .mo files which are needed for KDE to recognize the translations.

The latest copy of my `merge.sh` and `build.sh` can be found here:

A working example can be seen in the Tiled Menu widget:

└── ~/Code/plasmoid-helloworld/
    └── package
        ├── contents
        │   └── ...
        ├── translate
        │   ├── build.sh
        │   ├── fr.po
        │   ├── template.pot
        │   └── merge.sh
        └── metadata.desktop

After running build.sh we should end up with:

└── ~/Code/plasmoid-helloworld/
    └── package
        ├── contents
        │   └── locale
        │       └── fr
        │           └── LC_MESSAGES
        │               └── plasma_applet_com.github.zren.helloworld.mo
        └── ...

Install GetText

After we've wrapped all the messages in our code with i18n(...) calls, we then need to extract all the messages for our translators into a template.pot file.

To do this, we need to install the gettext package.

sudo apt install gettext

Generating template.pot

First thing we need to do in our merge.sh script, is list all files we wish to get translated in our widgets code.

The latest copy of my complete `merge.sh` script [can be found here](https://github.com/Zren/plasma-applet-lib/blob/master/package/translate/merge).

DIR is the directory (absolute path to package/translate/) since we may run the merge script from another directory.

We use kreadconfig5 to grab the widget's namespace (com.github.zren.helloworld) and store it in plasmoidName. We then remove the beginning of the namespace so we are left with helloworld and store that in widgetName. We also grab the website which a link to the GitHub repo for use as the bugAddress.

After validating that plasmoidName is not an empty string with bash's [ -z "$plasmoidName" ] operator, we then list all .qml and .js files using find and store the results of the command in a temporary infiles.list file.

Then we generate a template.pot.new using the xgettext command. After generating it, we use sed to replace a few placeholder strings.

translate/merge.sh

#!/bin/sh

DIR=`cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd`
plasmoidName=`kreadconfig5 --file="$DIR/../metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Name"`
widgetName="${plasmoidName##*.}" # Strip namespace
website=`kreadconfig5 --file="$DIR/../metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Website"`
bugAddress="$website"
packageRoot=".." # Root of translatable sources
projectName="plasma_applet_${plasmoidName}" # project name

#---
if [ -z "$plasmoidName" ]; then
    echo "[merge] Error: Couldn't read plasmoidName."
    exit
fi

#---
echo "[merge] Extracting messages"
find "${packageRoot}" -name '*.cpp' -o -name '*.h' -o -name '*.c' -o -name '*.qml' -o -name '*.js' | sort > "${DIR}/infiles.list"

xgettext \
    --files-from=infiles.list \
    --from-code=UTF-8 \
    --width=400 \
    --add-location=file \
    -C -kde -ci18n -ki18n:1 -ki18nc:1c,2 -ki18np:1,2 -ki18ncp:1c,2,3 -ktr2i18n:1 -kI18N_NOOP:1 \
    -kI18N_NOOP2:1c,2  -kN_:1 -kaliasLocale -kki18n:1 -kki18nc:1c,2 -kki18np:1,2 -kki18ncp:1c,2,3 \
    --package-name="${widgetName}" \
    --msgid-bugs-address="${bugAddress}" \
    -D "${packageRoot}" \
    -D "${DIR}" \
    -o "template.pot.new" \
    || \
    { echo "[merge] error while calling xgettext. aborting."; exit 1; }

sed -i 's/"Content-Type: text\/plain; charset=CHARSET\\n"/"Content-Type: text\/plain; charset=UTF-8\\n"/' "template.pot.new"
sed -i 's/# SOME DESCRIPTIVE TITLE./'"# Translation of ${widgetName} in LANGUAGE"'/' "template.pot.new"
sed -i 's/# Copyright (C) YEAR THE PACKAGE'"'"'S COPYRIGHT HOLDER/'"# Copyright (C) $(date +%Y)"'/' "template.pot.new"

Updating template.pot

Continuing our merge.sh script, we then check to see if an older template.pot file exists.

If it does, we'll replace the POT-Creation-Date in the new file with the older creation date, then run the diff command to detect if there's been any changes. If there has been changes, we fix the POT-Creation-Date and overwrite the old template.pot file. To make the changes more noticeable, we also list the added/removed translation messages.

If there hasn't been any changes, we simply delete the template.pot.new file.

Lastly, we delete the infiles.list to clean things up.

translate/merge.sh

if [ -f "template.pot" ]; then
    newPotDate=`grep "POT-Creation-Date:" template.pot.new | sed 's/.\{3\}$//'`
    oldPotDate=`grep "POT-Creation-Date:" template.pot | sed 's/.\{3\}$//'`
    sed -i 's/'"${newPotDate}"'/'"${oldPotDate}"'/' "template.pot.new"
    changes=`diff "template.pot" "template.pot.new"`
    if [ ! -z "$changes" ]; then
        # There's been changes
        sed -i 's/'"${oldPotDate}"'/'"${newPotDate}"'/' "template.pot.new"
        mv "template.pot.new" "template.pot"

        addedKeys=`echo "$changes" | grep "> msgid" | cut -c 9- | sort`
        removedKeys=`echo "$changes" | grep "< msgid" | cut -c 9- | sort`
        echo ""
        echo "Added Keys:"
        echo "$addedKeys"
        echo ""
        echo "Removed Keys:"
        echo "$removedKeys"
        echo ""

    else
        # No changes
        rm "template.pot.new"
    fi
else
    # template.pot didn't already exist
    mv "template.pot.new" "template.pot"
fi

rm "${DIR}/infiles.list"
echo "[merge] Done extracting messages"

Examining template.pot

Now that we've got a template.pot, let's take a look at it.

The messages we want to translate appear as msgid "Show Thing", with the file it came from appearing in a comment in the line above. Underneath is an empty msgstr "" which is where the translator will place the translated messages.

translate/template.pot

# Translation of helloworld in LANGUAGE
# Copyright (C) 2018
# This file is distributed under the same license as the helloworld package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: helloworld \n"
"Report-Msgid-Bugs-To: https://github.com/Zren/plasmoid-helloworldplugin\n"
"POT-Creation-Date: 2018-12-03 18:47-0500\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=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: ../contents/configGeneral.qml
msgid "Show notification"
msgstr ""

#: ../contents/ui/configGeneral.qml
msgid "%1 unread emails"
msgstr ""

#: ../contents/ui/configGeneral.qml
msgid "%1 unread email"
msgid_plural "%1 unread emails"
msgstr[0] ""
msgstr[1] ""

fr.po

Now that we've got a template.pot, our translators can copy it and rename it to fr.po.

We use fr since it is the locale code for French, which we'll be using later.

Translators can then start filling out the empty msgstr "" with translations.

translate/fr.po

#: ../contents/configGeneral.qml
msgid "Show notification"
msgstr "Montrer les notifications"

Merging updates into fr.po

Our merge.sh currently only extracts messages into template.pot. We should next merge any new messages extracted for translation into our fr.po file.

We'll first filter the translate directory for .po files.

Then for each .po file, we'll extract the locale code (fr) from the filename using the basename command then striping out the file extension.

We then use another GetText command msgmerge to generate a new fr.po.new file based on the old fr.po and the current template.pot.

Afterwards, we use sed to replace the LANGUAGE placeholder with our current locale code in case our translator left them as is.

When we're done, we overwrite the old fr.po with fr.po.new.

translate/merge.sh

#---
echo "[merge] Merging messages"
catalogs=`find . -name '*.po' | sort`
for cat in $catalogs; do
    echo "[merge] $cat"
    catLocale=`basename ${cat%.*}`
    msgmerge \
        --width=400 \
        --add-location=file \
        --no-fuzzy-matching \
        -o "$cat.new" \
        "$cat" "${DIR}/template.pot"
    sed -i 's/# SOME DESCRIPTIVE TITLE./'"# Translation of ${widgetName} in ${catLocale}"'/' "$cat.new"
    sed -i 's/# Translation of '"${widgetName}"' in LANGUAGE/'"# Translation of ${widgetName} in ${catLocale}"'/' "$cat.new"
    sed -i 's/# Copyright (C) YEAR THE PACKAGE'"'"'S COPYRIGHT HOLDER/'"# Copyright (C) $(date +%Y)"'/' "$cat.new"

    mv "$cat.new" "$cat"
done

echo "[merge] Done merging messages"

Building .mo

Once our fr.po has been filled out, we can then convert it to a binary .mo file. So lets get started on our build.sh script.

The latest copy of my complete `build.sh` script [can be found here](https://github.com/Zren/plasma-applet-lib/blob/master/package/translate/build).

We start with the same code that we used in our merge.sh script to parse our metadata.desktop file and get the widget's namespace. We also reuse the same code to iterate the .po files.

Then we use another GetText command msgfmt to convert the fr.po file into a fr.mo file.

We then make sure a contents/locale/fr/LC_MESSAGES/ folder exists, creating it if it does not.

Then we copy the fr.mo to the LC_MESSAGES folder, renaming it to plasma_applet_com.github.zren.helloworld.mo. Notice that we put plasma_applet_ in front of the widget's namespace.

translate/build.sh

#!/bin/sh

DIR=`cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd`
plasmoidName=`kreadconfig5 --file="$DIR/../metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Name"`
website=`kreadconfig5 --file="$DIR/../metadata.desktop" --group="Desktop Entry" --key="X-KDE-PluginInfo-Website"`
bugAddress="$website"
packageRoot=".." # Root of translatable sources
projectName="plasma_applet_${plasmoidName}" # project name

#---
if [ -z "$plasmoidName" ]; then
    echo "[build] Error: Couldn't read plasmoidName."
    exit
fi

#---
echo "[build] Compiling messages"

catalogs=`find . -name '*.po' | sort`
for cat in $catalogs; do
    echo "$cat"
    catLocale=`basename ${cat%.*}`
    msgfmt -o "${catLocale}.mo" "$cat"

    installPath="$DIR/../contents/locale/${catLocale}/LC_MESSAGES/${projectName}.mo"

    echo "[build] Install to ${installPath}"
    mkdir -p "$(dirname "$installPath")"
    mv "${catLocale}.mo" "${installPath}"
done

echo "[build] Done building messages"

Testing our translations

First make sure you run our build.sh translation script.

Next we need to install the language pack we want to test. We'll be testing with fr_CA (Canadian French).

  • Manjaro: You'll need to go to System Settings > Locale > Add > Français > Canada. Click Apply, enter your password, then wait for it to generate the language pack.

Then we need to override the locale environment variables just for our plasmoidviewer instance. If you run the locale command, it should list all the environment variables available to override.

In practice, we only need to override LANG="fr_CA.UTF-8" and another variable it didn't list LANGUAGE="fr_CA:fr". If your widget is a clock, then you might also need to override LC_TIME="fr_CA.UTF-8".

sh package/translate/build.sh
LANGUAGE="fr_CA:fr" LANG="fr_CA.UTF-8" plasmoidviewer -a package
$ locale
LANG=en_CA.UTF-8
LC_CTYPE="en_CA.UTF-8"
LC_NUMERIC=en_CA.UTF-8
LC_TIME=en_US.UTF-8
LC_COLLATE="en_CA.UTF-8"
LC_MONETARY=en_CA.UTF-8
LC_MESSAGES="en_CA.UTF-8"
LC_PAPER=en_CA.UTF-8
LC_NAME=en_CA.UTF-8
LC_ADDRESS=en_CA.UTF-8
LC_TELEPHONE=en_CA.UTF-8
LC_MEASUREMENT=en_CA.UTF-8
LC_IDENTIFICATION=en_CA.UTF-8
LC_ALL=

Reusing other translations

While it is bad practice to link to private code, if you know another widget has translated a string, you can use i18nd(domain, string, ...) to use translations from that domain. Note that a widget's domain starts with plasma_applet_, and ends with the widget's X-KDE-PluginInfo-Name.

Eg: plasma_applet_com.github.zren.helloworld

CheckBox {
    text: i18nd("plasma_applet_org.kde.plasma.digitalclock", "Show date")
}
CheckBox {
    text: i18nd("plasma_applet_org.kde.plasma.digitalclock", "Show seconds")
}
Button {
    text: i18nd("plasma_wallpaper_org.kde.image", "Open Wallpaper Image")
}

An example from the is:

In , we also parse the widget name in metadata.desktop first with xgettext --language=Desktop and --join-existing in the 2nd xgettext call.

A full list of locale codes . Make sure you use underscores (fr_CA) instead of dashes (fr-CA) if the language you are translating is not reusable for the generic fr language.

Kubuntu: You'll need to .

OpenSUSE: You'll need to .

An example can be found in org.kde.image's which reuses the same code for the org.kde.slideshow.

Ki18n
programmer's guide
Ki18n docs
translate/merge.sh
translate/build.sh
Zren/plasma-applet-tiledmenu/.../translate/
the complete merge.sh
can be found on StackOverflow
install the language-pack-fr package
install a secondary language using YaST
main.qml