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