Skip to content

Commit 7996f12

Browse files
author
Zibi Braniecki
committed
Migrate resolver to use copy-free parser
This is a massive rewrite of the whole crate. It separates out three crates: fluent-syntax becomes a zero-copy AST+parser(+serialized in the future) compabile with Fluent 0.8 fluent-bundle becomes aligned with fluent-bundle in JS fluent becomes a top-level crate with added simple ResourceManager. All of the code is compatible with current stable Rust 2018.
1 parent 3d69f32 commit 7996f12

File tree

104 files changed

+3845
-1371
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+3845
-1371
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/target/
2+
*/target/
23
**/*.rs.bk
34
Cargo.lock

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
[workspace]
22
members = [
3+
"fluent-syntax",
4+
"fluent-bundle",
5+
"fluent-cli",
36
"fluent",
4-
"fluent-syntax"
57
]
File renamed without changes.

fluent-bundle/Cargo.toml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[package]
2+
name = "fluent-bundle"
3+
description = """
4+
A localization system designed to unleash the entire expressive power of
5+
natural language translations.
6+
"""
7+
version = "0.4.3"
8+
edition = "2018"
9+
authors = [
10+
"Zibi Braniecki <[email protected]>",
11+
"Staś Małolepszy <[email protected]>"
12+
]
13+
homepage = "http://www.projectfluent.org"
14+
license = "Apache-2.0/MIT"
15+
repository = "https://github.com/projectfluent/fluent-rs"
16+
readme = "README.md"
17+
keywords = ["localization", "l10n", "i18n", "intl", "internationalization"]
18+
categories = ["localization", "internationalization"]
19+
20+
[dependencies]
21+
fluent-locale = "^0.4.1"
22+
fluent-syntax = { path = "../fluent-syntax" }
23+
failure = "0.1"
24+
failure_derive = "0.1"
25+
intl_pluralrules = "1.0"

fluent/README.md renamed to fluent-bundle/README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ Usage
2222
-----
2323

2424
```rust
25-
use fluent::bundle::FluentBundle;
25+
extern crate fluent;
26+
27+
use fluent_bundle::FluentBundle;
2628

2729
fn main() {
2830
let mut bundle = FluentBundle::new(&["en-US"]);

fluent/benches/lib.rs renamed to fluent-bundle/benches/lib.rs

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
#![feature(test)]
22

3-
extern crate fluent;
4-
extern crate fluent_syntax;
53
extern crate test;
64

7-
use fluent::bundle::FluentBundle;
5+
use fluent_bundle::bundle::FluentBundle;
86
use fluent_syntax::{ast, parser::parse};
97
use std::fs::File;
108
use std::io;
119
use std::io::Read;
1210
use test::Bencher;
1311

1412
fn read_file(path: &str) -> Result<String, io::Error> {
15-
let mut f = try!(File::open(path));
13+
let mut f = File::open(path)?;
1614
let mut s = String::new();
17-
try!(f.read_to_string(&mut s));
15+
f.read_to_string(&mut s)?;
1816
Ok(s)
1917
}
2018

@@ -27,7 +25,9 @@ fn bench_simple_format(b: &mut Bencher) {
2725

2826
for entry in resource.body {
2927
match entry {
30-
ast::Entry::Message(ast::Message { id, .. }) => ids.push(id.name),
28+
ast::ResourceEntry::Entry(ast::Entry::Message(ast::Message { id, .. })) => {
29+
ids.push(id.name)
30+
}
3131
_ => continue,
3232
};
3333
}
@@ -37,7 +37,7 @@ fn bench_simple_format(b: &mut Bencher) {
3737

3838
b.iter(|| {
3939
for id in &ids {
40-
bundle.format(id.as_str(), None);
40+
bundle.format(id, None);
4141
}
4242
});
4343
}
@@ -51,7 +51,9 @@ fn bench_menubar_format(b: &mut Bencher) {
5151

5252
for entry in resource.body {
5353
match entry {
54-
ast::Entry::Message(ast::Message { id, .. }) => ids.push(id.name),
54+
ast::ResourceEntry::Entry(ast::Entry::Message(ast::Message { id, .. })) => {
55+
ids.push(id.name)
56+
}
5557
_ => continue,
5658
};
5759
}
@@ -66,7 +68,7 @@ fn bench_menubar_format(b: &mut Bencher) {
6668
// widgets may only expect attributes and they shouldn't be forced to display a value.
6769
// Here however it doesn't matter because we know for certain that the message for `id`
6870
// exists.
69-
bundle.format_message(id.as_str(), None);
71+
bundle.format_message(id, None);
7072
}
7173
});
7274
}
File renamed without changes.
File renamed without changes.
File renamed without changes.

fluent/examples/external_arguments.rs renamed to fluent-bundle/examples/external_arguments.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
extern crate fluent;
2-
3-
use fluent::bundle::FluentBundle;
4-
use fluent::types::FluentValue;
1+
use fluent_bundle::bundle::FluentBundle;
2+
use fluent_bundle::types::FluentValue;
53
use std::collections::HashMap;
64

75
fn main() {

fluent/examples/functions.rs renamed to fluent-bundle/examples/functions.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
extern crate fluent;
2-
3-
use fluent::bundle::FluentBundle;
4-
use fluent::types::FluentValue;
1+
use fluent_bundle::bundle::FluentBundle;
2+
use fluent_bundle::types::FluentValue;
53

64
fn main() {
75
let mut bundle = FluentBundle::new(&["en-US"]);

fluent/examples/hello.rs renamed to fluent-bundle/examples/hello.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
extern crate fluent;
2-
3-
use fluent::bundle::FluentBundle;
1+
use fluent_bundle::bundle::FluentBundle;
42

53
fn main() {
64
let mut bundle = FluentBundle::new(&["en-US"]);

fluent/examples/message_reference.rs renamed to fluent-bundle/examples/message_reference.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
extern crate fluent;
2-
3-
use fluent::bundle::FluentBundle;
1+
use fluent_bundle::bundle::FluentBundle;
42

53
fn main() {
64
let mut bundle = FluentBundle::new(&["x-testing"]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
missing-arg-error = Error: Please provide a number as argument.
2+
input-parse-error = Error: Could not parse input `{ $input }`. Reason: { $reason }
3+
response-msg =
4+
{ $value ->
5+
[one] "{ $input }" has one Collatz step.
6+
*[other] "{ $input }" has { $value } Collatz steps.
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
missing-arg-error = Błąd: Proszę wprowadzić liczbę jako argument.
2+
input-parse-error = Błąd: Nie udało się sparsować `{ $input }`. Powód: { $reason }
3+
response-msg =
4+
{ $value ->
5+
[one] "{ $input }" ma jeden krok Collatza.
6+
[few] "{ $input }" ma { $value } kroki Collatza.
7+
*[many] "{ $input }" ma { $value } kroków Collatza.
8+
}

fluent/examples/selector.rs renamed to fluent-bundle/examples/selector.rs

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
extern crate fluent;
2-
3-
use fluent::bundle::FluentBundle;
4-
use fluent::types::FluentValue;
1+
use fluent_bundle::bundle::FluentBundle;
2+
use fluent_bundle::types::FluentValue;
53
use std::collections::HashMap;
64

75
fn main() {

fluent-bundle/examples/simple-app.rs

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//! This is an example of a simple application
2+
//! which calculates the Collatz conjecture.
3+
//!
4+
//! The function itself is trivial on purpose,
5+
//! so that we can focus on understanding how
6+
//! the application can be made localizable
7+
//! via Fluent.
8+
//!
9+
//! To try the app launch `cargo run --example simple NUM (LOCALES)`
10+
//!
11+
//! NUM is a number to be calculated, and LOCALES is an optional
12+
//! parameter with a comma-separated list of locales requested by the user.
13+
//!
14+
//! Example:
15+
//!
16+
//! caron run --example simple 123 de,pl
17+
//!
18+
//! If the second argument is omitted, `en-US` locale is used as the
19+
//! default one.
20+
use fluent_bundle::bundle::FluentBundle;
21+
use fluent_bundle::types::FluentValue;
22+
use fluent_locale::{negotiate_languages, NegotiationStrategy};
23+
use std::collections::HashMap;
24+
use std::env;
25+
use std::fs;
26+
use std::fs::File;
27+
use std::io;
28+
use std::io::prelude::*;
29+
use std::str::FromStr;
30+
31+
/// We need a generic file read helper function to
32+
/// read the localization resource file.
33+
///
34+
/// The resource files are stored in
35+
/// `./examples/resources/{locale}` directory.
36+
fn read_file(path: &str) -> Result<String, io::Error> {
37+
let mut f = File::open(path)?;
38+
let mut s = String::new();
39+
f.read_to_string(&mut s)?;
40+
Ok(s)
41+
}
42+
43+
/// This helper function allows us to read the list
44+
/// of available locales by reading the list of
45+
/// directories in `./examples/resources`.
46+
///
47+
/// It is expected that every directory inside it
48+
/// has a name that is a valid BCP47 language tag.
49+
fn get_available_locales() -> Result<Vec<String>, io::Error> {
50+
let mut locales = vec![];
51+
52+
let res_dir = fs::read_dir("./examples/resources/")?;
53+
for entry in res_dir {
54+
if let Ok(entry) = entry {
55+
let path = entry.path();
56+
if path.is_dir() {
57+
if let Some(name) = path.file_name() {
58+
if let Some(name) = name.to_str() {
59+
locales.push(String::from(name));
60+
}
61+
}
62+
}
63+
}
64+
}
65+
return Ok(locales);
66+
}
67+
68+
/// This function negotiates the locales between available
69+
/// and requested by the user.
70+
///
71+
/// It uses `fluent-locale` library but one could
72+
/// use any other that will resolve the list of
73+
/// available locales based on the list of
74+
/// requested locales.
75+
fn get_app_locales(requested: &[&str]) -> Result<Vec<String>, io::Error> {
76+
let available = get_available_locales()?;
77+
let resolved_locales = negotiate_languages(
78+
requested,
79+
&available,
80+
Some("en-US"),
81+
&NegotiationStrategy::Filtering,
82+
);
83+
return Ok(resolved_locales
84+
.into_iter()
85+
.map(|s| String::from(s))
86+
.collect::<Vec<String>>());
87+
}
88+
89+
static L10N_RESOURCES: &[&str] = &["simple.ftl"];
90+
91+
fn main() {
92+
// 1. Get the command line arguments.
93+
let args: Vec<String> = env::args().collect();
94+
let mut sources: Vec<String> = vec![];
95+
96+
// 2. If the argument length is more than 1,
97+
// take the second argument as a comma-separated
98+
// list of requested locales.
99+
//
100+
// Otherwise, take ["en-US"] as the default.
101+
let requested = args
102+
.get(2)
103+
.map_or(vec!["en-US"], |arg| arg.split(",").collect());
104+
105+
// 3. Negotiate it against the avialable ones
106+
let locales = get_app_locales(&requested).expect("Failed to retrieve available locales");
107+
108+
// 4. Create a new Fluent FluentBundle using the
109+
// resolved locales.
110+
let mut bundle = FluentBundle::new(&locales);
111+
112+
// 5. Load the localization resource
113+
for path in L10N_RESOURCES {
114+
let full_path = format!(
115+
"./examples/resources/{locale}/{path}",
116+
locale = locales[0],
117+
path = path
118+
);
119+
let source = read_file(&full_path).unwrap();
120+
sources.push(source);
121+
}
122+
123+
for source in &sources {
124+
bundle.add_messages(&source).unwrap();
125+
}
126+
127+
// 6. Check if the input is provided.
128+
match args.get(1) {
129+
Some(input) => {
130+
// 6.1. Cast it to a number.
131+
match isize::from_str(&input) {
132+
Ok(i) => {
133+
// 6.2. Construct a map of arguments
134+
// to format the message.
135+
let mut args = HashMap::new();
136+
args.insert("input", FluentValue::from(i));
137+
args.insert("value", FluentValue::from(collatz(i)));
138+
// 6.3. Format the message.
139+
println!("{}", bundle.format("response-msg", Some(&args)).unwrap().0);
140+
}
141+
Err(err) => {
142+
let mut args = HashMap::new();
143+
args.insert("input", FluentValue::from(input.to_string()));
144+
args.insert("reason", FluentValue::from(err.to_string()));
145+
println!(
146+
"{}",
147+
bundle
148+
.format("input-parse-error-msg", Some(&args))
149+
.unwrap()
150+
.0
151+
);
152+
}
153+
}
154+
}
155+
None => {
156+
println!("{}", bundle.format("missing-arg-error", None).unwrap().0);
157+
}
158+
}
159+
}
160+
161+
/// Collatz conjecture calculating function.
162+
fn collatz(n: isize) -> isize {
163+
match n {
164+
1 => 0,
165+
_ => match n % 2 {
166+
0 => 1 + collatz(n / 2),
167+
_ => 1 + collatz(n * 3 + 1),
168+
},
169+
}
170+
}

0 commit comments

Comments
 (0)