Strange behavoir with GTK3 (msys64) on Windows
gettext works very strange if gtk-rs is used in application. I reproduce it with simple code
use {gtk::*, gtk::prelude::*};
use gettextrs::*;
fn main() {
TextDomain::new("electra_stacking_gui")
.prepend(r"D:\rust\electra_stacking\target\debug\share")
.locale("en_US.UTF-8")
.init()
.unwrap();
let text = gettext("test");
println!("text = {}", text);
// uncomment it and code above will show wrong result
// (will print `test` instead of translated string)
/* let application = Application::new(
Some("com.github.testtest"),
Default::default(),
);
application.run(); */
}
[dependencies]
gtk = "0.15"
gettext-rs = { version = "0.7", features = ["gettext-system"] }
Why does gtk-rs affect gettext behavior?
I suspect that Application::new calls settextdomain, overriding the results of TextDomain::new. You could swap the two calls to fix this, or find out if GTK lets you configure the domain name somehow so you can do away with TextDomain.
But TextDomain::init and gettext and are called before Application::new in my example
Too much magic here ))
I redid example like
TextDomain::new("electra_stacking_gui")
.prepend(r"D:\test_mo\share")
.locale_category(LocaleCategory::LcAll)
.skip_system_data_paths()
.locale("en_US.UTF-8")
.init()
.unwrap();
let text = gettext("test");
println!("text = {}", text);
... GTK code here
and started the process monitor to see which .mo files are accessed by my application. The magic is sometimes I see right translated string. I.e. every time I ran my application sometimes I see the correct result and sometimes the wrong result O_o But if to stop process monitor I saw only wrong results O_o
When result is correct accessed .mo-files are (from process monitor):
D:\test_mo\share\locale\en\LC_MESSAGES\electra_stacking_gui.mo SUCCESS
D:\test_mo\share\locale\ru_RU\LC_MESSAGES\electra_stacking_gui.mo PATH NOT FOUND
D:\test_mo\share\locale\ru\LC_MESSAGES\electra_stacking_gui.mo SUCCESS
and when wrong:
D:\mingw64\share\locale\ru_RU\LC_MESSAGES\messages.mo PATH NOT FOUND
D:\mingw64\share\locale\ru\LC_MESSAGES\messages.mo PATH NOT FOUND
E:\Development\vscode\tools\msys64\mingw64\share\locale\ru_RU\LC_MESSAGES\glib20.mo PATH NOT FOUND
E:\Development\vscode\tools\msys64\mingw64\share\locale\ru\LC_MESSAGES\glib20.mo PATH NOT FOUND
D:\test_mo\share\locale\en\LC_MESSAGES\electra_stacking_gui.mo SUCCESS
Mo-files
D:\test_mo\share\locale\en\LC_MESSAGES\electra_stacking_gui.mo
D:\test_mo\share\locale\ru\LC_MESSAGES\electra_stacking_gui.mo
are equal and compiled from po-source
msgid "test"
msgstr "test test"
So It looks like UB in msys gettext or in gettext-rs
I write simple C test
#include <gtk/gtk.h>
#include <stdio.h>
#include <locale.h>
#include <libintl.h>
int main(int argc, char *argv[])
{
setlocale(LC_ALL, "en_EN.UTF-8");
bindtextdomain ("electra_stacking_gui", "D:\\test_mo\\share\\locale");
bind_textdomain_codeset("electra_stacking_gui", "UTF-8");
textdomain("electra_stacking_gui");
printf("text = %s\n", gettext("test"));
GtkApplication *app = gtk_application_new(
"org.gtk.example.HelloApp",
G_APPLICATION_FLAGS_NONE
);
int status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}
This example is ok
More investigation. Working examples: (1)
fn main() -> anyhow::Result<()> {
setlocale(LocaleCategory::LcAll, "");
bindtextdomain("electra_stacking_gui", r"D:\test_mo\share\locale")?;
textdomain("electra_stacking_gui")?;
let text = gettext("test");
println!("before Application::new = {}", text);
let application = Application::new(Some("com.github.testtest"), Default::default(),);
application.run();
Ok(())
}
(2)
fn main() -> anyhow::Result<()> {
setlocale(LocaleCategory::LcAll, "");
bindtextdomain("electra_stacking_gui", r"D:\test_mo\share\locale")?;
textdomain("electra_stacking_gui")?;
// Sleep added after `gettext` configuration is Ok
std::thread::sleep(std::time::Duration::from_millis(100));
let text = gettext("test");
println!("before Application::new = {}", text);
let application = Application::new(Some("com.github.testtest"), Default::default());
application.run();
Ok(())
}
Not working examples: (1)
fn main() -> anyhow::Result<()> {
// OMG! This sleep breaks gettext!!!
std::thread::sleep(std::time::Duration::from_millis(100));
setlocale(LocaleCategory::LcAll, "");
bindtextdomain("electra_stacking_gui", r"D:\test_mo\share\locale")?;
textdomain("electra_stacking_gui")?;
let text = gettext("test");
println!("before Application::new = {}", text);
let application = Application::new(Some("com.github.testtest"), Default::default());
application.run();
Ok(())
}
(2)
fn main() -> anyhow::Result<()> {
let application = Application::new(Some("com.github.testtest"), Default::default());
// Placing gettext configuration after `Application::new`
// brakes gettext
setlocale(LocaleCategory::LcAll, "");
bindtextdomain("electra_stacking_gui", r"D:\test_mo\share\locale")?;
textdomain("electra_stacking_gui")?;
let text = gettext("test");
println!("before Application::new = {}", text);
application.run();
Ok(())
}
@Minoru, any ideas?
FWIW, all the provided examples work for me on Linux. (This one contains a typo, should say "en_US.UTF-8" rather than "en_EN.UTF-8".) I don't have a Windows machine to test this, nor do I really have interest in digging deeper into that OS.
The fact that it looks for glib20.mo, and also that it works occasionally or doesn't work with a delay, gives me an idea that it's a race when loading DLLs: intl.dll from miingw for gettext, and glib20.dll for GTK (the DLL names are guesses).
How do you compile and link your C example? It's surprising that it works fine. I bet you're linking libintl statically! :)
Hi, I changed en_EN.UTF-8 -> en_US.UTF-8 in my C example and still no problem there. Also I added sleep(1); before gettext configuration, and no change.
I build C example with script
md bin
gcc -g0 -O3 -s -o bin/hello_world.exe ^
main.c ^
%msys%\mingw64\lib\libgtk-3.dll.a ^
%msys%\mingw64\lib\libgobject-2.0.dll.a ^
%msys%\mingw64\lib\libgio-2.0.dll.a ^
%msys%\mingw64\lib\libglib-2.0.dll.a ^
%msys%\mingw64\lib\libintl.dll.a
Look at last library. It called libintl.dll.a and it is for dynamic linking. I check idata section of example binary and it contains import of libintl-8.dll and list of functions:
libintl_bind_textdomain_codeset
libintl_bindtextdomain
libintl_gettext
libintl_setlocale
libintl_textdomain
I already translated my application and test it in different windows versions. Time by time I see untranslated interface if to start (and then close) application many times. So I need Ideas where to look to sort out the situation
Got it. Since the C program doesn't misbehave, I'm convinced that gettext-sys or gettext-rs is at fault here, but I don't see how. I'd try a debugger to see what exactly gets called when you invoke gettext; I still suspect that in some cases it calls libintl while in other it calls libglib. Sorry I don't have any better ideas.
Hm... If to set breakpoint at line with bindtextdomain in rust example sometimes I see stacktrace of one parallel thread
RtlCompareMemoryUlong (@RtlCompareMemoryUlong:9)
RtlGetCurrentServiceSessionId (@RtlGetCurrentServiceSessionId:1905)
RtlGetCurrentServiceSessionId (@RtlGetCurrentServiceSessionId:1286)
RtlFreeHeap (@RtlFreeHeap:24)
RtlRegisterSecureMemoryCacheCallback (@RtlRegisterSecureMemoryCacheCallback:1769)
RtlGetCurrentServiceSessionId (@RtlGetCurrentServiceSessionId:1380)
RtlGetCurrentServiceSessionId (@RtlGetCurrentServiceSessionId:1286)
RtlFreeHeap (@RtlFreeHeap:24)
free (@free:11)
g_getenv (@g_getenv:130)
g_win32_getlocale (@g_win32_getlocale:16)
g_get_current_dir_utf8 (@g_get_current_dir_utf8:45)
glib_gettext (@glib_gettext:8)
g_tcp_wrapper_connection_get_base_io_stream (@g_tcp_wrapper_connection_get_base_io_stream:118)
g_type_class_ref (@g_type_class_ref:308)
g_object_new_valist (@g_object_new_valist:307)
g_object_new (@g_object_new:11)
g_themed_icon_new (@g_themed_icon_new:17)
g_list_store_find (@g_list_store_find:4718)
g_list_store_find (@g_list_store_find:4793)
g_list_store_find (@g_list_store_find:6057)
g_list_store_find (@g_list_store_find:6512)
g_get_num_processors (@g_get_num_processors:207)
g_test_get_path (@g_test_get_path:30)
g_private_replace (@g_private_replace:79)
_beginthreadex (@_beginthreadex:85)
_endthreadex (@_endthreadex:53)
BaseThreadInitThunk (@BaseThreadInitThunk:9)
RtlUserThreadStart (@RtlUserThreadStart:12)
I'm not sure it fully correct but glib_gettext item can give you some ideas?
Sorry I'm so slow to respond! I'm kind of swamped lately.
That stacktrace is in line with my hypothesis about a race between DLLs. I have no idea how to debug it further though ._.
No problem. I'll look into this problem in more depth after I've finished some ideas for my application