@@ -15,24 +15,44 @@ fn read_file(path: &str) -> Result<String, io::Error> {
15
15
fs:: read_to_string ( path)
16
16
}
17
17
18
+ /// [ResourceManager] provides a standalone solution for managing localization resources which
19
+ /// can be used by `fluent-fallback` or other higher level bindings.
18
20
pub struct ResourceManager {
19
21
resources : FrozenMap < String , Box < FluentResource > > ,
20
22
path_scheme : String ,
21
23
}
22
24
23
25
impl 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
+ ///
24
42
pub fn new ( path_scheme : String ) -> Self {
25
43
ResourceManager {
26
44
resources : FrozenMap :: new ( ) ,
27
45
path_scheme,
28
46
}
29
47
}
30
48
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 {
32
52
let path = self
33
53
. path_scheme
34
54
. replace ( "{locale}" , locale)
35
- . replace ( "{res_id}" , res_id ) ;
55
+ . replace ( "{res_id}" , resource_id ) ;
36
56
if let Some ( res) = self . resources . get ( & path) {
37
57
res
38
58
} else {
@@ -45,34 +65,44 @@ impl ResourceManager {
45
65
}
46
66
}
47
67
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.
48
73
pub fn get_bundle (
49
74
& self ,
50
75
locales : Vec < LanguageIdentifier > ,
51
76
resource_ids : Vec < String > ,
52
77
) -> FluentBundle < & FluentResource > {
53
78
let mut bundle = FluentBundle :: new ( locales. clone ( ) ) ;
54
79
for res_id in & resource_ids {
80
+ println ! ( "res_id {:?}" , res_id) ;
55
81
let res = self . get_resource ( res_id, & locales[ 0 ] . to_string ( ) ) ;
56
82
bundle. add_resource ( res) . unwrap ( ) ;
57
83
}
58
84
bundle
59
85
}
60
86
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.
61
91
pub fn get_bundles (
62
92
& self ,
63
93
locales : Vec < LanguageIdentifier > ,
64
94
resource_ids : Vec < String > ,
65
95
) -> impl Iterator < Item = FluentBundle < & FluentResource > > {
66
96
let res_mgr = self ;
67
- let mut ptr = 0 ;
97
+ let mut idx = 0 ;
68
98
69
99
iter:: from_fn ( move || {
70
- locales. get ( ptr ) . map ( |locale| {
71
- ptr += 1 ;
100
+ locales. get ( idx ) . map ( |locale| {
101
+ idx += 1 ;
72
102
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 ( ) ;
76
106
}
77
107
bundle
78
108
} )
@@ -105,6 +135,10 @@ impl Iterator for BundleIter {
105
135
}
106
136
}
107
137
138
+ // TODO - These need to be implemented.
139
+ // https://github.com/projectfluent/fluent-rs/issues/281
140
+
141
+ // coverage(off)
108
142
impl Stream for BundleIter {
109
143
type Item = FluentBundleResult < FluentResource > ;
110
144
@@ -138,3 +172,72 @@ impl BundleGenerator for ResourceManager {
138
172
todo ! ( )
139
173
}
140
174
}
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