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:
Olivier Goffart 2024-11-17 11:40:02 +01:00 committed by GitHub
parent ebd1c2eb09
commit d68b84a3f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 140 additions and 57 deletions

View file

@ -1318,6 +1318,16 @@ inline void update_all_translations()
} }
#endif #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) #if !defined(DOXYGEN)
cbindgen_private::Flickable::Flickable() cbindgen_private::Flickable::Flickable()
{ {

View file

@ -223,6 +223,7 @@ pub use i_slint_core::model::{
}; };
pub use i_slint_core::sharedvector::SharedVector; pub use i_slint_core::sharedvector::SharedVector;
pub use i_slint_core::timers::{Timer, TimerMode}; 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 use i_slint_core::{format, string::SharedString};
pub mod private_unstable_api; pub mod private_unstable_api;

View file

@ -214,7 +214,7 @@ pub mod re_exports {
pub use i_slint_core::slice::Slice; pub use i_slint_core::slice::Slice;
pub use i_slint_core::timers::{Timer, TimerMode}; pub use i_slint_core::timers::{Timer, TimerMode};
pub use i_slint_core::translations::{ 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::{ pub use i_slint_core::window::{
InputMethodRequest, WindowAdapter, WindowAdapterRc, WindowInner, InputMethodRequest, WindowAdapter, WindowAdapterRc, WindowInner,

View file

@ -7,6 +7,8 @@ msgstr ""
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-10-25 03:10+0000\n" "POT-Creation-Date: 2024-10-25 03:10+0000\n"
"PO-Revision-Date: 2023-06-09 07:56+0200\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" "Language: fr_FR\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -112,17 +114,17 @@ msgstr "Brouillon"
#: ui/settings_page.slint:72 #: ui/settings_page.slint:72
msgctxt "SettingsPage" msgctxt "SettingsPage"
msgid "Language" msgid "Language"
msgstr "" msgstr "Langue"
#: ui/settings_page.slint:74 ui/settings_page.slint:75 #: ui/settings_page.slint:74 ui/settings_page.slint:75
msgctxt "SettingsPage" msgctxt "SettingsPage"
msgid "English" msgid "English"
msgstr "" msgstr "Anglais"
#: ui/settings_page.slint:75 #: ui/settings_page.slint:75
msgctxt "SettingsPage" msgctxt "SettingsPage"
msgid "French" msgid "French"
msgstr "" msgstr "Français"
#: ui/settings_page.slint:80 #: ui/settings_page.slint:80
msgctxt "SettingsPage" msgctxt "SettingsPage"
@ -167,7 +169,7 @@ msgstr "ATTENTE"
#: ui/printer_queue.slint:29 #: ui/printer_queue.slint:29
msgctxt "PrinterQueue" msgctxt "PrinterQueue"
msgid "Unknown job status" msgid "Unknown job status"
msgstr "" msgstr "Status inconnu"
#: ui/printer_queue.slint:60 #: ui/printer_queue.slint:60
msgctxt "PrintDetails" msgctxt "PrintDetails"

View file

@ -1708,6 +1708,20 @@ fn generate_item_tree(
"self->self_weak = vtable::VWeak(self_rc).into_dyn();".into(), "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() { if parent_ctx.is_none() {
create_code.push("self->globals = &self->m_globals;".into()); create_code.push("self->globals = &self->m_globals;".into());
create_code.push("self->m_globals.root_weak = self->self_weak;".into()); create_code.push("self->m_globals.root_weak = self->self_weak;".into());
@ -3922,17 +3936,6 @@ fn generate_translation(
..Default::default() ..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 { let ctx = EvaluationContext {
compilation_unit, compilation_unit,

View file

@ -211,13 +211,9 @@ pub fn generate(doc: &Document, compiler_config: &CompilerConfiguration) -> Toke
.unwrap_or_else(|| format_ident!("slint_generated")); .unwrap_or_else(|| format_ident!("slint_generated"));
#[cfg(not(feature = "bundle-translations"))] #[cfg(not(feature = "bundle-translations"))]
let (translations, exported_tr) = (quote!(), quote!()); let translations = quote!();
#[cfg(feature = "bundle-translations")] #[cfg(feature = "bundle-translations")]
let (translations, exported_tr) = llr let translations = llr.translations.as_ref().map(|t| (generate_translations(t, &llr)));
.translations
.as_ref()
.map(|t| (generate_translations(t, &llr), quote!(slint_set_language,)))
.unzip();
quote! { quote! {
#[allow(non_snake_case, non_camel_case_types)] #[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; const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : slint::#version_check = slint::#version_check;
} }
#[allow(unused_imports)] #[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)] #[allow(unused_imports)]
pub use slint::{ComponentHandle as _, Global as _, ModelExt as _}; pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
} }
@ -270,6 +266,14 @@ fn generate_public_component(
&ctx, &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!( quote!(
#component #component
pub struct #public_component_id(sp::VRc<sp::ItemTreeVTable, #inner_component_id>); pub struct #public_component_id(sp::VRc<sp::ItemTreeVTable, #inner_component_id>);
@ -277,6 +281,7 @@ fn generate_public_component(
impl #public_component_id { impl #public_component_id {
pub fn new() -> core::result::Result<Self, slint::PlatformError> { pub fn new() -> core::result::Result<Self, slint::PlatformError> {
let inner = #inner_component_id::new()?; let inner = #inner_component_id::new()?;
#init_bundle_translations
inner.globals.get().unwrap().init(); inner.globals.get().unwrap().init();
#inner_component_id::user_init(sp::VRc::map(inner.clone(), |x| x)); #inner_component_id::user_init(sp::VRc::map(inner.clone(), |x| x));
core::result::Result::Ok(Self(inner)) core::result::Result::Ok(Self(inner))
@ -3249,8 +3254,6 @@ fn generate_translations(
const _SLINT_TRANSLATED_STRINGS_PLURALS: &[&[sp::Option<&[&str]>]] = &[#(#plurals),*]; const _SLINT_TRANSLATED_STRINGS_PLURALS: &[&[sp::Option<&[&str]>]] = &[#(#plurals),*];
#[allow(unused)] #[allow(unused)]
const _SLINT_TRANSLATED_PLURAL_RULES: &[sp::Option<fn(i32) -> usize>] = &[#(#rules),*]; const _SLINT_TRANSLATED_PLURAL_RULES: &[sp::Option<fn(i32) -> usize>] = &[#(#rules),*];
pub fn slint_set_language(lang: &str) -> bool { const _SLINT_BUNDLED_LANGUAGES: &[&str] = &[#(#lang),*];
sp::set_language_internal(lang, &[#(#lang),*])
}
) )
} }

View file

@ -22,6 +22,8 @@ pub(crate) struct SlintContextInner {
/// so that every translated string gets re-translated. The property's value is the current selected /// so that every translated string gets re-translated. The property's value is the current selected
/// language when bundling translations. /// language when bundling translations.
pub(crate) translations_dirty: core::pin::Pin<Box<Property<usize>>>, 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: pub(crate) window_shown_hook:
core::cell::RefCell<Option<Box<dyn FnMut(&Rc<dyn crate::platform::WindowAdapter>)>>>, core::cell::RefCell<Option<Box<dyn FnMut(&Rc<dyn crate::platform::WindowAdapter>)>>>,
} }
@ -39,6 +41,7 @@ impl SlintContext {
platform, platform,
window_count: 0.into(), window_count: 0.into(),
translations_dirty: Box::pin(Property::new_named(0, "SlintContext::translations")), translations_dirty: Box::pin(Property::new_named(0, "SlintContext::translations")),
translations_bundle_languages: Default::default(),
window_shown_hook: Default::default(), window_shown_hook: Default::default(),
})) }))
} }

View file

@ -305,25 +305,70 @@ pub fn translate_from_bundle_with_plural(
output output
} }
pub fn set_language_internal(language: &str, languages: &[&str]) -> bool { /// This function is called by the generated code to assign the list of bundled languages.
let idx = languages.iter().position(|x| *x == language); /// Do nothing if the list is already assigned.
if let Some(idx) = idx { pub fn set_bundled_languages(languages: &[&'static str]) {
crate::context::GLOBAL_CONTEXT.with(|ctx| { crate::context::GLOBAL_CONTEXT.with(|ctx| {
let Some(ctx) = ctx.get() else { return false }; 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); ctx.0.translations_dirty.as_ref().set(idx);
true Ok(())
}) } else if locale == "" || locale == "en" {
} else if language == "en" {
crate::context::GLOBAL_CONTEXT.with(|ctx| {
let Some(ctx) = ctx.get() else { return false };
ctx.0.translations_dirty.as_ref().set(0); ctx.0.translations_dirty.as_ref().set(0);
true Ok(())
}) } else {
} else { Err(SelectBundledTranslationError::LanguageNotFound {
false 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")] #[cfg(feature = "ffi")]
mod ffi { mod ffi {
#![allow(unsafe_code)] #![allow(unsafe_code)]
@ -412,14 +457,17 @@ mod ffi {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn slint_translate_set_language( pub extern "C" fn slint_translate_set_bundled_languages(languages: Slice<Slice<'static, u8>>) {
lang: Slice<u8>,
languages: Slice<Slice<u8>>,
) -> bool {
let languages = languages let languages = languages
.iter() .iter()
.map(|x| core::str::from_utf8(x).unwrap()) .map(|x| core::str::from_utf8(x.as_slice()).unwrap())
.collect::<alloc::vec::Vec<_>>(); .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();
} }
} }

View file

@ -11,6 +11,7 @@
out property <string> t2: @tr("Hello {}.", "World"); out property <string> t2: @tr("Hello {}.", "World");
out property <string> t3: @tr("{} Hello {}", int_value, "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> 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")); 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> 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 <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" && 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{}." && 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"; && 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"; auto result2 = "xx1xx\nyy-999yy\nyy0yy\nyy42yy";
assert_eq(instance.get_result1(), result1); assert_eq(instance.get_result1(), result1);
assert_eq(instance.get_result2(), result2); assert_eq(instance.get_result2(), result2);
assert_eq(instance.get_t5(), "Untranslated string");
assert(instance.get_test()); 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_result1(), result1);
assert_eq(instance.get_result2(), result2); assert_eq(instance.get_result2(), result2);
assert_eq(instance.get_t5(), "Untranslated string");
assert(instance.get_test()); assert(instance.get_test());
assert(slint_set_language("up")); assert(slint::select_bundled_translation("up"));
std::string result1_upper = result1; std::string result1_upper = result1;
std::transform(result1_upper.begin(), result1_upper.end(), result1_upper.begin(), ::toupper); std::transform(result1_upper.begin(), result1_upper.end(), result1_upper.begin(), ::toupper);
std::string result2_upper = result2; std::string result2_upper = result2;
std::transform(result2_upper.begin(), result2_upper.end(), result2_upper.begin(), ::toupper); 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_result1()), result1_upper);
assert_eq(std::string_view(instance.get_result2()), result2_upper); assert_eq(std::string_view(instance.get_result2()), result2_upper);
assert_eq(instance.get_t5(), "Untranslated string");
assert(!instance.get_test()); 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_result1()), result1_upper);
assert_eq(std::string_view(instance.get_result2()), result2_upper); assert_eq(std::string_view(instance.get_result2()), result2_upper);
assert_eq(instance.get_t5(), "Untranslated string");
assert(!instance.get_test()); assert(!instance.get_test());
assert(slint_set_language("")); assert(slint::select_bundled_translation(""));
assert_eq(instance.get_result1(), result1); assert_eq(instance.get_result1(), result1);
assert_eq(instance.get_result2(), result2); assert_eq(instance.get_result2(), result2);
assert_eq(instance.get_t5(), "Untranslated string");
assert(instance.get_test()); 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_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_result2(), "rr1rr\nrr-999rr\nrr0rr\nss42ss");
assert_eq(instance.get_t5(), "Untranslated string");
assert(!instance.get_test()); 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"; let result2 = "xx1xx\nyy-999yy\nyy0yy\nyy42yy";
assert_eq!(instance.get_result1(), result1); assert_eq!(instance.get_result1(), result1);
assert_eq!(instance.get_result2(), result2); assert_eq!(instance.get_result2(), result2);
assert_eq!(instance.get_t5(), "Untranslated string");
assert!(instance.get_test()); 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_result1(), result1);
assert_eq!(instance.get_result2(), result2); assert_eq!(instance.get_result2(), result2);
assert_eq!(instance.get_t5(), "Untranslated string");
assert!(instance.get_test()); 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_result1(), result1.to_uppercase());
assert_eq!(instance.get_result2(), result2.to_uppercase()); assert_eq!(instance.get_result2(), result2.to_uppercase());
assert_eq!(instance.get_t5(), "Untranslated string");
assert!(!instance.get_test()); 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_result1(), result1.to_uppercase());
assert_eq!(instance.get_result2(), result2.to_uppercase()); assert_eq!(instance.get_result2(), result2.to_uppercase());
assert_eq!(instance.get_t5(), "Untranslated string");
assert!(!instance.get_test()); 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_result1(), result1);
assert_eq!(instance.get_result2(), result2); assert_eq!(instance.get_result2(), result2);
assert_eq!(instance.get_t5(), "Untranslated string");
assert!(instance.get_test()); 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_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_result2(), "rr1rr\nrr-999rr\nrr0rr\nss42ss");
assert_eq!(instance.get_t5(), "Untranslated string");
assert!(!instance.get_test()); assert!(!instance.get_test());
``` ```