Exist some way how I can change my gtk /gtkmm application language (locales) during program execution? Thanks
No.
GLib/GTK and every other library in the GNOME stack are based on gettext and the POSIX localisation API, which are set up at execution time through environment variables. Environment variables are only read at the start of a process, and changing them at run time is unsafe. Additionally, there’s no mechanism to know if the locale is changed, which means all textual UI elements created before the change in locale are not going to change, which would lead to a UI that has mixed translations.
I’m not really disagreeing with Emmanuele – it’s somewhat impractical in a non-trivial application. But GNOME Initial Setup supports this, with some extra gymnastics:
- You must use uselocale(), which is thread-safe but only affects the current thread; so you then need to ensure that translations are only fetched on the main thread (or arrange for it to be called on every thread in your program I guess) and that you manually adjust the environment for any other programs that you spawn.
- You need to update all textual UI elements when the locale changes. Initial Setup handles this by destroying and recreating most of its UI, and manually updating all the labels in the language-selection page.
Personally I would try to avoid having to do this. If you really must support allowing the user to override the system language in your application, perhaps you could prompt the user to restart the application when the language is changed. In a non-trivial application, it’s probably less work (and more generally useful) to save and restore the application state across a restart than it is to support dynamically changing locale.
Ok thanks for reply, in practical i can imagine some bash script with line:
LANG=de_DE.UTF-8 ./app
And rewrite this script when change language
Or is some way to set this variable on begin app ?
If I know this right, it should be possible to set a set locale by passing it to the setlocale
method as the locale argument?
The setlocale
method should already be the first thing your main
method calls. So you could read a preference before you call it, and it would be applied to all code called afterwards, like your GTK UI.
That being said:
Is there a good reason the program needs its own language preference apart from the rest of the system? A few exceptions aside the default “use system language” preference is the desired one anyway…
Ok thanks, I have done it:
#include <clocale>
#include <iostream>
#include <glibmm/i18n.h>
#include <gtkmm.h>
#include <iostream>
#include <fstream>
#include <string>
extern "C" __typeof(uselocale) __uselocale;
typedef __locale_t __c_locale;
__c_locale __cloc;
class ExampleWindow : public Gtk::Window
{
public:
ExampleWindow();
~ExampleWindow() override;
protected:
void fill_text_tag_table();
void fill_buffer();
void on_combo_changed();
//Child widgets:
Gtk::ComboBoxText m_Combo;
Gtk::Box m_box1;
// Child widgets:
Gtk::ScrolledWindow m_ScrolledWindow;
Gtk::TextView m_TextView;
Glib::RefPtr<Gtk::TextTagTable> m_refTextTagTable;
Glib::RefPtr<Gtk::TextBuffer> m_refTextBuffer;
};
ExampleWindow::ExampleWindow(): m_box1(Gtk::Orientation::VERTICAL)
{
set_title(_("English i18n example"));
set_default_size(500, 300);
m_Combo.append("en_US.UTF-8");
m_Combo.append("de_DE.UTF-8");
m_Combo.append("cs_CZ.UTF-8");
m_Combo.set_active(1);
m_Combo.signal_changed().connect(sigc::mem_fun(*this,
&ExampleWindow::on_combo_changed) );
// Add the TextView, inside a ScrolledWindow.
m_ScrolledWindow.set_child(m_TextView);
m_box1.set_homogeneous(true);
m_box1.append(m_Combo);
m_box1.append(m_ScrolledWindow);
set_child(m_box1);
fill_text_tag_table();
fill_buffer();
m_TextView.set_buffer(m_refTextBuffer);
}
ExampleWindow::~ExampleWindow()
{
}
void ExampleWindow::on_combo_changed()
{
Glib::ustring text = m_Combo.get_active_text();
if(!(text.empty()))
std::cout << "Combo changed: " << text << std::endl;
std::ofstream myfile;
myfile.open ("appconfig.cfg");
myfile << text << std::endl;
myfile.close();
}
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. 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");
}
const char* GETTEXT_PACKAGE = "myapp";
const char* PROGRAMNAME_LOCALEDIR = "locale";
int main(int argc, char* argv[])
{
std::string line;
std::ifstream myfilei("appconfig.cfg");
if (myfilei.is_open())
{ /* ok, proceed with output */
std::getline (myfilei,line);
std::cout <<"Prefernce settings:"<< line << std::endl; }
myfilei.close();
__cloc = newlocale(LC_ALL_MASK, line.c_str(), 0);
__c_locale __old = __gnu_cxx::__uselocale(__cloc);
bindtextdomain(GETTEXT_PACKAGE, PROGRAMNAME_LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
textdomain(GETTEXT_PACKAGE);
auto app = Gtk::Application::create("org.gtkmm.example");
std::cout << Glib::ustring(_("English App is Running\n"));
std::cout << Glib::ustring("Force unicode German text: Gr\xC3\xBC\xC3\x9F Gott\n");
return app->make_window_and_run<ExampleWindow>(argc, argv);
}
This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.