@@ -15,24 +15,44 @@ fn read_file(path: &str) -> Result<String, io::Error> {
1515 fs:: read_to_string ( path)
1616}
1717
18+ /// [ResourceManager] provides a standalone solution for managing localization resources which
19+ /// can be used by `fluent-fallback` or other higher level bindings.
1820pub struct ResourceManager {
1921 resources : FrozenMap < String , Box < FluentResource > > ,
2022 path_scheme : String ,
2123}
2224
2325impl ResourceManager {
26+ /// Create a new and empty [`ResourceManager`]. As resources are added they will be
27+ /// retained in the `resources` [`FrozenMap`]. The `path_scheme` argument defines
28+ /// how the files are organized.
29+ ///
30+ /// For instance `"./translations/{locale}/{res_id}"` will load files with the
31+ /// following structure:
32+ ///
33+ /// .
34+ /// └── translations
35+ /// ├── en-US
36+ /// │ ├── app.ftl
37+ /// │ └── errors.ftl
38+ /// └── pl
39+ /// ├── app.ftl
40+ /// └── errors.ftl
41+ ///
2442 pub fn new ( path_scheme : String ) -> Self {
2543 ResourceManager {
2644 resources : FrozenMap :: new ( ) ,
2745 path_scheme,
2846 }
2947 }
3048
31- fn get_resource ( & self , res_id : & str , locale : & str ) -> & FluentResource {
49+ /// Returns a [`FluentResource`], by either reading the file and loading it into
50+ /// memory, or retrieving it from an in-memory cache.
51+ fn get_resource ( & self , resource_id : & str , locale : & str ) -> & FluentResource {
3252 let path = self
3353 . path_scheme
3454 . replace ( "{locale}" , locale)
35- . replace ( "{res_id}" , res_id ) ;
55+ . replace ( "{res_id}" , resource_id ) ;
3656 if let Some ( res) = self . resources . get ( & path) {
3757 res
3858 } else {
@@ -45,34 +65,44 @@ impl ResourceManager {
4565 }
4666 }
4767
68+ /// Gets a [`FluentBundle`] from a list of resources. The bundle will only contain the
69+ /// resources from the first locale in the locales list. The other locales will be
70+ /// stored in the [`FluentBundle`] and will only be used for custom formatters such
71+ /// date time format, or plural rules. The message formatting will not fall back
72+ /// to other locales.
4873 pub fn get_bundle (
4974 & self ,
5075 locales : Vec < LanguageIdentifier > ,
5176 resource_ids : Vec < String > ,
5277 ) -> FluentBundle < & FluentResource > {
5378 let mut bundle = FluentBundle :: new ( locales. clone ( ) ) ;
5479 for res_id in & resource_ids {
80+ println ! ( "res_id {:?}" , res_id) ;
5581 let res = self . get_resource ( res_id, & locales[ 0 ] . to_string ( ) ) ;
5682 bundle. add_resource ( res) . unwrap ( ) ;
5783 }
5884 bundle
5985 }
6086
87+ /// Returns an iterator for a [`FluentBundle`] for each locale provided. Each
88+ /// iteration will load all of the resources for that single locale. i18n formatters
89+ /// such as date time format and plural rules will ignore the list of locales,
90+ /// unlike `get_bundle` and only use the single locale of the bundle.
6191 pub fn get_bundles (
6292 & self ,
6393 locales : Vec < LanguageIdentifier > ,
6494 resource_ids : Vec < String > ,
6595 ) -> impl Iterator < Item = FluentBundle < & FluentResource > > {
6696 let res_mgr = self ;
67- let mut ptr = 0 ;
97+ let mut idx = 0 ;
6898
6999 iter:: from_fn ( move || {
70- locales. get ( ptr ) . map ( |locale| {
71- ptr += 1 ;
100+ locales. get ( idx ) . map ( |locale| {
101+ idx += 1 ;
72102 let mut bundle = FluentBundle :: new ( vec ! [ locale. clone( ) ] ) ;
73- for res_id in & resource_ids {
74- let res = res_mgr. get_resource ( res_id , & locale. to_string ( ) ) ;
75- bundle. add_resource ( res ) . unwrap ( ) ;
103+ for resource_id in & resource_ids {
104+ let resource = res_mgr. get_resource ( resource_id , & locale. to_string ( ) ) ;
105+ bundle. add_resource ( resource ) . unwrap ( ) ;
76106 }
77107 bundle
78108 } )
@@ -105,6 +135,10 @@ impl Iterator for BundleIter {
105135 }
106136}
107137
138+ // TODO - These need to be implemented.
139+ // https://github.com/projectfluent/fluent-rs/issues/281
140+
141+ // coverage(off)
108142impl Stream for BundleIter {
109143 type Item = FluentBundleResult < FluentResource > ;
110144
@@ -138,3 +172,72 @@ impl BundleGenerator for ResourceManager {
138172 todo ! ( )
139173 }
140174}
175+ // coverage(on)
176+
177+ #[ cfg( test) ]
178+ mod test {
179+ use super :: * ;
180+ use unic_langid:: langid;
181+
182+ #[ test]
183+ fn caching ( ) {
184+ let res_mgr = ResourceManager :: new ( "./tests/resources/{locale}/{res_id}" . into ( ) ) ;
185+
186+ let _bundle = res_mgr. get_bundle ( vec ! [ langid!( "en-US" ) ] , vec ! [ "test.ftl" . into( ) ] ) ;
187+ let res_1 = res_mgr. get_resource ( "test.ftl" , "en-US" ) ;
188+
189+ let _bundle2 = res_mgr. get_bundle ( vec ! [ langid!( "en-US" ) ] , vec ! [ "test.ftl" . into( ) ] ) ;
190+ let res_2 = res_mgr. get_resource ( "test.ftl" , "en-US" ) ;
191+
192+ assert ! (
193+ std:: ptr:: eq( res_1, res_2) ,
194+ "The resources are cached in memory and reference the same thing."
195+ ) ;
196+ }
197+
198+ // TODO - This API should return a Result instead.
199+ // https://github.com/projectfluent/fluent-rs/issues/278
200+ #[ test]
201+ #[ should_panic]
202+ fn get_resource_error ( ) {
203+ let res_mgr = ResourceManager :: new ( "./tests/resources/{locale}/{res_id}" . into ( ) ) ;
204+
205+ let _bundle = res_mgr. get_bundle ( vec ! [ langid!( "en-US" ) ] , vec ! [ "test.ftl" . into( ) ] ) ;
206+ res_mgr. get_resource ( "nonexistent.ftl" , "en-US" ) ;
207+ }
208+
209+ // TODO - This API should return a Result instead.
210+ // https://github.com/projectfluent/fluent-rs/issues/278
211+ #[ test]
212+ #[ should_panic]
213+ fn get_bundle_error ( ) {
214+ let res_mgr = ResourceManager :: new ( "./tests/resources/{locale}/{res_id}" . into ( ) ) ;
215+ let _bundle = res_mgr. get_bundle ( vec ! [ langid!( "en-US" ) ] , vec ! [ "nonexistent.ftl" . into( ) ] ) ;
216+ }
217+
218+ // TODO - Syntax errors should be surfaced. This test has an invalid resource that
219+ // should fail, but currently isn't.
220+ // https://github.com/projectfluent/fluent-rs/issues/280
221+ #[ test]
222+ fn get_bundle_ignores_errors ( ) {
223+ let res_mgr = ResourceManager :: new ( "./tests/resources/{locale}/{res_id}" . into ( ) ) ;
224+ let bundle = res_mgr. get_bundle (
225+ vec ! [ langid!( "en-US" ) ] ,
226+ vec ! [ "test.ftl" . into( ) , "invalid.ftl" . into( ) ] ,
227+ ) ;
228+
229+ let mut errors = vec ! [ ] ;
230+ let msg = bundle. get_message ( "hello-world" ) . expect ( "Message exists" ) ;
231+ let pattern = msg. value ( ) . expect ( "Message has a value" ) ;
232+ let value = bundle. format_pattern ( & pattern, None , & mut errors) ;
233+ assert_eq ! ( value, "Hello World" ) ;
234+ assert ! ( errors. is_empty( ) ) ;
235+
236+ let mut errors = vec ! [ ] ;
237+ let msg = bundle. get_message ( "valid-message" ) . expect ( "Message exists" ) ;
238+ let pattern = msg. value ( ) . expect ( "Message has a value" ) ;
239+ let value = bundle. format_pattern ( & pattern, None , & mut errors) ;
240+ assert_eq ! ( value, "This is a valid message" ) ;
241+ assert ! ( errors. is_empty( ) ) ;
242+ }
243+ }
0 commit comments