Development/Tutorials/Localization/i18n Build Systems

    From KDE TechBase
    Building KDE's l10n Module
    Tutorial Series   Localization
    Previous   Writing Applications With Localization in Mind
    What's Next   n/a
    Further Reading   n/a

    Abstract

    Now that your application is ready to be localized, we next look at how to incorporate the necessary mechanisms into the CMake build system of your application.

    CMake and i18n

    Creating message catalogs has been dramatically simplified for KDE4. In the l10n/scripts module in KDE's subversion repository you will find a file called extract-messages.sh. When this script is run it will create all the necessary files to begin the translation process.

    Note
    Currently the l10n scripts are not in l10n/scripts but in branches/work/kde4-l10n/scripts/ as they are still under development.


    Warning
    This section needs improvements: Please help us to

    cleanup confusing sections and fix sections which contain a todo


    Add in a CMakeLists.txt recipe.

    The Translation Process

    Assuming the build system is set up properly, the following outlines the process that occurs to translate your application.

    Tip
    If your application is in the KDE code repository, all this happens automatically. If your code is not in the KDE repository, you must perform these steps yourself. See the section below on distributing message catalogs.


    Periodically, the script extract-messages.sh (a.k.a. scripty) runs on the KDE server. This program extracts all the i18n messages from your application per the messages targets in your Makefile.am's. The extracted messages are stored in template (.pot) files in the templates folder of the l10n module. See What Is Scripty for more information.

    The KDE translation teams translate the messages and commit them into a messages folder corresponding to the language, which is further broken down by module. For example, German translated messages for konqueror are committed to l10n/de/messages/kdebase/konqueror.po.

    When the l10n module is built, the .po files are compiled into a binary format for fast lookup and installed as .mo files to $KDEDIR/share/locale/xx/LC_MESSAGES/, where xx is the two-letter ISO 639 code for the language. These are called the message catalogs.

    At runtime, the i18n(...) function, using the original string you coded, looks up the string in the message catalog of the user's desktop language and returns the translated string. If the message catalog is missing, or the specific string is not found, i18n(...) falls back to the original string in your code.

    .desktop files in your project are handled separately. makemessages extracts strings, such as Name and Comment from the .desktop files and places them into a file named desktop_mmmm.pot, where mmmm is the module name, in the templates folder. Once translators have translated this file, makemessages inserts the translated strings back into the .desktop files. The list of strings extracted is in l10n/scripts/apply.cc. Here's the code that checks for them:

    if (checkTag("Name", in, argc, argv, newFile))

       continue;
    

    if (checkTag("Comment", in, argc, argv, newFile))

       continue;
    

    if (checkTag("Language", in, argc, argv, newFile))

       continue;
    

    if (checkTag("Keywords", in, argc, argv, newFile))

       continue;
    

    if (checkTag("About", in, argc, argv, newFile))

       continue;
    

    if (checkTag("Description", in, argc, argv, newFile))

       continue;
    

    if (checkTag("GenericName", in, argc, argv, newFile))

       continue;
    

    if (checkTag("Query", in, argc, argv, newFile))

       continue;
    

    if (checkTag("ExtraNames", in, argc, argv, newFile))

       continue;
    

    Runtime Loading Of Catalogs

    To have translations show up properly in an application, the name of the .pot file must match the name of the message catalog (.mo) file that your application will read at runtime. But what name will be used at runtime?

    In general, it will be the value returned by KGlobal::instance()->instanceName(). But what will that be? The general rules for standalone applications are:

    • the message catalog will default to the name of the application passed as the first argument to KAboutData
    • if your code calls KLocale::setMainCatalog(), that name will be used instead
    • if your code calls KLocale::insertCatalog(const QString&), the specified catalog will also be searched in addition to the main catalog

    If your code does not call either of the KLocale methods mentioned above and your application is a plugin, it gets a little more complicated. If your code exports your plugin using the K_EXPORT_COMPONENT_FACTORY macro, like this:

    K_EXPORT_COMPONENT_FACTORY( libkhtmlkttsdplugin,

     KGenericFactory<KHTMLPluginKTTSD>("khtmlkttsd") )
    

    then the catalog name will be the name passed in the KGenericFactory constructor - in the example above that woudl be khtmlkttsd. This is because the macro creates a Kinstance for you with the specified name.

    However, some classes, such as KTextEditor create a KPart containing your component and the name passed in the macro is not used. In this case, you should call KLocale::insertCatalog(const QString&) in the component's constructor.

    If in doubt, the safest thing to do is to call KLocale::insertCatalog(const QString&).

    Tip
    If you're not using the export macro for your plugin, you probably should be. See the API documentation of KGenericFactory for more information.


    Calling KLocale::insertCatalog(const QString&) if the catalog has already been inserted does nothing. However, calling KLocale::removeCatalog(const QString&) removes the catalog no matter how many times it has been inserted. Therefore, take care that you do not inadvertently remove the catalog when multiple components are sharing a single catalog. For example, the following sequence will break translations:

    // Component A loads and uses catalog xyz by calling insertCatalogue("xyz"); // Component B loads and also uses the same catalog. insertCatalogue("xyz"); // component B unloads and calls removeCatalogue("xyz") // Component A now doesn't translate properly.

    Notice that a sequence such as above will occur automatically if both components A and B are loaded using the K_EXPORT_COMPONENT_FACTORY macro and pass the same name argument to the KGenericFactory constructor.

    Naming .pot Files

    The name that you settle on for both the .pot file and catalog should be governed by where your code resides. If it is a component of another application and resides in that application's source tree, you'll want to share the main catalog of the application.

    If is a component of another application but resides elsewhere in the code repository, you will probably have to create a separate .pot, but keep in mind that .pot filenames must be unique across all of KDE.

    If it is a component of another application, but resides in the source tree of a second application, you should share with the second application. For example, the KDE text-to-speach daemon (KTTSD) includes a plugin for embedded Kate in kdeaccessibility/kttsd source tree. Since the plugin is not installed unless kttsd is installed, it shares its catalog with kttsd and calls KLocale::insertCatalog("kttsd") to do this.

    Distributing Message Catalogs

    Note
    The following improvements are needed in this section:

    How can messages for .desktop files be extracted? How can translated .desktop messages be inserted back into .desktop files?

    "make -f admin/Makefile.common package-messages" is obviously no longer correct


    If your application is in the KDE code repository, you don't have to do anything to distribute message catalogs. Users will either download the l10n source and build it or install binaries prepared by packagers.

    In addition, if your top-level folder and .pot file have the same name, the svn2dist script will automatically include .po files when you use it to make a source tarball for your app.

    If your application is not in the KDE code repository, you have to extract the messages yourself and provide the translated PO files within your distribution. To extract the messages you need to have xgettext installed and extractrc from kdesdk has to be in your path.


    Warning
    This section needs improvements: Please help us to

    cleanup confusing sections and fix sections which contain a todo


    The following paragraph needs to be updated to reflect the new work going on in branches/work/kde-l10n/scripts

    Then do the following commands in the base folder of your KDE application's source:

    mkdir po make -f admin/Makefile.common package-messages echo po>>SUBDIRS echo "POFILES = AUTO">po/Makefile.am

    After that you will find a file <appname>.pot, which contains all messages of your application in the subfolder po.

    Now you just have to find translators to translate the extracted messages into the various languages. As your application gets used by more people, you will find that translators will volunteer to do this. Translated PO files then have to be stored in the po folder with the naming scheme <languagecode>.po.

    Technical details

    The actual translation is done with the gettext package. It contains tools for extracting messages from source files and to handle changed messages, so that translators do not have to start over and over again. The extracted messages and the translations are stored in so called ``PO files using different encodings. These files are then compiled into a binary format (MO files) which then get installed.

    All translations of a language have to be stored in the same encoding which is defined in the charset file. KLocale reads this file when constructed and uses this information to decode the translations.

    Nowadays, UTF-8 is required as the PO files encoding in KDE code repository.

    Handbooks

    Handbooks, which are written in docbook format, are handled different from applications. The translated Handbooks are committed into the l10n module in the docs folder under each language. In addition, the docs folder is broken down by module and application. For example, the German Kate Handbook is committed to the l10n/de/docs/kdebase/kate/ folder. When compiled, the German Kate Handbook is installed to $KDEDIR/share/doc/HTML/de/kate/index.cache.bz2.

    Note that it is up to each translation team to generate the translated Handbook when they feel it is complete.