Add format args support
parent
617d0c7f7a
commit
7bb2631e5f
@ -0,0 +1,61 @@
|
||||
# Gettext macros
|
||||
|
||||
A few proc-macros to help you internationalize your Rust apps.
|
||||
|
||||
## How does it works?
|
||||
|
||||
There are three macros:
|
||||
|
||||
- `configure_i18n`, that should be in your `build.rs` file. It
|
||||
allows you to configure multiple translation domains, where to store
|
||||
their .pot file and which langage they support.
|
||||
- `init_i18n`, that should be called in your crate root. It tells which
|
||||
domain to use for this crate.
|
||||
- `i18n`, that translates a given message
|
||||
|
||||
The advantage of these macros is that they allow you to work with multiple translation
|
||||
domains (for instance, one for each of your workspace's crate), and that they automatically
|
||||
generate a .pot file for these domains.
|
||||
|
||||
## Example
|
||||
|
||||
*build.rs*
|
||||
|
||||
```rust
|
||||
use gettext_macros::configure_i18n;
|
||||
|
||||
fn main() {
|
||||
// Configure two different translation domains, with different locales
|
||||
configure_i18n!("my_app", ar, en, de, fr, it, ja); // This one will have its translations stored in ./po
|
||||
configure_i18n!("another_domain", "po/other_domain", en, de, ja); // This one in ./po/other_domain
|
||||
}
|
||||
```
|
||||
|
||||
*main.rs*
|
||||
|
||||
```rust
|
||||
// The translations for this module and its submodules will be
|
||||
// loaded from the "my_app" domain.
|
||||
init_i18n!("my_app");
|
||||
|
||||
fn init_catalog() -> gettext::Catalog {
|
||||
// return the correct catalog for the user's language,
|
||||
// usually with another crate.
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let catalog = init_catalog();
|
||||
println!("{}", i18n!(catalog, "Hello, world!"));
|
||||
let name = "Jane";
|
||||
println!("{}", i18n!(catalog, "Hello, {}!"; name));
|
||||
let message_count = 42;
|
||||
println!("{}", i18n!(catalog, "You have one new message", "You have {0} new messages"; message_count));
|
||||
}
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- Code cleanup
|
||||
- Format args checking
|
||||
- Use package name as default domain
|
||||
- Add build functions to gettext-utils
|
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "gettext-utils"
|
||||
version = "0.1.0"
|
||||
authors = ["Baptiste Gelez <baptiste@gelez.xyz>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
@ -0,0 +1,58 @@
|
||||
#[derive(Debug)]
|
||||
#[doc(hidden)]
|
||||
pub enum FormatError {
|
||||
UnmatchedCurlyBracket,
|
||||
InvalidPositionalArgument,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn try_format<'a>(
|
||||
str_pattern: &'a str,
|
||||
argv: &[::std::boxed::Box<dyn ::std::fmt::Display + 'a>],
|
||||
) -> ::std::result::Result<::std::string::String, FormatError> {
|
||||
use ::std::fmt::Write;
|
||||
use ::std::iter::Iterator;
|
||||
|
||||
//first we parse the pattern
|
||||
let mut pattern = vec![];
|
||||
let mut vars = vec![];
|
||||
let mut finish_or_fail = false;
|
||||
for (i, part) in str_pattern.split('}').enumerate() {
|
||||
if finish_or_fail {
|
||||
return ::std::result::Result::Err(FormatError::UnmatchedCurlyBracket);
|
||||
}
|
||||
if part.contains('{') {
|
||||
let mut part = part.split('{');
|
||||
let text = part.next().unwrap();
|
||||
let arg = part.next().ok_or(FormatError::UnmatchedCurlyBracket)?;
|
||||
if part.next() != ::std::option::Option::None {
|
||||
return ::std::result::Result::Err(FormatError::UnmatchedCurlyBracket);
|
||||
}
|
||||
pattern.push(text);
|
||||
vars.push(
|
||||
argv.get::<usize>(if arg.len() > 0 {
|
||||
arg.parse()
|
||||
.map_err(|_| FormatError::InvalidPositionalArgument)?
|
||||
} else {
|
||||
i
|
||||
})
|
||||
.ok_or(FormatError::InvalidPositionalArgument)?,
|
||||
);
|
||||
} else {
|
||||
finish_or_fail = true;
|
||||
pattern.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
//then we generate the result String
|
||||
let mut res = ::std::string::String::with_capacity(str_pattern.len());
|
||||
let mut pattern = pattern.iter();
|
||||
let mut vars = vars.iter();
|
||||
while let ::std::option::Option::Some(text) = pattern.next() {
|
||||
res.write_str(text).unwrap();
|
||||
if let ::std::option::Option::Some(var) = vars.next() {
|
||||
res.write_str(&format!("{}", var)).unwrap();
|
||||
}
|
||||
}
|
||||
::std::result::Result::Ok(res)
|
||||
}
|
Loading…
Reference in New Issue