[Spice-devel] [PATCH 12/22] Add exception handling classes
Christophe de Dinechin
cdupontd at redhat.com
Wed Mar 28 12:44:56 UTC 2018
> On 28 Mar 2018, at 13:12, Christophe Fergeau <cfergeau at redhat.com> wrote:
>
>> Do you have something against requiring string constants in English for our error messages?
>>
>> Is it so difficult to understand that if I have:
>>
>> WriteError(“can’t write”, “header”, filename)
>> WriteError(“can’t write”, “boson”, filename)
>>
>> I can localize all the combinations “can’t write header”, “can’t write boson” and ignore the filename in the localization process, whereas as soon as I have:
>
> You apparently suggest translating "can't write",
> "boson" and "header" separately, and to concatenate these translated
> bits together to get a translated "can't write boson" or "can't write
> header" translated string. I'm afraid this does not work like this, you
> need the full string to do the translation "can't write boson", "can't
> write header", and translate each separately.
Funny, I vaguely remember we had this discussion very recently. But not on list. So here we go again…
Consider a FileWriteError. In my proposal, the actual prototype has a C constant message, an C constant operation, and a variable part related to errno. For the sake of example, let me add an hypothetical filename. So let’s say we have:
- “write failed” and “write interrupted” as possible values for message
- “format”, “frame” and “header” as possible values for operation
- anything as errno (dynamic)
- anything as filename (dynamic)
At the moment, all filenames we get have the same lifetime as the program (e.g. they come from argv[n] or compile-time constants. But let’s imagine, for the sake of clarifying the previous discussion, that the filename can actually be constructed dynamically for example by concatenating a directory and some user input. In that case, the proper interface for FileWriteError would be:
FileWriteError(const char *message, const char *operation, int errno, std::string filename);
Yes, you read that right. There would be an std::string here for the dynamic filename. Because that part is dynamic, and its lifetime cannot be managed statically, so there is a good reason to pay the price.
The complete English message we might want can be something like : “write failed writing header for file ‘Hello.txt’: no space left on device (28)”.
In my proposal, that message is built in format_message with something like:
int written = snprintf(buffer, size, "%s writing %s for file ‘%s'", what(), operation, filename.c_str());
return append_strerror(buffer, size, written);
A first rough translation in French would translate pieces individually. That’s what Christophe says I’m suggesting. I’m not, but for the sake of the argument, let’s explore that. So we would have:
- “l’écriture a échoué” et “l’écriture a été interrompue” for individually translated messages
- “le format”, “une image” et “l’en tête” for individually translated operations
- “%s pendant %s pour le fichier ‘%s’” as a translation for the format string.
The translation could be achieved using:
int written = snprintf(buffer, size, _("%s writing %s for file ‘%s’”), _(what()), _(operation), filename.c_str());
return append_strerror(buffer, size, written);
This naive approach yields “l’écriture a été interrompue pendant le format pour le fichier ‘Hello.txt'”, which is understandable, but sounds a bit strange. It would be better to instead generate “L’écriture du format a été interrompue pour le fichier ‘Hello.txt’”; which involves word reordering and grammatical pairing (e.g. contractions, male/female, etc). In order to do that, you can take a more subtle approach where instead of translating pieces individually, you translate the complete message, but still carefully ignore the parts that you know are variable. So you could do instead:
snprintf(translated, sizeof(translated), “%s writing %s for file ‘%%s’”, what(), operation);
int written = snprintf(buffer, size, _(translated), filename.c_str());
return append_strerror(buffer, size, written);
In that case, obviously, you need to provide more translations:
- “write failed writing header for file ‘%s'” -> “l’écriture de l’en-tête du fichier ‘%s’ a échoué"
- “write failed writing frame for file ‘%s'” -> “l’écriture d’une image du fichier ‘%s' a échoué"
- “write interrupted writing frame for file ‘%s'” -> “l’écriture d’une image du fichier ‘%s’ a été interrompue”
… etc
What matters, however, is that you still have a finite number of combinations, because you have a finite number of input strings. And that guarantee is only valid as long as you can tell which parts are constant and which parts are variable.
What matters even more is that if in the interface of my Error-derived classes, I have not carefully separated the constant and the variable parts, I cannot make an exhaustive list of all possible translations. Therefore, it is important for the interface of Error to “strongly suggest” constant strings where I want constant strings. By the mean of a “const char *”. Where I expect a dynamic string, I use std::string (or const std::string & or whatever floats you boat).
>
>> WriteError(“can’t write header” + filename)
>>
>> it suddenly becomes impossible to do localization?
>
> so WriteError("can't write header" + filename) is actually *better* from
> a translation point of view, as you'd mark "can't write header" for
> translation and be done with it.
This would not work in German, where you’d want to have the file name somewhere in the middle, as in “Beim Schreiben der Kopfzeile für die Datei ‘Hello.txt' ist ein Fehler aufgetreten”…
Also, in our code, “header” is actually coming from a different part, which was the reason for introducing the “operation” argument. The simplification you are talking about really comes from having ignored that detail in my example. If you don’t ignore it, then the alternative is:
WriteError( “can’t write “ + operation + “ for “ + filename);
and your “better” argument, which was already slashed in microscopic fragments by the German counter example, becomes all the more difficult to reassemble.
https://www.smashingmagazine.com/2012/07/12-commandments-software-localization/ #2: “Never concatenate strings”
Regards
Christophe Christophe Christophe Christophe (since I have to repeat myself quite a lot, I thought I’d do it in the signature too ;-)
>
> Christophe
More information about the Spice-devel
mailing list