Add a function to mark translated strings as dirty

This commit is contained in:
Olivier Goffart 2024-04-18 14:27:48 +02:00
parent 2c012aa2d9
commit 808b1ef946
13 changed files with 112 additions and 14 deletions

View file

@ -933,7 +933,7 @@ macro_rules! declare_features {
};
}
declare_features! {interpreter backend_qt freestanding renderer_software renderer_skia experimental}
declare_features! {interpreter backend_qt freestanding renderer_software renderer_skia experimental gettext}
/// Generate the headers.
/// `root_dir` is the root directory of the slint git repo

View file

@ -1258,6 +1258,27 @@ inline SharedString translate(const SharedString &original, const SharedString &
} // namespace private_api
#ifdef SLINT_FEATURE_GETTEXT
/// Forces all the strings that are translated with `@tr(...)` to be re-evaluated.
/// This is useful if the language is changed at runtime.
/// The function is only available when Slint is compiled with `SLINT_FEATURE_GETTEXT`.
///
/// Example
/// ```cpp
/// my_ui->global<LanguageSettings>().on_french_selected([] {
/// // trick from https://www.gnu.org/software/gettext/manual/html_node/gettext-grok.html
/// setenv("LANGUAGE", langs[l], true);
/// extern int _nl_msg_cat_cntr;
/// ++_nl_msg_cat_cntr;
/// slint::update_all_translations();
/// });
/// ```
inline void update_all_translations()
{
cbindgen_private::slint_translations_mark_dirty();
}
#endif
#if !defined(DOXYGEN)
cbindgen_private::Flickable::Flickable()
{

View file

@ -11,3 +11,9 @@ endif()
add_executable(printerdemo main.cpp)
target_link_libraries(printerdemo PRIVATE Slint::Slint)
slint_target_sources(printerdemo ../ui/printerdemo.slint)
find_package(Intl)
if(Intl_FOUND)
target_compile_definitions(printerdemo PRIVATE HAVE_GETTEXT SRC_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
target_link_libraries(printerdemo PRIVATE Intl::Intl)
endif()

View file

@ -2,8 +2,14 @@
// SPDX-License-Identifier: MIT
#include "printerdemo.h"
#include "slint.h"
#include <cstdlib>
#include <ctime>
#ifdef HAVE_GETTEXT
# include <locale>
# include <libintl.h>
#endif
struct InkLevelModel : slint::Model<InkLevel>
{
@ -23,6 +29,11 @@ struct InkLevelModel : slint::Model<InkLevel>
int main()
{
#ifdef HAVE_GETTEXT
bindtextdomain("printerdemo", SRC_DIR "/../lang");
std::locale::global(std::locale(""));
#endif
auto printer_demo = MainWindow::create();
printer_demo->set_ink_levels(std::make_shared<InkLevelModel>());
printer_demo->on_quit([] { std::exit(0); });
@ -65,5 +76,16 @@ int main()
}
});
#if defined(HAVE_GETTEXT) && defined(SLINT_FEATURE_GETTEXT)
printer_demo->global<PrinterSettings>().on_change_language([](int l) {
static const char *langs[] = { "en", "fr" };
setenv("LANGUAGE", langs[l], true);
// trick from https://www.gnu.org/software/gettext/manual/html_node/gettext-grok.html
extern int _nl_msg_cat_cntr;
++_nl_msg_cat_cntr;
slint::update_all_translations();
});
#endif
printer_demo->run();
}

View file

@ -1,16 +1,13 @@
# Copyright © SixtyFPS GmbH <info@slint.dev>
# SPDX-License-Identifier: MIT
#
# Olivier Goffart <ogoffart@bepointbe.be>, 2023.
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-17 03:03+0000\n"
"PO-Revision-Date: 2023-06-09 07:56+0200\n"
"Last-Translator: Olivier Goffart <ogoffart@bepointbe.be>\n"
"Language-Team: German <kde-i18n-de@kde.org>\n"
"Language: de_DE\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

View file

@ -23,6 +23,11 @@ name = "printerdemo_lib"
slint = { path = "../../../api/rs/slint", features = ["backend-android-activity-05"] }
chrono = { version = "0.4", default-features = false, features = ["clock", "std"]}
# Disable gettext on macOS due to https://github.com/Koka/gettext-rs/issues/114
[target.'cfg(not(any(target_os = "macos", target_os = "android")))'.dependencies]
slint = { path = "../../../api/rs/slint", features=["gettext"] }
[build-dependencies]
slint-build = { path = "../../../api/rs/build" }

View file

@ -102,6 +102,21 @@ pub fn main() {
},
);
// Disable gettext on macOS due to https://github.com/Koka/gettext-rs/issues/114
#[cfg(not(any(target_os = "macos", target_os = "android")))]
{
slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/../lang/"));
main_window.global::<PrinterSettings>().on_change_language(|l| {
let lang = match l {
0 => "en",
1 => "fr",
_ => return,
};
std::env::set_var("LANGUAGE", lang);
slint::init_translations!(concat!(env!("CARGO_MANIFEST_DIR"), "/../lang/"));
})
}
main_window.run().unwrap();
}

View file

