6 changed files with 183 additions and 29 deletions
-
6Cargo.toml
-
61README.md
-
7gettext-utils/Cargo.toml
-
58gettext-utils/src/lib.rs
-
75src/lib.rs
-
5tests/main.rs
@ -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)
|
|||
}
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue