Page MenuHome

Official add-ons: replace f-strings by str.format() for I18n
ClosedPublic

Authored by Damien Picard (pioverfour) on Aug 3 2022, 11:29 PM.

Details

Summary

Unfortunately, messages cannot be properly extracted from f-strings.
Use str.format() method instead.

Diff Detail

Event Timeline

Damien Picard (pioverfour) requested review of this revision.Aug 3 2022, 11:29 PM
Damien Picard (pioverfour) created this revision.
object_print3d_utils/operators.py
768–772 ↗(On Diff #54362)

This fix is unrelated to I18n, I can split it if needed.

String "[De]Selected bones from {}" wasn’t properly translatable.

Update, applied 3D print toolbox.

Restore whitespace removed by autopep8

@Sybren A. Stüvel (sybren) would be nice to get green light (or not) on this one ;)

Sybren A. Stüvel (sybren) requested changes to this revision.Oct 20 2022, 4:01 PM

There was a discussion about formatting strings on devtalk, where %-formatting came out as preferred over str.format(). Not only does the %-formatting have my preference, it's also already used in the pose library code, so even for consistency I would recommend sticking to that.

pose_library/operators.py
34

The newline here can be removed, the bpy imports can be grouped together.

374–379

In the old code, the conditional expression was small, and only contained what needed to be conditional. The new code duplicates almost all the code there. I don't think this is an acceptable change, if only the formatting method is supposed to be altered here.

This revision now requires changes to proceed.Oct 20 2022, 4:01 PM
Damien Picard (pioverfour) planned changes to this revision.Oct 20 2022, 4:57 PM

Hello, thanks for the review and thanks for the devtalk thread, I wasn’t aware of this.

I’ll make the requested change to printf formatting, but one strong argument in favor of the format() method, is that if the placeholders are named or numbered, word order can be changed. This is sometimes needed to get grammatical sentences in localization.

In this code there is only one placeholder per string though, so no problem.

pose_library/operators.py
374–379

One possible intermediate solution is to do:

self.report({"INFO"}, (tip_("Selected bones from %s") if self.select else
                       tip_("Deselected bones from %s")).format(pose_asset.name))

Would that be all right? Otherwise, here is an explanation of why verbosity is strongly favored over this sort of constructions for internationalization.


Admittedly, this particular instance will almost surely work in all indo-european languages, but what about others? What if a language’s grammar uses different word orders, like:

“Bones selected from <name>”, but “In <name>, bones were deselected”?

Furthermore, what if “Selected” and “Deselected” were already used elsewhere in the code base but in a different context? For instance, to mean “Selected [feminine plural]” or “the <thing> that <is / was / is to be> selected” or “the <thing> has now been selected”? There are 20 usages of “Selected” in Blender, and 6 usages of “Deselected”, and I not all of them have the same nuance. This results in poor, broken translations, a frustrating time for users and a very frustrating for translators and developers trying to disambiguate between various usages (that is, as far as I know, Bastien and I right now).

My point is, we have no way to know for sure what is needed if we only consider the languages we already know about.

A lesser issue is that it makes translators’ work harder, because they have to know the context of the sentence, but most translators are not developers, so it’s good if they don’t have to delve into the code to know what they’re doing.

I’ll still revert if you insist, but using formatting to build sentences is very bad practice for i18n.

  • Removed newline
  • Replaced conditional block with conditional expression
Damien Picard (pioverfour) requested review of this revision.Oct 20 2022, 11:16 PM

I’ll make the requested change to printf formatting, but one strong argument in favor of the format() method, is that if the placeholders are named or numbered, word order can be changed. This is sometimes needed to get grammatical sentences in localization.

That's a good point. I wouldn't mind switching to that (either directly after this patch or when it actually becomes necessary), but then I feel it should be consistently done throughout the add-on, and not just for these few strings.

pose_library/operators.py
374–379

I’ll still revert if you insist, but using formatting to build sentences is very bad practice for i18n.

You make an excellent point, I'm glad to be corrected here :)

I think it would still be nice to keep the construction of the message conditional, and take the call to self.report(...) out of the conditional, something like:

if self.select:
    msg = tip_("Selected bones from %s") % pose_asset.name
else:
    msg = tip_("Deselected bones from %s") % pose_asset.name
self.report({"INFO"}, msg)
Sybren A. Stüvel (sybren) requested changes to this revision.Oct 21 2022, 12:39 PM
This revision now requires changes to proceed.Oct 21 2022, 12:39 PM
  • Used the requested construct for the conditional
  • Realized a few messages hadn’t been handled: I’d been focusing on f-strings, but printf formatting also has an issue. I’ll make a new pass in all official add-ons in a later patch to check what needs to be done.

I agree that consistency is good, at least per-add-on, and that a switch can be done later if the need arises.

I'd like to hear from @Bastien Montagne (mont29) on this: is pgettext_tip() the correct function for translating strings passed to operator.report({'LEVEL'}, message)? From the docs, it seems that pgettext_iface() would be a better choice here, but in other places I can see translated text for the report() function it's indeed using pgettext_tip().

As a confusing counter-example, bl_operators/wm.py seems to use pgettext_iface() for tooltips, and pgettext_tip() for reports.

That’s a fair question, and one that’s tripped me and basically everyone else a lot. I found from T76101#980282 about this comment by @Bastien Montagne (mont29) in rB23df1a774b5b:

Cheap tip: anything that is not "Camel Case" and/or that is more than a few words long should use TIP_ translation, not IFACE_ one.

This is not explicitly stated anywhere in the docs as far as I know, but that’s the current behaviour of report(). If you want to test that yourself, the following script prints the translated version of “Shader AOV” only if the Tooltips translation is enabled, and it uses report() in the operator.

import bpy

class SimpleOperator(bpy.types.Operator):
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"

    def execute(self, context):
        self.report({"INFO"},"Shader AOV")
        return {'FINISHED'}

def register():
    bpy.utils.register_class(SimpleOperator)

if __name__ == "__main__":
    register()
    bpy.ops.object.simple_operator()

By the way, I learned that Python’s printf-style formatting actually does provide a way to use named placeholders:

print('%(language)s has %(number)03d quote types.' %
      {'language': "Python", "number": 2})

I’ve never seen it used and think it looks a bit weird, but with it there is no need to switch to format() for i18n.

Re tip vs iface, main point has already been said, will update the official docs asap with detailed reasoning.

Re format, please do not use any fancy feature like named or positional parameter for now, this would require rather complex updates in i18n scripts. We have to ensure translated messages do not butcher the expected format parts (which happens very often already with the basic ones we currently support, and can easily lead Blender to crash when in C strings).
If we want to go that way, we first need to invest some dev time in our i18n tooling.

Re format, please do not use any fancy feature like named or positional parameter for now

Duly noted, thanks! I wasn’t planning to do that any time soon, just noting that it is possible. If the need ever arises, I’ll make sure to consult and test before using such features. :)

Thanks for making the pose library translatable!

This revision is now accepted and ready to land.Nov 8 2022, 11:31 AM

I messed up the merging from the v3.4 branch to master and instead cherry-picked, but it will merge fine if someone makes a new commit and merges again to master.