@ -170,6 +170,7 @@ export component SpinBox inherits Rectangle {
export component ComboBox inherits Rectangle {
in-out property <string> value;
in property <[string]> choices;
callback selected(int);
border-radius: 3px;
border-width: 2px;
@ -235,6 +236,7 @@ export component ComboBox inherits Rectangle {
item-area := TouchArea {
clicked => {
root.value = value;
root.selected(idx);
}
}
}

View file

@ -4,11 +4,11 @@
import { DemoPalette, Page } from "common.slint";
import { HomePage } from "./home_page.slint";
import { InkLevel, InkPage } from "./ink_page.slint";
import { SettingsPage } from "./settings_page.slint";
import { SettingsPage, PrinterSettings } from "./settings_page.slint";
import { PrinterQueue } from "./printer_queue.slint";
// re-export for the native code
export { PrinterQueue }
export { PrinterQueue, PrinterSettings }
import "./fonts/NotoSans-Regular.ttf";
import "./fonts/NotoSans-Bold.ttf";

View file

@ -3,6 +3,10 @@
import { DemoPalette, Page, SpinBox, ComboBox, CheckBox, Label } from "common.slint";
export global PrinterSettings {
callback change-language(int);
}
export component SettingsPage inherits Page {
header: @tr("Settings");
@ -65,10 +69,11 @@ export component SettingsPage inherits Page {
Rectangle {}
Label { text: @tr("Paper Type"); }
Label { text: @tr("Language"); }
ComboBox {
value: @tr("Standard");
choices: [@tr("Standard"), @tr("Non-standard")];
value: @tr("English");
choices: [@tr("English"), @tr("French")];
selected(x) => { PrinterSettings.change-language(x); }
}
}
Row {

View file

@ -5,6 +5,7 @@ use crate::api::PlatformError;
use crate::platform::{EventLoopProxy, Platform};
#[cfg(all(not(feature = "std"), feature = "unsafe-single-threaded"))]
use crate::thread_local;
use crate::Property;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
use alloc::rc::Rc;
@ -17,6 +18,9 @@ thread_local! {
pub(crate) struct SlintContextInner {
pub(crate) platform: Box<dyn Platform>,
pub(crate) window_count: core::cell::RefCell<isize>,
/// This property is read by all translations, and marked dirty when the language change
/// so that every translated string gets re-translated
pub(crate) translations_dirty: core::pin::Pin<Box<Property<()>>>,
}
/// This context is meant to hold the state and the backend.
@ -28,7 +32,11 @@ pub struct SlintContext(pub(crate) Rc<SlintContextInner>);
impl SlintContext {
/// Create a new context with a given platform
pub fn new(platform: Box<dyn Platform + 'static>) -> Self {
Self(Rc::new(SlintContextInner { platform, window_count: 0.into() }))
Self(Rc::new(SlintContextInner {
platform,
window_count: 0.into(),
translations_dirty: Box::pin(Property::new_named((), "SlintContext::translations")),
}))
}
/// Return an event proxy

View file

@ -192,6 +192,10 @@ pub fn translate(
#[cfg(all(target_family = "unix", feature = "gettext-rs"))]
fn translate_gettext(string: &str, ctx: &str, domain: &str, n: i32, plural: &str) -> String {
crate::context::GLOBAL_CONTEXT.with(|ctx| {
let Some(ctx) = ctx.get() else { return };
ctx.0.translations_dirty.as_ref().get();
});
fn mangle_context(ctx: &str, s: &str) -> String {
format!("{}\u{4}{}", ctx, s)
}
@ -220,6 +224,13 @@ fn translate_gettext(string: &str, ctx: &str, domain: &str, n: i32, plural: &str
}
}
pub fn mark_all_translations_dirty() {
crate::context::GLOBAL_CONTEXT.with(|ctx| {
let Some(ctx) = ctx.get() else { return };
ctx.0.translations_dirty.mark_dirty();
})
}
#[cfg(feature = "gettext-rs")]
/// Initialize the translation by calling the [`bindtextdomain`](https://man7.org/linux/man-pages/man3/bindtextdomain.3.html) function from gettext
pub fn gettext_bindtextdomain(_domain: &str, _dirname: std::path::PathBuf) -> std::io::Result<()> {
@ -230,6 +241,7 @@ pub fn gettext_bindtextdomain(_domain: &str, _dirname: std::path::PathBuf) -> st
START.call_once(|| {
gettextrs::setlocale(gettextrs::LocaleCategory::LcAll, "");
});
mark_all_translations_dirty();
}
Ok(())
}
@ -240,10 +252,8 @@ mod ffi {
use super::*;
use crate::slice::Slice;
/// Perform the translation and formatting.
#[no_mangle]
/// Returns a nul-terminated pointer for this string.
/// The returned value is owned by the string, and should not be used after any
/// mutable function have been called on the string, and must not be freed.
pub extern "C" fn slint_translate(
to_translate: &mut SharedString,
context: &SharedString,
@ -255,4 +265,10 @@ mod ffi {
*to_translate =
translate(to_translate.as_str(), &context, &domain, arguments.as_slice(), n, &plural)
}
/// Mark all translated string as dirty to perform re-translation in case the language change
#[no_mangle]
pub extern "C" fn slint_translations_mark_dirty() {
mark_all_translations_dirty();
}
}

View file

@ -90,6 +90,7 @@ pub fn generate(show_warnings: bool) -> Result<(), Box<dyn std::error::Error>> {
renderer_software: true,
renderer_skia: true,
experimental: false,
gettext: true,
};
cbindgen::gen_all(&root, &generated_headers_dir, enabled_features)?;