mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 22:31:14 +00:00
Change API to set the language with bundle translation
* Change API to set the language with bundle translation Part of #6793
This commit is contained in:
parent
ebd1c2eb09
commit
d68b84a3f1
9 changed files with 140 additions and 57 deletions
|
@ -1318,6 +1318,16 @@ inline void update_all_translations()
|
|||
}
|
||||
#endif
|
||||
|
||||
/// Select the current translation language when using bundled translations.
|
||||
/// This function requires that the application's `.slint` file was compiled with bundled
|
||||
/// translations. It must be called after creating the first component. Returns true if the language
|
||||
/// was selected; false if the language was not found in the list of bundled translations.
|
||||
inline bool select_bundled_translation(std::string_view locale)
|
||||
{
|
||||
return cbindgen_private::slint_translate_select_bundled_translation(
|
||||
slint::private_api::string_to_slice(locale));
|
||||
}
|
||||
|
||||
#if !defined(DOXYGEN)
|
||||
cbindgen_private::Flickable::Flickable()
|
||||
{
|
||||
|
|
|
@ -223,6 +223,7 @@ pub use i_slint_core::model::{
|
|||
};
|
||||
pub use i_slint_core::sharedvector::SharedVector;
|
||||
pub use i_slint_core::timers::{Timer, TimerMode};
|
||||
pub use i_slint_core::translations::{select_bundled_translation, SelectBundledTranslationError};
|
||||
pub use i_slint_core::{format, string::SharedString};
|
||||
|
||||
pub mod private_unstable_api;
|
||||
|
|
|
@ -214,7 +214,7 @@ pub mod re_exports {
|
|||
pub use i_slint_core::slice::Slice;
|
||||
pub use i_slint_core::timers::{Timer, TimerMode};
|
||||
pub use i_slint_core::translations::{
|
||||
set_language_internal, translate_from_bundle, translate_from_bundle_with_plural,
|
||||
set_bundled_languages, translate_from_bundle, translate_from_bundle_with_plural,
|
||||
};
|
||||
pub use i_slint_core::window::{
|
||||
InputMethodRequest, WindowAdapter, WindowAdapterRc, WindowInner,
|
||||
|
|
|
@ -7,6 +7,8 @@ msgstr ""
|
|||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-10-25 03:10+0000\n"
|
||||
"PO-Revision-Date: 2023-06-09 07:56+0200\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: fr_FR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -112,17 +114,17 @@ msgstr "Brouillon"
|
|||
#: ui/settings_page.slint:72
|
||||
msgctxt "SettingsPage"
|
||||
msgid "Language"
|
||||
msgstr ""
|
||||
msgstr "Langue"
|
||||
|
||||
#: ui/settings_page.slint:74 ui/settings_page.slint:75
|
||||
msgctxt "SettingsPage"
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
msgstr "Anglais"
|
||||
|
||||
#: ui/settings_page.slint:75
|
||||
msgctxt "SettingsPage"
|
||||
msgid "French"
|
||||
msgstr ""
|
||||
msgstr "Français"
|
||||
|
||||
#: ui/settings_page.slint:80
|
||||
msgctxt "SettingsPage"
|
||||
|
@ -167,7 +169,7 @@ msgstr "ATTENTE"
|
|||
#: ui/printer_queue.slint:29
|
||||
msgctxt "PrinterQueue"
|
||||
msgid "Unknown job status"
|
||||
msgstr ""
|
||||
msgstr "Status inconnu"
|
||||
|
||||
#: ui/printer_queue.slint:60
|
||||
msgctxt "PrintDetails"
|
||||
|
|
|
@ -1708,6 +1708,20 @@ fn generate_item_tree(
|
|||
"self->self_weak = vtable::VWeak(self_rc).into_dyn();".into(),
|
||||
];
|
||||
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
if let Some(translations) = &root.translations {
|
||||
let lang_len = translations.languages.len();
|
||||
create_code.push(format!(
|
||||
"std::array<slint::cbindgen_private::Slice<uint8_t>, {lang_len}> languages {{ {} }};",
|
||||
translations
|
||||
.languages
|
||||
.iter()
|
||||
.map(|l| format!("slint::private_api::string_to_slice({l:?})"))
|
||||
.join(", ")
|
||||
));
|
||||
create_code.push(format!("slint::cbindgen_private::slint_translate_set_bundled_languages({{ languages.data(), {lang_len} }});"));
|
||||
}
|
||||
|
||||
if parent_ctx.is_none() {
|
||||
create_code.push("self->globals = &self->m_globals;".into());
|
||||
create_code.push("self->m_globals.root_weak = self->self_weak;".into());
|
||||
|
@ -3922,17 +3936,6 @@ fn generate_translation(
|
|||
..Default::default()
|
||||
}));
|
||||
}
|
||||
let lang_len = translations.languages.len();
|
||||
declarations.push(Declaration::Function(Function {
|
||||
name: "slint_set_language".into(),
|
||||
signature: "(std::string_view lang)".into(),
|
||||
is_inline: true,
|
||||
statements: Some(vec![
|
||||
format!("std::array<slint::cbindgen_private::Slice<uint8_t>, {lang_len}> languages {{ {} }};", translations.languages.iter().map(|l| format!("slint::private_api::string_to_slice({l:?})")).join(", ")),
|
||||
format!("return slint::cbindgen_private::slint_translate_set_language(slint::private_api::string_to_slice(lang), {{ languages.data(), {lang_len} }});"
|
||||
)]),
|
||||
..Default::default()
|
||||
}));
|
||||
|
||||
let ctx = EvaluationContext {
|
||||
compilation_unit,
|
||||
|
|
|
@ -211,13 +211,9 @@ pub fn generate(doc: &Document, compiler_config: &CompilerConfiguration) -> Toke
|
|||
.unwrap_or_else(|| format_ident!("slint_generated"));
|
||||
|
||||
#[cfg(not(feature = "bundle-translations"))]
|
||||
let (translations, exported_tr) = (quote!(), quote!());
|
||||
let translations = quote!();
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
let (translations, exported_tr) = llr
|
||||
.translations
|
||||
.as_ref()
|
||||
.map(|t| (generate_translations(t, &llr), quote!(slint_set_language,)))
|
||||
.unzip();
|
||||
let translations = llr.translations.as_ref().map(|t| (generate_translations(t, &llr)));
|
||||
|
||||
quote! {
|
||||
#[allow(non_snake_case, non_camel_case_types)]
|
||||
|
@ -237,7 +233,7 @@ pub fn generate(doc: &Document, compiler_config: &CompilerConfiguration) -> Toke
|
|||
const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : slint::#version_check = slint::#version_check;
|
||||
}
|
||||
#[allow(unused_imports)]
|
||||
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)* #exported_tr};
|
||||
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)*};
|
||||
#[allow(unused_imports)]
|
||||
pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
|
||||
}
|
||||
|
@ -270,6 +266,14 @@ fn generate_public_component(
|
|||
&ctx,
|
||||
);
|
||||
|
||||
#[cfg(feature = "bundle-translations")]
|
||||
let init_bundle_translations = unit
|
||||
.translations
|
||||
.as_ref()
|
||||
.map(|_| quote!(sp::set_bundled_languages(_SLINT_BUNDLED_LANGUAGES);));
|
||||
#[cfg(not(feature = "bundle-translations"))]
|
||||
let init_bundle_translations = quote!();
|
||||
|
||||
quote!(
|
||||
#component
|
||||
pub struct #public_component_id(sp::VRc<sp::ItemTreeVTable, #inner_component_id>);
|
||||
|
@ -277,6 +281,7 @@ fn generate_public_component(
|
|||
impl #public_component_id {
|
||||
pub fn new() -> core::result::Result<Self, slint::PlatformError> {
|
||||
let inner = #inner_component_id::new()?;
|
||||
#init_bundle_translations
|
||||
inner.globals.get().unwrap().init();
|
||||
#inner_component_id::user_init(sp::VRc::map(inner.clone(), |x| x));
|
||||
core::result::Result::Ok(Self(inner))
|
||||
|
@ -3249,8 +3254,6 @@ fn generate_translations(
|
|||
const _SLINT_TRANSLATED_STRINGS_PLURALS: &[&[sp::Option<&[&str]>]] = &[#(#plurals),*];
|
||||
#[allow(unused)]
|
||||
const _SLINT_TRANSLATED_PLURAL_RULES: &[sp::Option<fn(i32) -> usize>] = &[#(#rules),*];
|
||||
pub fn slint_set_language(lang: &str) -> bool {
|
||||
sp::set_language_internal(lang, &[#(#lang),*])
|
||||
}
|
||||
const _SLINT_BUNDLED_LANGUAGES: &[&str] = &[#(#lang),*];
|
||||
)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ pub(crate) struct SlintContextInner {
|
|||
/// so that every translated string gets re-translated. The property's value is the current selected
|
||||
/// language when bundling translations.
|
||||
pub(crate) translations_dirty: core::pin::Pin<Box<Property<usize>>>,
|
||||
pub(crate) translations_bundle_languages:
|
||||
core::cell::RefCell<Option<alloc::vec::Vec<&'static str>>>,
|
||||
pub(crate) window_shown_hook:
|
||||
core::cell::RefCell<Option<Box<dyn FnMut(&Rc<dyn crate::platform::WindowAdapter>)>>>,
|
||||
}
|
||||
|
@ -39,6 +41,7 @@ impl SlintContext {
|
|||
platform,
|
||||
window_count: 0.into(),
|
||||
translations_dirty: Box::pin(Property::new_named(0, "SlintContext::translations")),
|
||||
translations_bundle_languages: Default::default(),
|
||||
window_shown_hook: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -305,25 +305,70 @@ pub fn translate_from_bundle_with_plural(
|
|||
output
|
||||
}
|
||||
|
||||
pub fn set_language_internal(language: &str, languages: &[&str]) -> bool {
|
||||
let idx = languages.iter().position(|x| *x == language);
|
||||
if let Some(idx) = idx {
|
||||
crate::context::GLOBAL_CONTEXT.with(|ctx| {
|
||||
let Some(ctx) = ctx.get() else { return false };
|
||||
/// This function is called by the generated code to assign the list of bundled languages.
|
||||
/// Do nothing if the list is already assigned.
|
||||
pub fn set_bundled_languages(languages: &[&'static str]) {
|
||||
crate::context::GLOBAL_CONTEXT.with(|ctx| {
|
||||
let Some(ctx) = ctx.get() else { return };
|
||||
if ctx.0.translations_bundle_languages.borrow().is_none() {
|
||||
ctx.0.translations_bundle_languages.replace(Some(languages.to_vec()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Select the current translation language when using bundled translations.
|
||||
/// This function requires that the application's `.slint` file was compiled with bundled translations..
|
||||
/// It must be called after creating the first component.
|
||||
/// Returns `Ok` if the language was selected; [`SelectBundledTranslationError`] otherwise.
|
||||
pub fn select_bundled_translation(locale: &str) -> Result<(), SelectBundledTranslationError> {
|
||||
crate::context::GLOBAL_CONTEXT.with(|ctx| {
|
||||
let Some(ctx) = ctx.get() else {
|
||||
return Err(SelectBundledTranslationError::NoLanguageBundled);
|
||||
};
|
||||
let languages = ctx.0.translations_bundle_languages.borrow();
|
||||
let Some(languages) = &*languages else {
|
||||
return Err(SelectBundledTranslationError::NoLanguageBundled);
|
||||
};
|
||||
let idx = languages.iter().position(|x| *x == locale);
|
||||
if let Some(idx) = idx {
|
||||
ctx.0.translations_dirty.as_ref().set(idx);
|
||||
true
|
||||
})
|
||||
} else if language == "en" {
|
||||
crate::context::GLOBAL_CONTEXT.with(|ctx| {
|
||||
let Some(ctx) = ctx.get() else { return false };
|
||||
Ok(())
|
||||
} else if locale == "" || locale == "en" {
|
||||
ctx.0.translations_dirty.as_ref().set(0);
|
||||
true
|
||||
})
|
||||
} else {
|
||||
false
|
||||
Ok(())
|
||||
} else {
|
||||
Err(SelectBundledTranslationError::LanguageNotFound {
|
||||
available_languages: languages.iter().map(|x| (*x).into()).collect(),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Error type returned from the [`select_bundled_translation`] function.
|
||||
#[derive(Debug)]
|
||||
pub enum SelectBundledTranslationError {
|
||||
/// The language was not found. The list of available languages is included in this error variant.
|
||||
LanguageNotFound { available_languages: crate::SharedVector<SharedString> },
|
||||
/// There are no bundled languages. Either [`select_bundled_translation`] was called before creating a component, or the application's `.slint` file was compiled without the bundle translation option.
|
||||
NoLanguageBundled,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for SelectBundledTranslationError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
SelectBundledTranslationError::LanguageNotFound { available_languages } => {
|
||||
write!(f, "The specified language was not found. Available languages are: {available_languages:?}")
|
||||
}
|
||||
SelectBundledTranslationError::NoLanguageBundled => {
|
||||
write!(f, "There are no bundled languages. Either select_bundled_translation was called before creating a component, or the application's `.slint` file was compiled without the bundle translation option")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::error::Error for SelectBundledTranslationError {}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
mod ffi {
|
||||
#![allow(unsafe_code)]
|
||||
|
@ -412,14 +457,17 @@ mod ffi {
|
|||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn slint_translate_set_language(
|
||||
lang: Slice<u8>,
|
||||
languages: Slice<Slice<u8>>,
|
||||
) -> bool {
|
||||
pub extern "C" fn slint_translate_set_bundled_languages(languages: Slice<Slice<'static, u8>>) {
|
||||
let languages = languages
|
||||
.iter()
|
||||
.map(|x| core::str::from_utf8(x).unwrap())
|
||||
.map(|x| core::str::from_utf8(x.as_slice()).unwrap())
|
||||
.collect::<alloc::vec::Vec<_>>();
|
||||
set_language_internal(core::str::from_utf8(&lang).unwrap(), &languages)
|
||||
set_bundled_languages(&languages);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn slint_translate_select_bundled_translation(locale: Slice<u8>) -> bool {
|
||||
let locale = core::str::from_utf8(&locale).unwrap();
|
||||
return select_bundled_translation(locale).is_ok();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
out property <string> t2: @tr("Hello {}.", "World");
|
||||
out property <string> t3: @tr("{} Hello {}", int_value, "World");
|
||||
out property <string> t4: @tr("{1} Hello {0}🌍", @tr("World"), int_value + 1);
|
||||
out property <string> t5: @tr("Untranslated string");
|
||||
|
||||
property <string> c1: @tr("Context" => "xx{0}xx", @tr("CC" => "aa"));
|
||||
|
||||
|
@ -20,7 +21,7 @@
|
|||
out property <string> result1: make_plural1(1, @tr("Plop")) + "\n" + make_plural1(2, @tr("Flop🎃")) + "\n" + make_plural1(10, t1);
|
||||
out property <string> result2: make_plural2(1) + "\n" + make_plural2(-999) + "\n" + make_plural2(0) + "\n" + make_plural2(42);
|
||||
|
||||
out property <bool> test: t1 == "Hello World{}." && t2 == "Hello World." && t3 == "42 Hello World" && t4 == "43 Hello World🌍"
|
||||
out property <bool> test: t1 == "Hello World{}." && t2 == "Hello World." && t3 == "42 Hello World" && t4 == "43 Hello World🌍" && t5 == "Untranslated string"
|
||||
&& c1 == "xxaaxx"
|
||||
&& result1 == "there is one file in my Plop\nthere are 2 files in my Flop🎃\nthere are 10 files in my Hello World{}."
|
||||
&& result2 == "xx1xx\nyy-999yy\nyy0yy\nyy42yy";
|
||||
|
@ -33,35 +34,41 @@ auto result1 = "there is one file in my Plop\nthere are 2 files in my Flop🎃\n
|
|||
auto result2 = "xx1xx\nyy-999yy\nyy0yy\nyy42yy";
|
||||
assert_eq(instance.get_result1(), result1);
|
||||
assert_eq(instance.get_result2(), result2);
|
||||
assert_eq(instance.get_t5(), "Untranslated string");
|
||||
assert(instance.get_test());
|
||||
|
||||
assert(!slint_set_language("abc"));
|
||||
assert(!slint::select_bundled_translation("abc"));
|
||||
assert_eq(instance.get_result1(), result1);
|
||||
assert_eq(instance.get_result2(), result2);
|
||||
assert_eq(instance.get_t5(), "Untranslated string");
|
||||
assert(instance.get_test());
|
||||
|
||||
assert(slint_set_language("up"));
|
||||
assert(slint::select_bundled_translation("up"));
|
||||
std::string result1_upper = result1;
|
||||
std::transform(result1_upper.begin(), result1_upper.end(), result1_upper.begin(), ::toupper);
|
||||
std::string result2_upper = result2;
|
||||
std::transform(result2_upper.begin(), result2_upper.end(), result2_upper.begin(), ::toupper);
|
||||
assert_eq(std::string_view(instance.get_result1()), result1_upper);
|
||||
assert_eq(std::string_view(instance.get_result2()), result2_upper);
|
||||
assert_eq(instance.get_t5(), "Untranslated string");
|
||||
assert(!instance.get_test());
|
||||
|
||||
assert(!slint_set_language("def"));
|
||||
assert(!slint::select_bundled_translation("def"));
|
||||
assert_eq(std::string_view(instance.get_result1()), result1_upper);
|
||||
assert_eq(std::string_view(instance.get_result2()), result2_upper);
|
||||
assert_eq(instance.get_t5(), "Untranslated string");
|
||||
assert(!instance.get_test());
|
||||
|
||||
assert(slint_set_language(""));
|
||||
assert(slint::select_bundled_translation(""));
|
||||
assert_eq(instance.get_result1(), result1);
|
||||
assert_eq(instance.get_result2(), result2);
|
||||
assert_eq(instance.get_t5(), "Untranslated string");
|
||||
assert(instance.get_test());
|
||||
|
||||
assert(slint_set_language("fr"));
|
||||
assert(slint::select_bundled_translation("fr"));
|
||||
assert_eq(instance.get_result1(), "Il y a 1 fichier dans mon Plouf\nIl y a 2 fichiers dans mon Floup🍓\nIl y a 10 fichiers dans mon Bonjour Monde{}.");
|
||||
assert_eq(instance.get_result2(), "rr1rr\nrr-999rr\nrr0rr\nss42ss");
|
||||
assert_eq(instance.get_t5(), "Untranslated string");
|
||||
assert(!instance.get_test());
|
||||
```
|
||||
|
||||
|
@ -72,31 +79,37 @@ let result1 = "there is one file in my Plop\nthere are 2 files in my Flop🎃\nt
|
|||
let result2 = "xx1xx\nyy-999yy\nyy0yy\nyy42yy";
|
||||
assert_eq!(instance.get_result1(), result1);
|
||||
assert_eq!(instance.get_result2(), result2);
|
||||
assert_eq!(instance.get_t5(), "Untranslated string");
|
||||
assert!(instance.get_test());
|
||||
|
||||
assert!(!slint_set_language("abc"));
|
||||
assert!(slint::select_bundled_translation("abc").is_err());
|
||||
assert_eq!(instance.get_result1(), result1);
|
||||
assert_eq!(instance.get_result2(), result2);
|
||||
assert_eq!(instance.get_t5(), "Untranslated string");
|
||||
assert!(instance.get_test());
|
||||
|
||||
assert!(slint_set_language("up"));
|
||||
assert!(slint::select_bundled_translation("up").is_ok());
|
||||
assert_eq!(instance.get_result1(), result1.to_uppercase());
|
||||
assert_eq!(instance.get_result2(), result2.to_uppercase());
|
||||
assert_eq!(instance.get_t5(), "Untranslated string");
|
||||
assert!(!instance.get_test());
|
||||
|
||||
assert!(!slint_set_language("def"));
|
||||
assert!(slint::select_bundled_translation("def").is_err());
|
||||
assert_eq!(instance.get_result1(), result1.to_uppercase());
|
||||
assert_eq!(instance.get_result2(), result2.to_uppercase());
|
||||
assert_eq!(instance.get_t5(), "Untranslated string");
|
||||
assert!(!instance.get_test());
|
||||
|
||||
assert!(slint_set_language(""));
|
||||
assert!(slint::select_bundled_translation("").is_ok());
|
||||
assert_eq!(instance.get_result1(), result1);
|
||||
assert_eq!(instance.get_result2(), result2);
|
||||
assert_eq!(instance.get_t5(), "Untranslated string");
|
||||
assert!(instance.get_test());
|
||||
|
||||
assert!(slint_set_language("fr"));
|
||||
assert!(slint::select_bundled_translation("fr").is_ok());
|
||||
assert_eq!(instance.get_result1(), "Il y a 1 fichier dans mon Plouf\nIl y a 2 fichiers dans mon Floup🍓\nIl y a 10 fichiers dans mon Bonjour Monde{}.");
|
||||
assert_eq!(instance.get_result2(), "rr1rr\nrr-999rr\nrr0rr\nss42ss");
|
||||
assert_eq!(instance.get_t5(), "Untranslated string");
|
||||
assert!(!instance.get_test());
|
||||
|
||||
```
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue