I18n: Translate messages in extensions, operator descriptions, Node Wrangler

- Operator descriptions use tip_() since they will be displayed in
  tooltips.

- Extension messages:
  - Split "(Add-on|Theme) \"{:s}\" already installed!" into two
    messages.
  - Use rpt_() to translate error messages.

- Restore core add-on name and description translation.

- Use DATA_ to translate paint material slot name, so that translation
  happens only if the user enabled it for user-created data.

- Node Wrangler contains functions used to build operators' poll
  methods. This change allows them to be properly translated by using
  str.format() instead of f-strings, and explicit extraction with
  tip_().

Pull Request: https://projects.blender.org/blender/blender/pulls/123795
This commit is contained in:
Damien Picard 2024-07-04 10:49:52 +02:00 committed by Bastien Montagne
parent 2aee84a611
commit 6a52c76a65
6 changed files with 60 additions and 49 deletions

@ -34,6 +34,7 @@ from bpy.props import (
)
from bpy.app.translations import (
pgettext_iface as iface_,
pgettext_tip as tip_,
pgettext_rpt as rpt_,
)
@ -1399,7 +1400,7 @@ class EXTENSIONS_OT_repo_sync_all(Operator, _ExtCmdMixIn):
@classmethod
def description(cls, _context, props):
if props.use_active_only:
return "Refresh the list of extensions for the active repository"
return tip_("Refresh the list of extensions for the active repository")
return "" # Default.
def exec_command_iter(self, is_modal):
@ -1596,7 +1597,7 @@ class EXTENSIONS_OT_package_upgrade_all(Operator, _ExtCmdMixIn):
@classmethod
def description(cls, _context, props):
if props.use_active_only:
return "Upgrade all the extensions to their latest version for the active repository"
return tip_("Upgrade all the extensions to their latest version for the active repository")
return "" # Default.
def exec_command_iter(self, is_modal):
@ -2812,15 +2813,16 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn):
return False
if item_local is not None:
if item_local.type == "add-on":
message = rpt_("Add-on \"{:s}\" already installed!")
elif item_local.type == "theme":
message = rpt_("Theme \"{:s}\" already installed!")
else:
assert False, "Unreachable"
self._draw_override = (
self._draw_override_errors,
{
"errors": [
iface_("{:s} \"{:s}\" already installed!").format(
iface_(string.capwords(item_local.type)),
item_local.name,
)
]
"errors": [message.format(item_local.name)]
}
)
return False
@ -2989,7 +2991,7 @@ class EXTENSIONS_OT_package_uninstall_system(Operator):
@classmethod
def description(cls, _context, _props):
return EXTENSIONS_OT_package_uninstall.__doc__
return tip_(EXTENSIONS_OT_package_uninstall.__doc__)
def execute(self, _context):
return {'CANCELLED'}
@ -3448,18 +3450,18 @@ class EXTENSIONS_OT_userpref_allow_online_popup(Operator):
col = layout.column()
if bpy.app.online_access_override:
lines = (
"Online access required to install or update.",
rpt_("Online access required to install or update."),
"",
"Launch Blender without --offline-mode"
rpt_("Launch Blender without --offline-mode"),
)
else:
lines = (
"Please turn Online Access on the System settings.",
rpt_("Please turn Online Access on the System settings."),
"",
"Internet access is required to install extensions from the internet."
rpt_("Internet access is required to install extensions from the internet."),
)
for line in lines:
col.label(text=line)
col.label(text=line, translate=False)
class EXTENSIONS_OT_package_enable_not_installed(Operator):

