Skip to content

Commit 95f5685

Browse files
authored
Bundle translations (#6661)
This currently doesn't have public API to enable it yet. TODO: - Error handling in the compiler - Public API in the compiler configuration - Documentation
1 parent 2f62c60 commit 95f5685

File tree

23 files changed

+1163
-15
lines changed

23 files changed

+1163
-15
lines changed

api/cpp/include/slint.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,6 +1265,39 @@ inline SharedString translate(const SharedString &original, const SharedString &
12651265
return result;
12661266
}
12671267

1268+
inline SharedString translate_from_bundle(std::span<const char8_t *const> strs,
1269+
cbindgen_private::Slice<SharedString> arguments)
1270+
{
1271+
SharedString result;
1272+
cbindgen_private::slint_translate_from_bundle(
1273+
cbindgen_private::Slice<const char *>(
1274+
const_cast<char const **>(reinterpret_cast<char const *const *>(strs.data())),
1275+
strs.size()),
1276+
arguments, &result);
1277+
return result;
1278+
}
1279+
inline SharedString
1280+
translate_from_bundle_with_plural(std::span<const char8_t *const> strs,
1281+
std::span<const uint32_t> indices,
1282+
std::span<uintptr_t (*const)(int32_t)> plural_rules,
1283+
cbindgen_private::Slice<SharedString> arguments, int n)
1284+
{
1285+
SharedString result;
1286+
cbindgen_private::Slice<const char *> strs_slice(
1287+
const_cast<char const **>(reinterpret_cast<char const *const *>(strs.data())),
1288+
strs.size());
1289+
cbindgen_private::Slice<uint32_t> indices_slice(
1290+
const_cast<uint32_t *>(reinterpret_cast<const uint32_t *>(indices.data())),
1291+
indices.size());
1292+
cbindgen_private::Slice<uintptr_t (*)(int32_t)> plural_rules_slice(
1293+
const_cast<uintptr_t (**)(int32_t)>(
1294+
reinterpret_cast<uintptr_t (*const *)(int32_t)>(plural_rules.data())),
1295+
plural_rules.size());
1296+
cbindgen_private::slint_translate_from_bundle_with_plural(
1297+
strs_slice, indices_slice, plural_rules_slice, arguments, n, &result);
1298+
return result;
1299+
}
1300+
12681301
} // namespace private_api
12691302

12701303
#ifdef SLINT_FEATURE_GETTEXT

api/rs/slint/private_unstable_api.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,9 @@ pub mod re_exports {
213213
};
214214
pub use i_slint_core::slice::Slice;
215215
pub use i_slint_core::timers::{Timer, TimerMode};
216+
pub use i_slint_core::translations::{
217+
set_language_internal, translate_from_bundle, translate_from_bundle_with_plural,
218+
};
216219
pub use i_slint_core::window::{
217220
InputMethodRequest, WindowAdapter, WindowAdapterRc, WindowInner,
218221
};

internal/compiler/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ display-diagnostics = ["codemap", "codemap-diagnostic"]
2929

3030
# Enabled the support to render images and font in the binary
3131
software-renderer = ["image", "dep:resvg", "fontdue", "i-slint-common/shared-fontdb", "dep:rayon"]
32-
3332
embed-glyphs-as-sdf = ["dep:fdsm", "dep:ttf-parser-fdsm", "dep:nalgebra", "dep:image-fdsm", "dep:rayon"]
3433

34+
# Translation bundler
35+
bundle-translations = ["dep:polib"]
36+
3537
default = []
3638

