From 6a52c76a655b70b15953d6bc570634e76ad4d750 Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Thu, 4 Jul 2024 10:49:52 +0200 Subject: [PATCH] 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 --- .../addons_core/bl_pkg/bl_extension_ops.py | 30 ++++++++------- scripts/addons_core/bl_pkg/bl_extension_ui.py | 14 ++++--- .../addons_core/node_wrangler/utils/nodes.py | 21 ++++++---- scripts/addons_core/pose_library/operators.py | 4 +- scripts/startup/bl_operators/wm.py | 38 +++++++++---------- .../editors/sculpt_paint/paint_image_proj.cc | 2 +- 6 files changed, 60 insertions(+), 49 deletions(-) diff --git a/scripts/addons_core/bl_pkg/bl_extension_ops.py b/scripts/addons_core/bl_pkg/bl_extension_ops.py index 8c5b026414f..c11c4f0e44e 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_ops.py +++ b/scripts/addons_core/bl_pkg/bl_extension_ops.py @@ -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): diff --git a/scripts/addons_core/bl_pkg/bl_extension_ui.py b/scripts/addons_core/bl_pkg/bl_extension_ui.py index 7a9472f567e..bf62218652f 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_ui.py +++ b/scripts/addons_core/bl_pkg/bl_extension_ui.py @@ -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' diff --git a/scripts/addons_core/node_wrangler/utils/nodes.py b/scripts/addons_core/node_wrangler/utils/nodes.py index 665c10010aa..ef3036a2462 100644 --- a/scripts/addons_core/node_wrangler/utils/nodes.py +++ b/scripts/addons_core/node_wrangler/utils/nodes.py @@ -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 diff --git a/scripts/addons_core/pose_library/operators.py b/scripts/addons_core/pose_library/operators.py index 624a785f11a..4600e40b7aa 100644 --- a/scripts/addons_core/pose_library/operators.py +++ b/scripts/addons_core/pose_library/operators.py @@ -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): diff --git a/scripts/startup/bl_operators/wm.py b/scripts/startup/bl_operators/wm.py index 6b67e30b6a5..66bbbf11261 100644 --- a/scripts/startup/bl_operators/wm.py +++ b/scripts/startup/bl_operators/wm.py @@ -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 diff --git a/source/blender/editors/sculpt_paint/paint_image_proj.cc b/source/blender/editors/sculpt_paint/paint_image_proj.cc index 5f1bf46ef5d..cfebcf1fd8a 100644 --- a/source/blender/editors/sculpt_paint/paint_image_proj.cc +++ b/source/blender/editors/sculpt_paint/paint_image_proj.cc @@ -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,