Main change is switching from individual String allocations to a shared buffer, along with caching icu4x formatters to reduce duplicated work.
RA3H7PWCI7WHONXB32IWYECQ3VKPKEXXYAJDIOHWNAGHDO7L5ZLQC 4MRF5E76QSW3EPICI6TNEGJ2KSBWODWMIDQPLYALDWBYWKAV5LJAC P6FW2GGOW24UZZAWQ6IDDI66JBWTIY26TATMCIOETZ4GRRGGUI3AC NO3PDO7PY7J3WPADNCS5VD6HKFY63E23I3SDR4DHXNVQJTG27RAAC F5LG7WENUUDRSCTDMA4M6BAC5RWTGQO45C4ZEBZDX6FHCTTHBVGQC HHJDRLLNN36UNIA7STAXEEVBCEMPJNB7SJQOS3TJLLYN4AEZ4MHQC BFL2Y7GN6NBXXNAUSD4M6T6CIVQ2OLERPE2CAFSLRF377WFFTVCQC KZLFC7OWYNK3G5YNHRANUK3VUVCM6W6J34N7UABYA24XMZWAVVHQC 7U2DXFMPZO4P53AMWYCVXG3EPB7UIAPEY4PDDINX4TTABHD5NGMQC CESJ4CTO26X4GBZBPXRXLOJT3JQJOGFN5EJSNAAZELNQRZF7QSYAC IRW6JACS3KVVA6HW5SBNBOHOQ2WRBHYGDND3FUWJYKJC7ZMOAVOQC 3NMKD6I57ONAGHEN4PZIAV2KPYESVR4JL3DTWSHXKCMVJBEQ4GIQC C6W7N6N57UCNHEV55HEZ3G7WN2ZOBGMFBB5M5ZPDB2HNNHHTOPBQC WWDZWJTRJWSLVFMQFHS7JMDPK5VNDIQ6IHSMES7BVKYHZY6WRYKAC 7M4UI3TWQIAA333GQ577HDWDWZPSZKWCYG556L6SBRLB6SZDQYPAC 7JPOCQEISAIOD7LV4JYBE6NNUWUKKNE73MEPQYTIZ7PP44ZAD2RAC MABGENI7CW5F5D3BFUJ7BS2H7XPYG4F3UMWGDPFFSMCCZKUUDLDQC LU6IFZFGPIKF3CBWZWITWVBSCYWF7Q4UXJDXVRWZ4XV7PKE5RSTQC JUV7C6ET4ZQJNLO7B4JB7XMLV2YUBZB4ARJHN4JEPHDGGBWGLEVAC BAH2JCJPTDXAE6XGSLIPBQZU4GQY65HI66Q4XTFNL65MV6VSNF2QC U2PHMYPDFQQYTPDVVJLWDJM5G45ILXLWDDDTZVV2NBOSCED323MQC QJC4IQITOQP65AFLA5CMH2EXHB6B3SOLW2XBV72U5ZQU2KOR2EIAC QM64L3XOUB74M2D7TXDJWXGJNQN46IMF22Y24VNFQ5FEWODLVBLAC NB7K77TZAT5ESYFZATMSGYPKOW3GGVWZLLNDAD4JKZEG7KUAX2HAC 6XEMHUGSNX5YSWZYM7PZUTTUMFODMGO74QLHGEXQ5LAC7LPS7JNQC KFFAQIZUWCJGRHOPDYXZNZM5DESD6XYU4PK3YH7T25OIMRR6O2MQC AE3AZFVKJBURLY6T6H5477BSP5LISUQYPSPDRSPXRO435KGYTRZAC S26YOXQIUO3B7FCWZ33RI54OHFVXUDSFKBMVAAND3BW3H5WRGNRAC RUCC2HKZZTUHN3G6IWS4NK3VYGXAI6PORJH2YZKPRAYSDWH63ESQC USKESL6XR6C7676X3PO3SFFL5EMKMA7EQMPZAA72A7F7UZSONOIQC 7YOM2QEFZ5HWVEISP3VIR2GKF2NNH4KKTLTWEMYMMWNYKICXWTZAC EKXWNEPK4FTYKT2RJL2L7HTM64VQGDD3DYD6NZIDGMMV6ITHUVZAC PGBXJWIHSVTRD7CGDSCPC4YHI65EBKMQFEX62RZWL4EZB63622XAC IZ67IMRIPBOYLOAR5WE5NYA7MHOT7TXXEE7WM63MU4JSH6OM7YQQC BC22FLOQBQ6EOUSCN6THUXQDZYZPWSF6QJX5Z6WA4GYFR6BK4DZAC XSRT5QWX3WE6RQC2Y3NTAEVV5GQDGDR6UCQMNW2EFLSNRRRKNPIAC YUW3BUXXSWXHLNQNVDOCHSZ44G4NJ64T75DFOO6KKIFFTYXBBF7QC AS7RDZT74V3SSWFSJYEGHT64QCEOFEZ46GUPF4ZAKEHTHBBNG7KQC R2BAN2V6VS4OBNG6MK5BOUYQCJMGLD37IIE5RX4HXZLQ3C5AQRHAC XDJBTEXUZNIAC2TKC4Z3OZORWAXR4ZWYUHNM6OEGXTL6WZDXOVZQC 2HHBS7VWRQRDNDCCSV3BJJJHA7LGN3L4CMKIP7URWQFXPI6QFNDQC XPGOKS6XWM2Q2R74DDEHQRMZH6BWQH2FMQGAXBLO7FW52J3VFKSQC Q7LUHXXBN3ACNMGT4O2SKY5EGDEJQXMVJGYETTKNWIUAZCQV6HGAC LYOV6ZIRUE34ZJG6X6BVPZ6R4LLHLMMWC5FHVLGW73HIHQUHDJYQC XGNME3WRU3MJDTFHUFJYARLVXWBZIH5ODBOIIFTXHNCBTZQH2R7QC O77KA6C4UJGZXVGPEA7WCRQH6XYQJPWETSPDXI3VOKOSRQND7JEQC 5TEX4MNUC4LDDRMNEOVCFNUUEZAGUXMKO3OIEQFXWRQKXSHY2NRQC VZYZRAO4EXCHW2LBVFG5ELSWG5SCNDREMJ6RKQ4EKQGI2T7SD3ZQC UKFEFT6LSI4K7X6UHQFZYD52DILKXMZMYSO2UYS2FCHNPXIF4BEQC pub fn new<L: Localize>(environment: &'environment InteractionEnvironment,prompt: L,) -> Self {let localized_prompt = prompt.localize_for(&environment.locale);
pub fn new<L: Localize>(environment: &'environment InteractionEnvironment, prompt: L) -> Self {let mut localized_prompt = String::new();prompt.localize(&environment.localization_context, &mut localized_prompt);
let confirmation_text = confirmation.localize_for(&self.environment.locale);let mismatch_error_text = mismatch_error.localize_for(&self.environment.locale);
let mut localized_prompt = String::new();confirmation_prompt.localize(&self.environment.localization_context,&mut localized_prompt,);let mut localized_mismatch_error = String::new();mismatch_error.localize(&self.environment.localization_context,&mut localized_mismatch_error,);
Err(message) => {// Localize the error messageErr(message.localize_for(&locale))
Err(error_message) => {let mut localized_error = String::new();error_message.localize(&localization_context, &mut localized_error);Err(localized_error)
pub fn new<L: Localize>(environment: &'environment InteractionEnvironment,prompt: L,) -> Self {let localized_prompt = prompt.localize_for(&environment.locale);
pub fn new<L: Localize>(environment: &'environment InteractionEnvironment, prompt: L) -> Self {let mut localized_prompt = String::new();prompt.localize(&environment.localization_context, &mut localized_prompt);
let localized_text = default.localize_for(&self.environment.locale);self.default = Some(localized_text);
let mut localized_default = String::new();default.localize(&self.environment.localization_context,&mut localized_default,);self.default = Some(localized_default);
self.progress_bars.println(message.localize_for(&self.locale))?;
let mut localized_message = String::new();message.localize(&self.localization_context, &mut localized_message);self.progress_bars.println(localized_message)?;
let result = message.localize_for(&locale);pretty_assertions::assert_eq!(result, expected.as_ref());
let context = Context::new(locale);let mut buffer = String::new();message.localize(&context, &mut buffer);pretty_assertions::assert_eq!(expected.as_ref(), buffer);
let mut buffer = String::new();// TODO: handle different rule types according to fluent code (not just cardinal)// TODO: only generate this when needed in messageconst PLURAL_RULE_TYPE: ::l10n_embed::macro_prelude::icu_plurals::PluralRuleType =::l10n_embed::macro_prelude::icu_plurals::PluralRuleType::Cardinal;let plural_options = ::l10n_embed::macro_prelude::icu_plurals::PluralRulesOptions::default().with_type(PLURAL_RULE_TYPE);let plural_rules = ::l10n_embed::macro_prelude::icu_plurals::PluralRules::try_new(locale.into(),plural_options,).unwrap();
quote!(plural_rules.category_for(#category_reference))
quote! {{// TODO: handle different rule types according to fluent code (not just cardinal)let plural_rules = context.plural_rule(::l10n_embed::macro_prelude::icu_plurals::PluralRuleType::Cardinal).unwrap();plural_rules.category_for(#category_reference)}}
use fixed_decimal::{Decimal, FloatPrecision};use icu_experimental::relativetime::{RelativeTimeFormatter, RelativeTimeFormatterOptions, options::Numeric,};use icu_locale::Locale;
use fixed_decimal::Decimal;
let formatter = match selected_unit {Unit::Year => {RelativeTimeFormatter::try_new_long_year(locale.into(), FORMATTER_OPTIONS)}Unit::Month => {RelativeTimeFormatter::try_new_long_month(locale.into(), FORMATTER_OPTIONS)}Unit::Week => {RelativeTimeFormatter::try_new_long_week(locale.into(), FORMATTER_OPTIONS)}Unit::Day => RelativeTimeFormatter::try_new_long_day(locale.into(), FORMATTER_OPTIONS),Unit::Hour => {RelativeTimeFormatter::try_new_long_hour(locale.into(), FORMATTER_OPTIONS)}Unit::Minute => {RelativeTimeFormatter::try_new_long_minute(locale.into(), FORMATTER_OPTIONS)}Unit::Second => {RelativeTimeFormatter::try_new_long_second(locale.into(), FORMATTER_OPTIONS)}_ => unreachable!(),}.unwrap();
let formatter = context.relative_time_formatter(selected_unit).unwrap();
fn localize_for(&self, locale: &icu_locale::Locale) -> String {let message = self.message.localize_for(locale);match self.style {Some(style) => format!("{style}{message}{style:#}"),None => message,
fn localize(&self, context: &Context, buffer: &mut String) {// Prefix with the terminal color codesif let Some(style) = self.style {let prefix = style.render().to_string();buffer.push_str(&prefix);}self.message.localize(context, buffer);// Suffix with the terminal reset codesif let Some(style) = self.style {let suffix = style.render_reset().to_string();buffer.push_str(&suffix);
fn localize_for(&self, locale: &Locale) -> String {let list_formatter = ListFormatter::try_new_and(locale.into(),ListFormatterOptions::default().with_length(self.length),).unwrap();
fn localize(&self, context: &Context, buffer: &mut String) {let list_formatter = context.list_formatter(self.length);let localized_messages = self.messages.iter().map(|message| {let mut buffer = String::new();message.localize(context, &mut buffer);
use icu_decimal::options::DecimalFormatterOptions;use icu_decimal::{DecimalFormatter, DecimalFormatterPreferences};use icu_experimental::relativetime::{RelativeTimeFormatter, RelativeTimeFormatterOptions, RelativeTimeFormatterPreferences,};use icu_list::options::{ListFormatterOptions, ListLength};use icu_list::{ListFormatter, ListFormatterPreferences};
fn localize_for(&self, locale: &Locale) -> String;
fn localize(&self, context: &Context, buffer: &mut String);}pub struct Context {pub locale: Locale,decimal_formatter: OnceLock<DecimalFormatter>,list_formatters: [OnceLock<ListFormatter>; 3],plural_rules: [OnceLock<PluralRules>; 2],relative_time_formatters: [OnceLock<RelativeTimeFormatter>; 7],}impl Context {pub fn new(locale: Locale) -> Self {Self {locale,decimal_formatter: OnceLock::new(),list_formatters: [const { OnceLock::new() }; 3],plural_rules: [const { OnceLock::new() }; 2],relative_time_formatters: [const { OnceLock::new() }; 7],}}pub fn decimal_formatter(&self) -> &DecimalFormatter {self.decimal_formatter.get_or_init(|| {DecimalFormatter::try_new(DecimalFormatterPreferences::from(&self.locale),DecimalFormatterOptions::default(),).unwrap()})}pub fn list_formatter(&self, length: ListLength) -> &ListFormatter {let index = match length {ListLength::Wide => 0,ListLength::Short => 1,ListLength::Narrow => 2,_ => unimplemented!(),};self.list_formatters[index].get_or_init(|| {ListFormatter::try_new_and(ListFormatterPreferences::from(&self.locale),ListFormatterOptions::default().with_length(length),).unwrap()})}pub fn plural_rule(&self, rule_type: PluralRuleType) -> Option<&PluralRules> {let index = match rule_type {PluralRuleType::Cardinal => 0,PluralRuleType::Ordinal => 1,_ => return None,};let plural_rules = self.plural_rules[index].get_or_init(|| {PluralRules::try_new(PluralRulesPreferences::from(&self.locale),PluralRulesOptions::default().with_type(rule_type),).unwrap()});Some(plural_rules)}pub fn relative_time_formatter(&self, unit: jiff::Unit) -> Option<&RelativeTimeFormatter> {let index = match unit {jiff::Unit::Year => 0,jiff::Unit::Month => 1,jiff::Unit::Week => 2,jiff::Unit::Day => 3,jiff::Unit::Hour => 4,jiff::Unit::Minute => 5,jiff::Unit::Second => 6,_ => return None,};let formatter = self.relative_time_formatters[index].get_or_init(|| {let preferences = RelativeTimeFormatterPreferences::from(&self.locale);const OPTIONS: RelativeTimeFormatterOptions = RelativeTimeFormatterOptions {numeric: icu_experimental::relativetime::options::Numeric::Auto,};match unit {jiff::Unit::Year => RelativeTimeFormatter::try_new_long_year(preferences, OPTIONS),jiff::Unit::Month => {RelativeTimeFormatter::try_new_long_month(preferences, OPTIONS)}jiff::Unit::Week => RelativeTimeFormatter::try_new_long_week(preferences, OPTIONS),jiff::Unit::Day => RelativeTimeFormatter::try_new_long_day(preferences, OPTIONS),jiff::Unit::Hour => RelativeTimeFormatter::try_new_long_hour(preferences, OPTIONS),jiff::Unit::Minute => {RelativeTimeFormatter::try_new_long_minute(preferences, OPTIONS)}jiff::Unit::Second => {RelativeTimeFormatter::try_new_long_second(preferences, OPTIONS)}_ => unreachable!(),}.unwrap()});Some(formatter)}
fn localize_for(&self, locale: &Locale) -> String {let localized_items: Vec<String> = self.messages.iter().map(|item| item.localize_for(locale)).collect();
fn localize(&self, context: &Context, buffer: &mut String) {let separator = "\n".repeat(SEPARATOR_COUNT);for (index, message) in self.messages.iter().enumerate() {// Add the newlines before every additional lineif index > 0 {buffer.push_str(&separator)}
fn localize_for(&self, locale: &Locale) -> String {let formatter =DecimalFormatter::try_new(locale.into(), DecimalFormatterOptions::default()).unwrap();
fn localize(&self, context: &Context, buffer: &mut String) {let formatter = context.decimal_formatter();
println!("Current time: {}",current_timestamp.localize_for(&DEFAULT_LOCALE));println!("Unix epoch: {}", unix_epoch.localize_for(&DEFAULT_LOCALE));println!("Two hours from now: {}",in_two_hours.localize_for(&DEFAULT_LOCALE));println!("Since start of year (UTC): {}",start_of_year.localize_for(&DEFAULT_LOCALE));
let timestamps: [(&str, Timestamp); 4] = [("Current time", current_timestamp),("Unix epoch", unix_epoch),("Two hours from now", in_two_hours),("Since start of year (UTC)", start_of_year),];let context = Context::new(locale!("en-US"));let mut buffer = String::new();for (name, timestamp) in timestamps {buffer.clear();timestamp.localize(&context, &mut buffer);println!("{name}: {buffer}");}
println!("Five million: {}",five_million.localize_for(&DEFAULT_LOCALE));println!("Static string: {}",static_str.localize_for(&DEFAULT_LOCALE));println!("Unix epoch: {}", unix_epoch.localize_for(&DEFAULT_LOCALE));
let styles: [(&str, Box<dyn Localize>); 3] = [("Bold green", Box::new(bold_green)),("Italic blue", Box::new(italic_blue)),("Strikethrough red", Box::new(strikethrough_red)),];let context = Context::new(locale!("en-US"));let mut buffer = String::new();for (name, style) in styles {buffer.clear();style.localize(&context, &mut buffer);println!("{name}: {buffer}");}
println!("Static string: {}",static_str.localize_for(&DEFAULT_LOCALE));println!("Heap allocated string: {}",heap_allocated_string.localize_for(&DEFAULT_LOCALE));println!("Copy on write string: {}",copy_on_write_str.localize_for(&DEFAULT_LOCALE));
let strings: [(&str, Box<dyn Localize>); 3] = [("Static string", Box::new(static_str)),("Heap allocated string", Box::new(heap_allocated_string)),("Copy on write string", Box::new(copy_on_write_str)),];let context = Context::new(locale!("en-US"));let mut buffer = String::new();for (name, string) in strings {buffer.clear();string.localize(&context, &mut buffer);println!("{name}: {buffer}");}
println!("Current directory: {}",current_directory.localize_for(&DEFAULT_LOCALE));println!("Current directory (UTF-8): {}",current_directory_utf8.localize_for(&DEFAULT_LOCALE));
let directories: [(&str, Box<dyn Localize>); 2] = [("Current directory", Box::new(current_directory)),("Current directory (UTF-8)",Box::new(current_directory_utf8),),];let context = Context::new(locale!("en-US"));let mut buffer = String::new();for (name, directory) in directories {buffer.clear();directory.localize(&context, &mut buffer);println!("{name}: {buffer}");}
println!("Narrow list: {}", narrow_list.localize_for(&DEFAULT_LOCALE));println!("Short list: {}", short_list.localize_for(&DEFAULT_LOCALE));println!("Wide list: {}", wide_list.localize_for(&DEFAULT_LOCALE));
let lists: [(&str, Box<dyn Localize>); 3] = [("Narrow list", Box::new(narrow_list)),("Short list", Box::new(short_list)),("Wide list", Box::new(wide_list)),];let context = Context::new(locale!("en-US"));let mut buffer = String::new();for (name, list) in lists {buffer.clear();list.localize(&context, &mut buffer);println!("{name}: {buffer}");}
let layouts = [("Single newline",single_newline.localize_for(&DEFAULT_LOCALE),),("Double newlines",double_newlines.localize_for(&DEFAULT_LOCALE),),("Vertical padding",vertical_padding.localize_for(&DEFAULT_LOCALE),),("Horizontal padding",horizontal_padding.localize_for(&DEFAULT_LOCALE),),("Combined padding",combined_padding.localize_for(&DEFAULT_LOCALE),),("Padded newlines",padded_newlines.localize_for(&DEFAULT_LOCALE),),
let layouts: [(&str, Box<dyn Localize>); 6] = [("Single newline", Box::new(single_newline)),("Double newlines", Box::new(double_newlines)),("Vertical padding", Box::new(vertical_padding)),("Horizontal padding", Box::new(horizontal_padding)),("Combined padding", Box::new(combined_padding)),("Padded newlines", Box::new(padded_newlines)),
println!("Zero: {}", zero.localize_for(&DEFAULT_LOCALE));println!("Five million: {}",five_million.localize_for(&DEFAULT_LOCALE));println!("Zero point two: {}",zero_point_two.localize_for(&DEFAULT_LOCALE));println!("Negative two point five: {}",negative_two_point_five.localize_for(&DEFAULT_LOCALE));
let numbers: [(&str, Box<dyn Localize>); 4] = [("Zero", Box::new(zero)),("Five million", Box::new(five_million)),("Zero point two", Box::new(zero_point_two)),("Negative two point five", Box::new(negative_two_point_five)),];for (name, number) in numbers {buffer.clear();number.localize(&context, &mut buffer);println!("{name}: {buffer}");}
writeable.workspace = true
writeable = "0.6"