@@ -65,19 +65,19 @@ include!(concat!(env!("OUT_DIR"), "/embedded_locales.rs"));
6565
6666// A struct to handle localization with optional English fallback
6767struct Localizer {
68- primary_bundle : FluentBundle < FluentResource > ,
69- fallback_bundle : Option < FluentBundle < FluentResource > > ,
68+ primary_bundle : FluentBundle < & ' static FluentResource > ,
69+ fallback_bundle : Option < FluentBundle < & ' static FluentResource > > ,
7070}
7171
7272impl Localizer {
73- fn new ( primary_bundle : FluentBundle < FluentResource > ) -> Self {
73+ fn new ( primary_bundle : FluentBundle < & ' static FluentResource > ) -> Self {
7474 Self {
7575 primary_bundle,
7676 fallback_bundle : None ,
7777 }
7878 }
7979
80- fn with_fallback ( mut self , fallback_bundle : FluentBundle < FluentResource > ) -> Self {
80+ fn with_fallback ( mut self , fallback_bundle : FluentBundle < & ' static FluentResource > ) -> Self {
8181 self . fallback_bundle = Some ( fallback_bundle) ;
8282 self
8383 }
@@ -107,7 +107,10 @@ impl Localizer {
107107 }
108108}
109109
110- // Global localizer stored in thread-local OnceLock
110+ // Cache localizer. FluentResource cannot be shared between threads while FluentBundle can be shared
111+ static UUCORE_FLUENT : OnceLock < FluentResource > = OnceLock :: new ( ) ;
112+ static CHECKSUM_FLUENT : OnceLock < FluentResource > = OnceLock :: new ( ) ;
113+ static UTIL_FLUENT : OnceLock < FluentResource > = OnceLock :: new ( ) ;
111114thread_local ! {
112115 static LOCALIZER : OnceLock <Localizer > = const { OnceLock :: new( ) } ;
113116}
@@ -136,8 +139,8 @@ fn create_bundle(
136139 locale : & LanguageIdentifier ,
137140 locales_dir : & Path ,
138141 util_name : & str ,
139- ) -> Result < FluentBundle < FluentResource > , LocalizationError > {
140- let mut bundle = FluentBundle :: new ( vec ! [ locale. clone( ) ] ) ;
142+ ) -> Result < FluentBundle < & ' static FluentResource > , LocalizationError > {
143+ let mut bundle: FluentBundle < & ' static FluentResource > = FluentBundle :: new ( vec ! [ locale. clone( ) ] ) ;
141144
142145 // Disable Unicode directional isolate characters
143146 bundle. set_use_isolating ( false ) ;
@@ -148,7 +151,8 @@ fn create_bundle(
148151 . and_then ( |locale_path| fs:: read_to_string ( locale_path) . ok ( ) )
149152 . and_then ( |ftl| FluentResource :: try_new ( ftl) . ok ( ) )
150153 {
151- bundle. add_resource_overriding ( resource) ;
154+ // use Box::leak to provide 'static lifetime for shared FluentBundle between threads
155+ bundle. add_resource_overriding ( Box :: leak ( Box :: new ( resource) ) ) ;
152156 }
153157 } ;
154158
@@ -193,10 +197,11 @@ fn init_localization(
193197 . expect ( "Default locale should always be valid" ) ;
194198
195199 // Try to create a bundle that combines common and utility-specific strings
196- let english_bundle = create_bundle ( & default_locale, locales_dir, util_name) . or_else ( |_| {
197- // Fallback to embedded utility-specific and common strings
198- create_english_bundle_from_embedded ( & default_locale, util_name)
199- } ) ?;
200+ let english_bundle: FluentBundle < & ' static FluentResource > =
201+ create_bundle ( & default_locale, locales_dir, util_name) . or_else ( |_| {
202+ // Fallback to embedded utility-specific and common strings
203+ create_english_bundle_from_embedded ( & default_locale, util_name)
204+ } ) ?;
200205
201206 let loc = if locale == & default_locale {
202207 // If requesting English, just use English as primary (no fallback needed)
@@ -220,8 +225,18 @@ fn init_localization(
220225}
221226
222227/// Helper function to parse FluentResource from content string
223- fn parse_fluent_resource ( content : & str ) -> Result < FluentResource , LocalizationError > {
224- FluentResource :: try_new ( content. to_string ( ) ) . map_err (
228+ fn parse_fluent_resource (
229+ content : & str ,
230+ cache : & ' static OnceLock < FluentResource > ,
231+ ) -> Result < & ' static FluentResource , LocalizationError > {
232+ // global cache breaks unit tests
233+ if cfg ! ( not( test) ) {
234+ if let Some ( res) = cache. get ( ) {
235+ return Ok ( res) ;
236+ }
237+ }
238+
239+ let resource = FluentResource :: try_new ( content. to_string ( ) ) . map_err (
225240 |( _partial_resource, errs) : ( FluentResource , Vec < ParserError > ) | {
226241 if let Some ( first_err) = errs. into_iter ( ) . next ( ) {
227242 let snippet = first_err
@@ -238,42 +253,48 @@ fn parse_fluent_resource(content: &str) -> Result<FluentResource, LocalizationEr
238253 LocalizationError :: LocalesDirNotFound ( "Parse error without details" . to_string ( ) )
239254 }
240255 } ,
241- )
256+ ) ?;
257+ // global cache breaks unit tests
258+ if cfg ! ( not( test) ) {
259+ Ok ( cache. get_or_init ( || resource) )
260+ } else {
261+ Ok ( Box :: leak ( Box :: new ( resource) ) )
262+ }
242263}
243264
244265/// Create a bundle from embedded English locale files with common uucore strings
245266fn create_english_bundle_from_embedded (
246267 locale : & LanguageIdentifier ,
247268 util_name : & str ,
248- ) -> Result < FluentBundle < FluentResource > , LocalizationError > {
269+ ) -> Result < FluentBundle < & ' static FluentResource > , LocalizationError > {
249270 // Only support English from embedded files
250271 if * locale != "en-US" {
251272 return Err ( LocalizationError :: LocalesDirNotFound (
252273 "Embedded locales only support en-US" . to_string ( ) ,
253274 ) ) ;
254275 }
255276
256- let mut bundle = FluentBundle :: new ( vec ! [ locale. clone( ) ] ) ;
277+ let mut bundle: FluentBundle < & ' static FluentResource > = FluentBundle :: new ( vec ! [ locale. clone( ) ] ) ;
257278 bundle. set_use_isolating ( false ) ;
258279
259280 // First, try to load common uucore strings
260281 if let Some ( uucore_content) = get_embedded_locale ( "uucore/en-US.ftl" ) {
261- let uucore_resource = parse_fluent_resource ( uucore_content) ?;
282+ let uucore_resource = parse_fluent_resource ( uucore_content, & UUCORE_FLUENT ) ?;
262283 bundle. add_resource_overriding ( uucore_resource) ;
263284 }
264285
265286 // Checksum algorithms need locale messages from checksum_common
266287 if util_name. ends_with ( "sum" ) {
267288 if let Some ( uucore_content) = get_embedded_locale ( "checksum_common/en-US.ftl" ) {
268- let uucore_resource = parse_fluent_resource ( uucore_content) ?;
289+ let uucore_resource = parse_fluent_resource ( uucore_content, & CHECKSUM_FLUENT ) ?;
269290 bundle. add_resource_overriding ( uucore_resource) ;
270291 }
271292 }
272293
273294 // Then, try to load utility-specific strings
274295 let locale_key = format ! ( "{util_name}/en-US.ftl" ) ;
275296 if let Some ( ftl_content) = get_embedded_locale ( & locale_key) {
276- let resource = parse_fluent_resource ( ftl_content) ?;
297+ let resource = parse_fluent_resource ( ftl_content, & UTIL_FLUENT ) ?;
277298 bundle. add_resource_overriding ( resource) ;
278299 }
279300
@@ -428,7 +449,8 @@ pub fn setup_localization(p: &str) -> Result<(), LocalizationError> {
428449 // No locales directory found, use embedded English with common strings directly
429450 let default_locale = LanguageIdentifier :: from_str ( DEFAULT_LOCALE )
430451 . expect ( "Default locale should always be valid" ) ;
431- let english_bundle = create_english_bundle_from_embedded ( & default_locale, p) ?;
452+ let english_bundle: FluentBundle < & ' static FluentResource > =
453+ create_english_bundle_from_embedded ( & default_locale, p) ?;
432454 let localizer = Localizer :: new ( english_bundle) ;
433455
434456 LOCALIZER . with ( |lock| {
@@ -593,14 +615,15 @@ mod tests {
593615 fn create_test_bundle (
594616 locale : & LanguageIdentifier ,
595617 test_locales_dir : & Path ,
596- ) -> Result < FluentBundle < FluentResource > , LocalizationError > {
597- let mut bundle = FluentBundle :: new ( vec ! [ locale. clone( ) ] ) ;
618+ ) -> Result < FluentBundle < & ' static FluentResource > , LocalizationError > {
619+ let mut bundle: FluentBundle < & ' static FluentResource > =
620+ FluentBundle :: new ( vec ! [ locale. clone( ) ] ) ;
598621 bundle. set_use_isolating ( false ) ;
599622
600623 // Only load from the test directory - no common strings or utility-specific paths
601624 let locale_path = test_locales_dir. join ( format ! ( "{locale}.ftl" ) ) ;
602625 if let Ok ( ftl_content) = fs:: read_to_string ( & locale_path) {
603- let resource = parse_fluent_resource ( & ftl_content) ?;
626+ let resource = parse_fluent_resource ( & ftl_content, & UUCORE_FLUENT ) ?;
604627 bundle. add_resource_overriding ( resource) ;
605628 return Ok ( bundle) ;
606629 }
@@ -763,7 +786,7 @@ invalid-syntax = This is { $missing
763786 #[ test]
764787 fn test_localizer_format_primary_bundle ( ) {
765788 let temp_dir = create_test_locales_dir ( ) ;
766- let en_bundle = create_test_bundle (
789+ let en_bundle: FluentBundle < & ' static FluentResource > = create_test_bundle (
767790 & LanguageIdentifier :: from_str ( "en-US" ) . unwrap ( ) ,
768791 temp_dir. path ( ) ,
769792 )
0 commit comments