3739
[dependencies]
@@ -65,6 +67,8 @@ ttf-parser-fdsm = { package = "ttf-parser", version = "0.24.1", optional = true
6567
image-fdsm = { package = "image", version = "0.25", optional = true, default-features = false }
6668
nalgebra = { version = "0.33.0", optional = true }
6769
rayon = { workspace = true, optional = true }
70+
# translations
71+
polib = { version = "0.2", optional = true }
6872

6973
[dev-dependencies]
7074
i-slint-parser-test-macro = { path = "./parser-test-macro" }

internal/compiler/generator/cpp.rs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,11 @@ pub fn generate(
710710

711711
let llr = llr::lower_to_item_tree::lower_to_item_tree(&doc, compiler_config);
712712

713+
#[cfg(feature = "bundle-translations")]
714+
if let Some(translations) = &llr.translations {
715+
generate_translation(translations, &llr, &mut file.resources);
716+
}
717+
713718
// Forward-declare the root so that sub-components can access singletons, the window, etc.
714719
file.declarations.extend(
715720
llr.public_components
@@ -3369,6 +3374,16 @@ fn compile_expression(expr: &llr::Expression, ctx: &EvaluationContext) -> String
33693374
)
33703375
}
33713376
Expression::EmptyComponentFactory => panic!("component-factory not yet supported in C++"),
3377+
Expression::TranslationReference { format_args, string_index, plural } => {
3378+
let args = compile_expression(format_args, ctx);
3379+
match plural {
3380+
Some(plural) => {
3381+
let plural = compile_expression(plural, ctx);
3382+
format!("slint::private_api::translate_from_bundle_with_plural(slint_translation_bundle_plural_{string_index}_str, slint_translation_bundle_plural_{string_index}_idx, slint_translated_plural_rules, {args}, {plural})")
3383+
}
3384+
None => format!("slint::private_api::translate_from_bundle(slint_translation_bundle_{string_index}, {args})"),
3385+
}
3386+
},
33723387
}
33733388
}
33743389

@@ -3828,3 +3843,106 @@ fn generate_type_aliases(file: &mut File, doc: &Document) {
38283843

38293844
file.declarations.extend(type_aliases);
38303845
}
3846+
3847+
#[cfg(feature = "bundle-translations")]
3848+
fn generate_translation(
3849+
translations: &llr::translations::Translations,
3850+
compilation_unit: &llr::CompilationUnit,
3851+
declarations: &mut Vec<Declaration>,
3852+
) {
3853+
for (idx, m) in translations.strings.iter().enumerate() {
3854+
declarations.push(Declaration::Var(Var {
3855+
ty: "const char8_t* const".into(),
3856+
name: format_smolstr!("slint_translation_bundle_{idx}"),
3857+
array_size: Some(m.len()),
3858+
init: Some(format!(
3859+
"{{ {} }}",
3860+
m.iter()
3861+
.map(|s| match s {
3862+
Some(s) => format_smolstr!("u8\"{}\"", escape_string(s.as_str())),
3863+
None => "nullptr".into(),
3864+
})
3865+
.join(", ")
3866+
)),
3867+
..Default::default()
3868+
}));
3869+
}
3870+
for (idx, ms) in translations.plurals.iter().enumerate() {
3871+
let all_strs = ms.iter().flatten().flatten();
3872+
let all_strs_len = all_strs.clone().count();
3873+
declarations.push(Declaration::Var(Var {
3874+
ty: "const char8_t* const".into(),
3875+
name: format_smolstr!("slint_translation_bundle_plural_{}_str", idx),
3876+
array_size: Some(all_strs_len),
3877+
init: Some(format!(
3878+
"{{ {} }}",
3879+
all_strs.map(|s| format_smolstr!("u8\"{}\"", escape_string(s.as_str()))).join(", ")
3880+
)),
3881+
..Default::default()
3882+
}));
3883+
3884+
let mut count = 0;
3885+
declarations.push(Declaration::Var(Var {
3886+
ty: "const uint32_t".into(),
3887+
name: format_smolstr!("slint_translation_bundle_plural_{}_idx", idx),
3888+
array_size: Some(ms.len()),
3889+
init: Some(format!(
3890+
"{{ {} }}",
3891+
ms.iter()
3892+
.map(|x| {
3893+
count += x.as_ref().map_or(0, |x| x.len());
3894+
count
3895+
})
3896+
.join(", ")
3897+
)),
3898+
..Default::default()
3899+
}));
3900+
}
3901+
let lang_len = translations.languages.len();
3902+
declarations.push(Declaration::Function(Function {
3903+
name: "slint_set_language".into(),
3904+
signature: "(std::string_view lang)".into(),
3905+
is_inline: true,
3906+
statements: Some(vec![
3907+
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(", ")),
3908+
format!("return slint::cbindgen_private::slint_translate_set_language(slint::private_api::string_to_slice(lang), {{ languages.data(), {lang_len} }});"
3909+
)]),
3910+
..Default::default()
3911+
}));
3912+
3913+
let ctx = EvaluationContext {
3914+
compilation_unit,
3915+
current_sub_component: None,
3916+
current_global: None,
3917+
generator_state: CppGeneratorContext {
3918+
global_access: "\n#error \"language rule can't access state\";".into(),
3919+
conditional_includes: &Default::default(),
3920+
},
3921+
parent: None,
3922+
argument_types: &[Type::Int32],
3923+
};
3924+
declarations.push(Declaration::Var(Var {
3925+
ty: format_smolstr!(
3926+
"const std::array<uintptr_t (*const)(int32_t), {}>",
3927+
translations.plural_rules.len()
3928+
),
3929+
name: "slint_translated_plural_rules".into(),
3930+
init: Some(format!(
3931+
"{{ {} }}",
3932+
translations
3933+
.plural_rules
3934+
.iter()
3935+
.map(|s| match s {
3936+
Some(s) => {
3937+
format!(
3938+
"[]([[maybe_unused]] int32_t arg_0) -> uintptr_t {{ return {}; }}",
3939+
compile_expression(s, &ctx)
3940+
)
3941+
}
3942+
None => "nullptr".into(),
3943+
})
3944+
.join(", ")
3945+
)),
3946+
..Default::default()
3947+
}));
3948+
}

