Wouldn’t you like to write your i18n code using gettext like this?
msg = _(t'Hello {name}')
msg = ngettext(t'{n} snake', t'{n} snakes', n)
flash(_(t'Category "{cat.title}" moved to "{target_cat.title}"'))
instead of this?
msg = _('Hello {name}').format(name=name)
msg = ngettext('{n} snake', '{n} snakes', n).format(n=n)
flash(_('Category "{}" moved to "{}"').format(cat.title, target_cat.title))
Because the latter isn’t super pretty to begin with and prone to errors such as calling .format() inside the gettext call instead of outside, or using positional multiple placeholders (and some languages may require a different order).
Now to the tricky part: How to convert the expressions from the t-string interpolation to names that are suitable in format strings and translations.
My proposed way to solve this is to derive a format string field name from the expression, and reject anything that’s too complex or too dynamic. My current implementation covers simple cases such as plain variables, accessing attributes and items, and calling a method w/o arguments:
PS: Maybe an interesting fact: A less powerful version of this exists in Jinja2 templates for a very long time - it lets you use plain variables inside {% trans %} and uses the variable name during extraction, but the value during translation.