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

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);
}