internal/compiler/generator/rust.rs

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,15 @@ pub fn generate(doc: &Document, compiler_config: &CompilerConfiguration) -> Toke
210210
.map(|c| format_ident!("slint_generated{}", ident(&c.id)))
211211
.unwrap_or_else(|| format_ident!("slint_generated"));
212212

213+
#[cfg(not(feature = "bundle-translations"))]
214+
let (translations, exported_tr) = (quote!(), quote!());
215+
#[cfg(feature = "bundle-translations")]
216+
let (translations, exported_tr) = llr
217+
.translations
218+
.as_ref()
219+
.map(|t| (generate_translations(t, &llr), quote!(slint_set_language,)))
220+
.unzip();
221+
213222
quote! {
214223
#[allow(non_snake_case, non_camel_case_types)]
215224
#[allow(unused_braces, unused_parens)]
@@ -224,10 +233,11 @@ pub fn generate(doc: &Document, compiler_config: &CompilerConfiguration) -> Toke
224233
#(#public_components)*
225234
#shared_globals
226235
#(#resource_symbols)*
236+
#translations
227237
const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : slint::#version_check = slint::#version_check;
228238
}
229239
#[allow(unused_imports)]
230-
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)*};
240+
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)* #exported_tr};
231241
#[allow(unused_imports)]
232242
pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
233243
}
@@ -2574,6 +2584,22 @@ fn compile_expression(expr: &Expression, ctx: &EvaluationContext) -> TokenStream
25742584
}
25752585
}
25762586
Expression::EmptyComponentFactory => quote!(slint::ComponentFactory::default()),
2587+
Expression::TranslationReference { format_args, string_index, plural } => {
2588+
let args = compile_expression(format_args, ctx);
2589+
match plural {
2590+
Some(plural) => {
2591+
let plural = compile_expression(plural, ctx);
2592+
quote!(sp::translate_from_bundle_with_plural(
2593+
&self::_SLINT_TRANSLATED_STRINGS_PLURALS[#string_index],
2594+
&self::_SLINT_TRANSLATED_PLURAL_RULES,
2595+
sp::Slice::<sp::SharedString>::from(#args).as_slice(),
2596+
#plural as _
2597+
))
2598+
}
2599+
None => quote!(sp::translate_from_bundle(&self::_SLINT_TRANSLATED_STRINGS[#string_index], sp::Slice::<sp::SharedString>::from(#args).as_slice())),
2600+
2601+
}
2602+
},
25772603
}
25782604
}
25792605

@@ -3138,3 +3164,59 @@ fn generate_named_exports(doc: &Document) -> Vec<TokenStream> {
31383164
})
31393165
.collect::<Vec<_>>()
31403166
}
3167+
3168+
#[cfg(feature = "bundle-translations")]
3169+
fn generate_translations(
3170+
translations: &llr::translations::Translations,
3171+
compilation_unit: &llr::CompilationUnit,
3172+
) -> TokenStream {
3173+
let strings = translations.strings.iter().map(|strings| {
3174+
let array = strings.iter().map(|s| match s.as_ref().map(SmolStr::as_str) {
3175+
Some(s) => quote!(Some(#s)),
3176+
None => quote!(None),
3177+
});
3178+
quote!(&[#(#array),*])
3179+
});
3180+
let plurals = translations.plurals.iter().map(|plurals| {
3181+
let array = plurals.iter().map(|p| match p {
3182+
Some(p) => {
3183+
let p = p.iter().map(SmolStr::as_str);
3184+
quote!(Some(&[#(#p),*]))
3185+
}
3186+
None => quote!(None),
3187+
});
3188+
quote!(&[#(#array),*])
3189+
});
3190+
3191+
let ctx = EvaluationContext {
3192+
compilation_unit,
3193+
current_sub_component: None,
3194+
current_global: None,
3195+
generator_state: RustGeneratorContext {
3196+
global_access: quote!(compile_error!("language rule can't access state")),
3197+
},
3198+
parent: None,
3199+
argument_types: &[Type::Int32],
3200+
};
3201+
let rules = translations.plural_rules.iter().map(|rule| {
3202+
let rule = match rule {
3203+
Some(rule) => {
3204+
let rule = compile_expression(rule, &ctx);
3205+
quote!(Some(|arg: i32| { let args = (arg,); (#rule) as usize } ))
3206+
}
3207+
None => quote!(None),
3208+
};
3209+
quote!(#rule)
3210+
});
3211+
let lang = translations.languages.iter().map(SmolStr::as_str).map(|lang| quote!(#lang));
3212+
3213+
quote!(
3214+
const _SLINT_TRANSLATED_STRINGS: &[&[sp::Option<&str>]] = &[#(#strings),*];
3215+
const _SLINT_TRANSLATED_STRINGS_PLURALS: &[&[sp::Option<&[&str]>]] = &[#(#plurals),*];
3216+
#[allow(unused)]
3217+
const _SLINT_TRANSLATED_PLURAL_RULES: &[sp::Option<fn(i32) -> usize>] = &[#(#rules),*];
3218+
pub fn slint_set_language(lang: &str) -> bool {
3219+
sp::set_language_internal(lang, &[#(#lang),*])
3220+
}
3221+
)
3222+
}

internal/compiler/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,9 @@ pub struct CompilerConfiguration {
132132

133133
/// The domain used as one of the parameter to the translate function
134134
pub translation_domain: Option<String>,
135+
/// When Some, this is the path where the translations are looked at to bundle the translations
136+
#[cfg(feature = "bundle-translations")]
137+
pub translation_path_bundle: Option<std::path::PathBuf>,
135138

136139
/// C++ namespace
137140
pub cpp_namespace: Option<String>,
@@ -220,6 +223,10 @@ impl CompilerConfiguration {
220223
components_to_generate: ComponentSelection::ExportedWindows,
221224
#[cfg(feature = "software-renderer")]
222225
font_cache: Default::default(),
226+
#[cfg(feature = "bundle-translations")]
227+
translation_path_bundle: std::env::var("SLINT_BUNDLE_TRANSLATIONS")
228+
.ok()
229+
.map(|x| x.into()),
223230
}
224231
}
225232

internal/compiler/llr.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub use item_tree::*;
1010
pub mod lower_expression;
1111
pub mod lower_to_item_tree;
1212
pub mod pretty_print;
13+
#[cfg(feature = "bundle-translations")]
14+
pub mod translations;
1315

1416
/// The optimization passes over the LLR
1517
pub mod optim_passes {

internal/compiler/llr/expression.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,15 @@ pub enum Expression {
190190
},
191191

192192
EmptyComponentFactory,
193+
194+
/// A reference to bundled translated string
195+
TranslationReference {
196+
/// An expression of type array of strings
197+
format_args: Box<Expression>,
198+
string_index: usize,
199+
/// The `n` value to use for the plural form if it is a plural form
200+
plural: Option<Box<Expression>>,
201+
},
193202
}
194203

195204
impl Expression {
@@ -306,6 +315,7 @@ impl Expression {
306315
}
307316
Self::MinMax { ty, .. } => ty.clone(),
308317
Self::EmptyComponentFactory => Type::ComponentFactory,
318+
Self::TranslationReference { .. } => Type::String,
309319
}
310320
}
311321
}
@@ -388,6 +398,12 @@ macro_rules! visit_impl {
388398
$visitor(rhs);
389399
}
390400
Expression::EmptyComponentFactory => {}
401+
Expression::TranslationReference { format_args, plural, string_index: _ } => {
402+
$visitor(format_args);
403+
if let Some(plural) = plural {
404+
$visitor(plural);
405+
}
406+
}
391407
}
392408
};
393409
}

internal/compiler/llr/item_tree.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,8 @@ pub struct CompilationUnit {
368368
pub sub_components: Vec<Rc<SubComponent>>,
369369
pub globals: Vec<GlobalComponent>,
370370
pub has_debug_info: bool,
371+
#[cfg(feature = "bundle-translations")]
372+
pub translations: Option<super::translations::Translations>,
371373
}
372374

373375
impl CompilationUnit {

0 commit comments

Comments
 (0)