Example
| Chapter 27. Internationalization and Localization |

## Example

Here is a simple example with translatable strings in C++ code. It shows what you can do if you don't use autotools or meson. It is also an example of using the `Gtk::TextView` widget.

This program can set the locale, if it's specified in a configuration file. This is not normal behavior. Most programs accept the locale that the user has selected, for instance with an environment variable.

Figure 27.1. Translatable Strings

Source Code

File: `examplewindow.h` (For use with gtkmm 4)

    #ifndef GTKMM_EXAMPLEWINDOW_H
    #define GTKMM_EXAMPLEWINDOW_H

    #include <gtkmm.h>

    class ExampleWindow : public Gtk::Window
    {
    public:
      ExampleWindow();
      ~ExampleWindow() override;

      // Configuration file, showing configured locale.
      static const std::string config_file_name;

    protected:
      void fill_text_tag_table();
      void fill_buffer();

      void on_dropdown_changed();

      // Child widgets
      Gtk::HeaderBar m_HeaderBar;
      Gtk::DropDown m_DropDown;
      Gtk::ScrolledWindow m_ScrolledWindow;
      Gtk::TextView m_TextView;

      Glib::RefPtr<Gtk::StringList> m_StringList;
      Glib::RefPtr<Gtk::TextTagTable> m_refTextTagTable;
      Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer;
    };

    #endif // GTKMM_EXAMPLEWINDOW_H

File: `examplewindow.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <fstream>
    #include <iostream>
    #include <glibmm/i18n.h>

    const std::string ExampleWindow::config_file_name("appconfig.cfg");

    ExampleWindow::ExampleWindow()
    {
      set_title(_("English i18n example"));
      set_default_size(500, 300);

      // Header bar
      m_HeaderBar.set_show_title_buttons(true);
      m_HeaderBar.pack_start(m_DropDown);

      // Set headerbar as titlebar
      set_titlebar(m_HeaderBar);

      // Fill the dropdown
      const std::vector<Glib::ustring> strings{
        "Locale", "en_US.UTF-8", "de_DE.UTF-8", "cs_CZ.UTF-8", "sv_SE.UTF-8"
      };
      m_StringList = Gtk::StringList::create(strings);
      m_DropDown.set_model(m_StringList);
      m_DropDown.set_selected(0);

      m_DropDown.property_selected().signal_changed().connect(
        sigc::mem_fun(*this, &ExampleWindow::on_dropdown_changed));

      // Add the TextView, inside a ScrolledWindow.
      m_ScrolledWindow.set_child(m_TextView);
      set_child(m_ScrolledWindow);

      fill_text_tag_table();
      fill_buffer();
      m_TextView.set_buffer(m_refTextBuffer);
    }

    ExampleWindow::~ExampleWindow()
    {
    }

    void ExampleWindow::on_dropdown_changed()
    {
      const auto selected = m_DropDown.get_selected();
      const auto text = m_StringList->get_string(selected);
      std::cout << "DropDown changed: " << text << std::endl;
      if (selected > 0)
      {
        std::ofstream myfile;
        myfile.open(config_file_name);
        if (myfile.is_open())
        {
          myfile << text << std::endl;
          myfile.close();
        }
        else
          std::cout << "Could not open file " << config_file_name << std::endl;
      }
    }

    void ExampleWindow::fill_text_tag_table()
    {
      m_refTextTagTable = Gtk::TextTagTable::create();
      auto textTag = Gtk::TextTag::create("plain-text");
      textTag->property_wrap_mode() = Gtk::WrapMode::WORD;
      m_refTextTagTable->add(textTag);

      textTag = Gtk::TextTag::create("script");
      textTag->property_wrap_mode() = Gtk::WrapMode::NONE;
      textTag->property_indent() = 20;
      textTag->property_family() = "Monospace";
      textTag->property_background() = "rgb(92%, 92%, 92%)"; // Light gray
      m_refTextTagTable->add(textTag);
    }

    void ExampleWindow::fill_buffer()
    {
      m_refTextBuffer = Gtk::TextBuffer::create(m_refTextTagTable);

      // Get beginning of buffer; each insertion will revalidate the
      // iterator to point to just after the inserted text.
      auto iter = m_refTextBuffer->begin();

      iter = m_refTextBuffer->insert_with_tag(iter,
        "To build myapp with messages in a different language, e.g. German,"
        " do this in a terminal window:\n\n",
        "plain-text");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "g++ -o myapp main.cc examplewindow.cc `pkg-config --cflags --libs gtkmm-4.0` -std=c++17\n\n",
        "script");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "Before you run myapp, create the files with translated texts:\n\n", "plain-text");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "mkdir po\n"
        "cd po\n"
        "cat >POTFILES.in <<EOF\n"
        "# List of source files containing translatable strings.\n"
        "main.cc\n"
        "examplewindow.cc\n"
        "EOF\n\n", "script");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "Export all quoted texts from source code: _(\"text\") to file myapp.pot:\n\n",
        "plain-text");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "intltool-update --pot --gettext-package myapp\n\n", "script");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "Create the de.po file:\n\n", "plain-text");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "msginit --no-translator --locale de_DE.UTF-8\n\n", "script");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "Now run poedit or some other editor, open file de.po and translate texts of messages."
        " Write the translations after 'msgstr'."
        " If charset is not UTF-8 in the .po file, change to UTF-8."
        " Then save the de.po file. (Later, to update the .po file, use ",
        "plain-text");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "intltool-update de", "script");
      iter = m_refTextBuffer->insert_with_tag(iter,
        ".) Still in the po directory, create myapp.mo from de.po:\n\n", "plain-text");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "mkdir --parents ../locale/de/LC_MESSAGES\n"
        "msgfmt --check --verbose --output-file ../locale/de/LC_MESSAGES/myapp.mo de.po\n\n",
        "script");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "Run myapp with specified language:\n\n", "plain-text");
      iter = m_refTextBuffer->insert_with_tag(iter,
        "cd ..\n"
        "LANG=de_DE.UTF-8 ./myapp\n\n", "script");
    }

File: `main.cc` (For use with gtkmm 4)

    #include "examplewindow.h"
    #include <fstream>
    #include <iostream>
    #include <string>
    #include <locale>
    #include <clocale>
    #include <glibmm/i18n.h>

    const char* GETTEXT_PACKAGE = "myapp";
    const char* PROGRAMNAME_LOCALEDIR = "locale";

    int main(int argc, char* argv[])
    {
      std::ifstream myfile(ExampleWindow::config_file_name);
      if (myfile.is_open())
      {
        // Set the configured locale.
        std::string line;
        std::getline(myfile, line);
        std::cout <<"Preference settings: "<< line << std::endl;
        // Don't use the user's preferred locale.
        Glib::set_init_to_users_preferred_locale(false);
        Glib::setenv("LANG", line, true);
        std::cout << "getenv(LANG)= " << Glib::getenv("LANG") << '\n';
        std::setlocale(LC_ALL, line.c_str());
        myfile.close();
      }
      // If the configuration file does not exist, the user's preferred locale is used.

      bindtextdomain(GETTEXT_PACKAGE, PROGRAMNAME_LOCALEDIR);
      bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
      textdomain(GETTEXT_PACKAGE);

      // Gtk::Application::create() calls Glib::init(), which sets the C++ global locale.
      auto app = Gtk::Application::create("org.gtkmm.example");

      // On startup, when std::cout is created, the global locale is the "C" locale,
      // AKA the classic locale.
      std::cout << _("English App is Running") << ", "
        << std::cout.getloc().name() << " locale: " << 1234.56 << '\n';

      // Use the new global locale for future output to std::cout.
      std::cout.imbue(std::locale());

      std::cout << _("English App is Running") << ", "
        << std::cout.getloc().name() << " locale: " << 1234.56 << '\n';
      std::cout << "Force unicode German text: Gr\xC3\xBC\xC3\x9F Gott\n";

      // Shows the window and returns when it is closed.
      return app->make_window_and_run<ExampleWindow>(argc, argv);
    }

Getting help with translations |  | Chapter 28. Custom Widgets