@ -225,7 +225,7 @@ def addon_draw_item_expanded(
item_tracker_url, # `str`
):
from bpy.app.translations import (
pgettext_iface as iface_,
contexts as i18n_contexts,
)
split = layout.split(factor=0.8)
@ -242,7 +242,7 @@ def addon_draw_item_expanded(
rowsub.alignment = 'RIGHT'
if addon_type == ADDON_TYPE_LEGACY_CORE:
rowsub.active = False
rowsub.label(text=iface_("Built-in"))
rowsub.label(text="Built-in")
rowsub.separator()
elif addon_type == ADDON_TYPE_LEGACY_USER:
rowsub.operator("preferences.addon_remove", text="Uninstall").module = mod.__name__
@ -268,7 +268,7 @@ def addon_draw_item_expanded(
# Only add "Report a Bug" button if tracker_url is set
# or the add-on is bundled (use official tracker then).
if item_tracker_url or (addon_type == ADDON_TYPE_LEGACY_CORE):
col_a.label(text="Feedback")
col_a.label(text="Feedback", text_ctxt=i18n_contexts.editor_preferences)
if item_tracker_url:
col_b.split(factor=0.5).operator(
"wm.url_open", text="Report a Bug", icon='URL',
@ -1065,6 +1065,8 @@ def extensions_panel_draw_online_extensions_request_impl(
self,
_context,
):
from bpy.app.translations import pgettext_rpt as rpt_
layout = self.layout
layout_header, layout_panel = layout.panel("advanced", default_closed=False)
layout_header.label(text="Online Extensions")
@ -1076,10 +1078,10 @@ def extensions_panel_draw_online_extensions_request_impl(
# Text wrapping isn't supported, manually wrap.
for line in (
"Internet access is required to install and update online extensions. ",
"You can adjust this later from \"System\" preferences.",
rpt_("Internet access is required to install and update online extensions. "),
rpt_("You can adjust this later from \"System\" preferences."),
):
box.label(text=line)
box.label(text=line, translate=False)
row = box.row(align=True)
row.alignment = 'LEFT'

@ -5,6 +5,7 @@
import bpy
from bpy_extras.node_utils import connect_sockets
from math import hypot, inf
from bpy.app.translations import pgettext_tip as tip_
def force_update(context):
@ -214,12 +215,15 @@ def nw_check_selected(cls, context, min=1, max=inf):
num_selected = len(context.selected_nodes)
if num_selected < min:
if min > 1:
cls.poll_message_set(f"At least {min} nodes must be selected.")
poll_message = tip_("At least {:s} nodes must be selected.").format(min)
else:
cls.poll_message_set(f"At least {min} node must be selected.")
poll_message = tip_("At least one node must be selected.")
cls.poll_message_set(poll_message)
return False
if num_selected > max:
cls.poll_message_set(f"{num_selected} nodes are selected, but this operator can only work on {max}.")
poll_message = tip_("{:s} nodes are selected, but this operator can only work on {:s}.").format(
num_selected, max)
cls.poll_message_set(poll_message)
return False
return True
@ -227,18 +231,21 @@ def nw_check_selected(cls, context, min=1, max=inf):
def nw_check_space_type(cls, context, types):
if context.space_data.tree_type not in types:
tree_types_str = ", ".join(t.split('NodeTree')[0].lower() for t in sorted(types))
cls.poll_message_set("Current node tree type not supported.\n"
"Should be one of " + tree_types_str + ".")
poll_message = tip_("Current node tree type not supported.\n"
"Should be one of {:s}.").format(tree_types_str)
cls.poll_message_set(poll_message)
return False
return True
def nw_check_node_type(cls, context, type, invert=False):
if invert and context.active_node.type == type:
cls.poll_message_set(f"Active node should be not of type {type}.")
poll_message = tip_("Active node should not be of type {:s}.").format(type)
cls.poll_message_set(poll_message)
return False
elif not invert and context.active_node.type != type:
cls.poll_message_set(f"Active node should be of type {type}.")
poll_message = tip_("Active node should be of type {:s}.").format(type)
cls.poll_message_set(poll_message)
return False
return True

@ -377,8 +377,8 @@ class POSELIB_OT_pose_asset_select_bones(PoseAssetUser, Operator):
@classmethod
def description(cls, _context: Context, properties: 'POSELIB_OT_pose_asset_select_bones') -> str:
if properties.select:
return cls.bl_description
return cls.bl_description.replace("Select", "Deselect")
return tip_(cls.bl_description)
return tip_("Deselect those bones that are used in this pose")
class POSELIB_OT_convert_old_poselib(Operator):

@ -207,11 +207,11 @@ def description_from_data_path(base, data_path, *, prefix, value=Ellipsis):
if (
(rna_prop := context_path_to_rna_property(base, data_path)) and
(description := iface_(rna_prop.description))
(description := tip_(rna_prop.description))
):
description = iface_("{:s}: {:s}").format(prefix, description)
description = tip_("{:s}: {:s}").format(prefix, description)
if value != Ellipsis:
description = "{:s}\n{:s}: {:s}".format(description, iface_("Value"), str(value))
description = "{:s}\n{:s}: {:s}".format(description, tip_("Value"), str(value))
return description
return None
@ -288,7 +288,7 @@ class WM_OT_context_set_boolean(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value)
return description_from_data_path(context, props.data_path, prefix=tip_("Assign"), value=props.value)
execute = execute_context_assign
@ -309,7 +309,7 @@ class WM_OT_context_set_int(Operator): # same as enum
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value)
return description_from_data_path(context, props.data_path, prefix=tip_("Assign"), value=props.value)
execute = execute_context_assign
@ -329,7 +329,7 @@ class WM_OT_context_scale_float(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Scale"), value=props.value)
return description_from_data_path(context, props.data_path, prefix=tip_("Scale"), value=props.value)
def execute(self, context):
data_path = self.data_path
@ -367,7 +367,7 @@ class WM_OT_context_scale_int(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Scale"), value=props.value)
return description_from_data_path(context, props.data_path, prefix=tip_("Scale"), value=props.value)
def execute(self, context):
data_path = self.data_path
@ -411,7 +411,7 @@ class WM_OT_context_set_float(Operator): # same as enum
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value)
return description_from_data_path(context, props.data_path, prefix=tip_("Assign"), value=props.value)
execute = execute_context_assign
@ -431,7 +431,7 @@ class WM_OT_context_set_string(Operator): # same as enum
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value)
return description_from_data_path(context, props.data_path, prefix=tip_("Assign"), value=props.value)
execute = execute_context_assign
@ -451,7 +451,7 @@ class WM_OT_context_set_enum(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value)
return description_from_data_path(context, props.data_path, prefix=tip_("Assign"), value=props.value)
execute = execute_context_assign
@ -471,7 +471,7 @@ class WM_OT_context_set_value(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Assign"), value=props.value)
return description_from_data_path(context, props.data_path, prefix=tip_("Assign"), value=props.value)
def execute(self, context):
data_path = self.data_path
@ -495,7 +495,7 @@ class WM_OT_context_toggle(Operator):
# Currently unsupported, it might be possible to extract this.
if props.module:
return None
return description_from_data_path(context, props.data_path, prefix=iface_("Toggle"))
return description_from_data_path(context, props.data_path, prefix=tip_("Toggle"))
def execute(self, context):
data_path = self.data_path
@ -536,7 +536,7 @@ class WM_OT_context_toggle_enum(Operator):
@classmethod
def description(cls, context, props):
value = "({!r}, {!r})".format(props.value_1, props.value_2)
return description_from_data_path(context, props.data_path, prefix=iface_("Toggle"), value=value)
return description_from_data_path(context, props.data_path, prefix=tip_("Toggle"), value=value)
def execute(self, context):
data_path = self.data_path
@ -575,7 +575,7 @@ class WM_OT_context_cycle_int(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Cycle"))
return description_from_data_path(context, props.data_path, prefix=tip_("Cycle"))
def execute(self, context):
data_path = self.data_path
@ -615,7 +615,7 @@ class WM_OT_context_cycle_enum(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Cycle"))
return description_from_data_path(context, props.data_path, prefix=tip_("Cycle"))
def execute(self, context):
data_path = self.data_path
@ -664,7 +664,7 @@ class WM_OT_context_cycle_array(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Cycle"))
return description_from_data_path(context, props.data_path, prefix=tip_("Cycle"))
def execute(self, context):
data_path = self.data_path
@ -693,7 +693,7 @@ class WM_OT_context_menu_enum(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Menu"))
return description_from_data_path(context, props.data_path, prefix=tip_("Menu"))
def execute(self, context):
data_path = self.data_path
@ -724,7 +724,7 @@ class WM_OT_context_pie_enum(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Pie Menu"))
return description_from_data_path(context, props.data_path, prefix=tip_("Pie Menu"))
def invoke(self, context, event):
wm = context.window_manager
@ -765,7 +765,7 @@ class WM_OT_operator_pie_enum(Operator):
@classmethod
def description(cls, context, props):
return description_from_data_path(context, props.data_path, prefix=iface_("Pie Menu"))
return description_from_data_path(context, props.data_path, prefix=tip_("Pie Menu"))
def invoke(self, context, event):
wm = context.window_manager

@ -6814,7 +6814,7 @@ static void get_default_texture_layer_name_for_object(Object *ob,
{
Material *ma = BKE_object_material_get(ob, ob->actcol);
const char *base_name = ma ? &ma->id.name[2] : &ob->id.name[2];
BLI_snprintf(dst, dst_maxncpy, "%s %s", base_name, layer_type_items[texture_type].name);
BLI_snprintf(dst, dst_maxncpy, "%s %s", base_name, DATA_(layer_type_items[texture_type].name));
}
static int texture_paint_add_texture_paint_slot_invoke(bContext *C,