gettext-rs
gettext-rs copied to clipboard
Provide macros which perform formatting after calling `gettext`
We have macros that do something like format!(gettext(…), …)
, but at runtime (format!
expects a string literal, so we can't use that). Our macros supported {}
and {n}
, and there was a PR to add escaping ({{
and }}
). That PR uncovered a problem: if the format string is invalid, what should our macros return? How should invalid formats be handled (see #85)?
To find out what user's expectations are, I looked at how people use gettext-rs functions:
- GTK apps (mentioned in #18) use Rust-style formats and assume that the string is correct (i.e. they don't panic and they don't return Result)
- in my own app, Newsboat, I wrap
strnprintf
C function, use printf-style formats, and assume the format string is correct - gstore crate has its own macro. It uses Rust-style formats, and assumes the format string is correct
- tr crate. Rust-style formats, assumes correctness
-
serde_yaml crate uses Rust-style formats, returns
Result
I also looked at how others solve this problem:
- Python's
format
method throwsValueError
if the format string contains imbalanced curly braces - C++
std::vformat
throws too -
runtime-fmt and dynfmt crates return
Result
So it looks like the expected solution would be returning Result
. Some ideas from @delight-aug:
From the perspective of the user of macros, with Result, I could call auto-bug-reporting and display a message to users, that something went wrong. Or I could just unwrap it all the time and let my program crash because of some string. Also, it doesn't look good when you want to include a phrase in GtkLabel, but you need to match it first (or unwrap it in place). Maybe a good decision is to let users specify an action to run on a broken string? So they set up a closure near textdomain bindings. And by default just swallow things...
This, plus the lack of clear winner among the "runtime formattting" crates, makes me think we should postpone this work, and gather more ideas of what users actually need.
If you'd use a macro that does something like format!(gettext(…), …)
, please comment below and explain:
- what format specifiers you expect to use. Just plain
{}
? Positional{1}
? Named{foo}
? How about complex ones like{:3.4x}
? - if the translated string is not a valid format sequence (e.g. the translator made a typo), what do you expect the macro to do? Panic? Communicate the failure via
Result
? Return an empty string? Run some closure that you set up earlier?
I think the first thing to do if a translated string has broken formatting is to use the English (default) string. But what to do if its formating is broken, too?
I'm working on Fractal, a GTK app.
From my point of view, a sane fallback using the default string seems like a good default, although providing a custom closure would be a more generic solution.
But what to do if its formating is broken, too?
The macro should probably check that the formatting of the string is good and fail cargo check
if it doesn't.
From a translator point of view, I believe that named format specifiers are a must have. A specifier with a good name is easier to understand. Also, the translator might need to change the variables' order and it's not doable without it. I do not think that complex specifiers are a must-have.
We are rewriting our app from scratch because of several big changes we needed to make. Our legacy code uses custom i18n methods based on an old version of this crate, to allow us to use named variables. They might be of help. Of course the syntax is not as friendly as a macro would be, and we'd like to avoid having a custom implementation.
The macro should probably check that the formatting of the string is good and fail cargo check if it doesn't.
That's a good idea we missed. This crate is just a bindings library for gettext, so I thought we had to format the String
that actual gettext returns at runtime. But I think, idealistically, nothing stops us from using the str
provided to macro at compile-time to verify that the default string isn't broken (to at least have a working fallback string).
That being said, I'm struggling to think about how to implement it better. Just in case, Is there anything that allows writing custom compile-time checks for macros?
and we'd like to avoid having a custom implementation
And I'll be delighted to be the one to write the shared implementation!
That being said, I'm struggling to think about how to implement it better. Just in case, Is there anything that allows writing custom compile-time checks for macros?
With procedural macros, if it panics, it gives a compile-time error. So you can just use panic!("Error message")
for custom errors.
I have looked at procedural macros, and it seems absolutely possible to create the type of macros you suggested. I'll start working on it after 9th December, the deadline for my coursework. However, I can't say whether it will be implemented in this crate — that's up for @Minoru to decide.