From ec22a9123337863f92f67af86783047d6a08cf2b Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Sat, 31 May 2025 12:48:13 +0200 Subject: [PATCH 1/4] I18n: Allow translation of Bitwise Math node label The Bitwise Math node's label uses a formatted string, where the operation name was not translated. --- source/blender/makesrna/RNA_access.hh | 4 ++++ source/blender/makesrna/intern/rna_access.cc | 16 ++++++++++++++++ .../nodes/function/nodes/node_fn_bit_math.cc | 3 ++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/source/blender/makesrna/RNA_access.hh b/source/blender/makesrna/RNA_access.hh index f1561393341..e89e2095b76 100644 --- a/source/blender/makesrna/RNA_access.hh +++ b/source/blender/makesrna/RNA_access.hh @@ -274,6 +274,10 @@ int RNA_enum_bitflag_identifiers(const EnumPropertyItem *item, int value, const char **r_identifier); bool RNA_enum_name(const EnumPropertyItem *item, int value, const char **r_name); +bool RNA_enum_name_gettexted(const EnumPropertyItem *item, + int value, + const char *translation_context, + const char **r_name); bool RNA_enum_description(const EnumPropertyItem *item, int value, const char **r_description); int RNA_enum_from_value(const EnumPropertyItem *item, int value); int RNA_enum_from_identifier(const EnumPropertyItem *item, const char *identifier); diff --git a/source/blender/makesrna/intern/rna_access.cc b/source/blender/makesrna/intern/rna_access.cc index 87496ad3a23..8fc3e4529e7 100644 --- a/source/blender/makesrna/intern/rna_access.cc +++ b/source/blender/makesrna/intern/rna_access.cc @@ -1911,6 +1911,22 @@ bool RNA_enum_name(const EnumPropertyItem *item, const int value, const char **r return false; } +bool RNA_enum_name_gettexted(const EnumPropertyItem *item, + int value, + const char *translation_context, + const char **r_name) +{ + bool result; + + result = RNA_enum_name(item, value, r_name); + + if (result) { + *r_name = BLT_translate_do_iface(translation_context, *r_name); + } + + return result; +}; + bool RNA_enum_description(const EnumPropertyItem *item, const int value, const char **r_description) diff --git a/source/blender/nodes/function/nodes/node_fn_bit_math.cc b/source/blender/nodes/function/nodes/node_fn_bit_math.cc index 3b36682d31c..744f5bb1edb 100644 --- a/source/blender/nodes/function/nodes/node_fn_bit_math.cc +++ b/source/blender/nodes/function/nodes/node_fn_bit_math.cc @@ -121,7 +121,8 @@ static void node_label(const bNodeTree * /*ntree*/, const bNode *node, char *lab { char name[64] = {0}; const char *operation_name = IFACE_("Unknown"); - RNA_enum_name(bit_math_operation_items.data(), node->custom1, &operation_name); + RNA_enum_name_gettexted( + bit_math_operation_items.data(), node->custom1, BLT_I18NCONTEXT_DEFAULT, &operation_name); SNPRINTF(name, IFACE_("Bitwise %s"), operation_name); BLI_strncpy(label, name, maxlen); } From 0bd797a4337766d49cc5139fb8e5c15879329e58 Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Sat, 31 May 2025 13:19:13 +0200 Subject: [PATCH 2/4] I18n: Allow translation of a few reports using formatting Reports whose message uses string formatting needs to be first translated, then formatted. Also in one instance, use rpt_() instead of iface_(). --- scripts/addons_core/bl_pkg/bl_extension_ops.py | 13 ++++++------- scripts/startup/bl_operators/anim.py | 2 +- scripts/startup/bl_operators/image_as_planes.py | 13 +++++++------ scripts/startup/bl_operators/node.py | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/scripts/addons_core/bl_pkg/bl_extension_ops.py b/scripts/addons_core/bl_pkg/bl_extension_ops.py index 3427e2bb7fb..0935dcf95a5 100644 --- a/scripts/addons_core/bl_pkg/bl_extension_ops.py +++ b/scripts/addons_core/bl_pkg/bl_extension_ops.py @@ -36,7 +36,6 @@ from bpy.app.translations import ( pgettext_iface as iface_, pgettext_tip as tip_, pgettext_rpt as rpt_, - ) from . import ( @@ -1916,10 +1915,10 @@ class EXTENSIONS_OT_repo_unlock(Operator): repo_name, repo_directory, _lock_age, _lock_error = self._repo_vars if (error := bl_extension_utils.repo_lock_directory_force_unlock(repo_directory)): - self.report({'ERROR'}, "Force unlock failed: {:s}".format(error)) + self.report({'ERROR'}, rpt_("Force unlock failed: {:s}").format(error)) return {'CANCELLED'} - self.report({'INFO'}, "Unlocked: {:s}".format(repo_name)) + self.report({'INFO'}, rpt_("Unlocked: {:s}").format(repo_name)) return {'FINISHED'} def draw(self, _context): @@ -2815,7 +2814,7 @@ class EXTENSIONS_OT_package_install_files(Operator, _ExtCmdMixIn): return {'CANCELLED'} if isinstance(result := pkg_manifest_dict_from_archive_or_error(filepath), str): - self.report({'ERROR'}, "Error in manifest {:s}".format(result)) + self.report({'ERROR'}, rpt_("Error in manifest {:s}").format(result)) return {'CANCELLED'} pkg_id = result["id"] @@ -3157,7 +3156,7 @@ class EXTENSIONS_OT_package_install(Operator, _ExtCmdMixIn): if python_version ], ), str): - self.report({'ERROR'}, iface_("The extension is incompatible with this system:\n{:s}").format(error)) + self.report({'ERROR'}, rpt_("The extension is incompatible with this system:\n{:s}").format(error)) return {'CANCELLED'} del error @@ -3852,7 +3851,7 @@ class EXTENSIONS_OT_repo_lock_all(Operator): lock_handle.release() return {'CANCELLED'} - self.report({'INFO'}, "Locked {:d} repos(s)".format(len(lock_result))) + self.report({'INFO'}, rpt_("Locked {:d} repos(s)").format(len(lock_result))) EXTENSIONS_OT_repo_lock_all.lock = lock_handle return {'FINISHED'} @@ -3876,7 +3875,7 @@ class EXTENSIONS_OT_repo_unlock_all(Operator): # This isn't canceled, but there were issues unlocking. return {'FINISHED'} - self.report({'INFO'}, "Unlocked {:d} repos(s)".format(len(lock_result))) + self.report({'INFO'}, rpt_("Unlocked {:d} repos(s)").format(len(lock_result))) return {'FINISHED'} diff --git a/scripts/startup/bl_operators/anim.py b/scripts/startup/bl_operators/anim.py index 9f3e108eb5f..60b4c642eef 100644 --- a/scripts/startup/bl_operators/anim.py +++ b/scripts/startup/bl_operators/anim.py @@ -500,7 +500,7 @@ class ARMATURE_OT_copy_bone_color_to_selected(Operator): # Anything else: case _: - self.report({'ERROR'}, "Cannot do anything in mode {!r}".format(context.mode)) + self.report({'ERROR'}, rpt_("Cannot do anything in mode {!r}").format(context.mode)) return {'CANCELLED'} if not bone_source: diff --git a/scripts/startup/bl_operators/image_as_planes.py b/scripts/startup/bl_operators/image_as_planes.py index 7df92c5861f..ce7f293cf51 100644 --- a/scripts/startup/bl_operators/image_as_planes.py +++ b/scripts/startup/bl_operators/image_as_planes.py @@ -22,6 +22,7 @@ from bpy.props import ( from bpy.app.translations import ( pgettext_tip as tip_, + pgettext_rpt as rpt_, contexts as i18n_contexts, ) from mathutils import Vector @@ -852,13 +853,13 @@ class IMAGE_OT_import_as_mesh_planes( def invoke(self, context, _event): engine = context.scene.render.engine if engine not in COMPATIBLE_ENGINES: - self.report({'ERROR'}, tip_("Cannot generate materials for unknown {:s} render engine").format(engine)) + self.report({'ERROR'}, rpt_("Cannot generate materials for unknown {:s} render engine").format(engine)) return {'CANCELLED'} if engine == 'BLENDER_WORKBENCH': self.report( {'WARNING'}, - tip_("Generating Cycles/EEVEE compatible material, but won't be visible with {:s} engine").format( + rpt_("Generating Cycles/EEVEE compatible material, but won't be visible with {:s} engine").format( engine, )) @@ -917,7 +918,7 @@ class IMAGE_OT_import_as_mesh_planes( plane.select_set(True) # All done! - self.report({'INFO'}, tip_("Added {:d} Image Plane(s)").format(len(planes))) + self.report({'INFO'}, rpt_("Added {:d} Image Plane(s)").format(len(planes))) return {'FINISHED'} # Operate on a single image. @@ -1130,13 +1131,13 @@ class IMAGE_OT_convert_to_mesh_plane(MaterialProperties_MixIn, TextureProperties engine = scene.render.engine if engine not in COMPATIBLE_ENGINES: - self.report({'ERROR'}, tip_("Cannot generate materials for unknown {:s} render engine").format(engine)) + self.report({'ERROR'}, rpt_("Cannot generate materials for unknown {:s} render engine").format(engine)) return {'CANCELLED'} if engine == 'BLENDER_WORKBENCH': self.report( {'WARNING'}, - tip_("Generating Cycles/EEVEE compatible material, but won't be visible with {:s} engine").format( + rpt_("Generating Cycles/EEVEE compatible material, but won't be visible with {:s} engine").format( engine, )) @@ -1206,7 +1207,7 @@ class IMAGE_OT_convert_to_mesh_plane(MaterialProperties_MixIn, TextureProperties self.report({'ERROR'}, "No images converted") return {'CANCELLED'} - self.report({'INFO'}, "{:d} image(s) converted to mesh plane(s)".format(converted)) + self.report({'INFO'}, rpt_("{:d} image(s) converted to mesh plane(s)").format(converted)) return {'FINISHED'} def draw(self, context): diff --git a/scripts/startup/bl_operators/node.py b/scripts/startup/bl_operators/node.py index 33db9a833a5..3714fb09fb3 100644 --- a/scripts/startup/bl_operators/node.py +++ b/scripts/startup/bl_operators/node.py @@ -660,7 +660,7 @@ class NODE_OT_viewer_shortcut_set(Operator): bpy.ops.node.activate_viewer() viewer_node.ui_shortcut = self.viewer_index - self.report({'INFO'}, "Assigned shortcut {:d} to {:s}".format(self.viewer_index, viewer_node.name)) + self.report({'INFO'}, rpt_("Assigned shortcut {:d} to {:s}").format(self.viewer_index, viewer_node.name)) return {'FINISHED'} @@ -696,7 +696,7 @@ class NODE_OT_viewer_shortcut_get(Operator): viewer_node = n if not viewer_node: - self.report({'INFO'}, "Shortcut {:d} is not assigned to a Viewer node yet".format(self.viewer_index)) + self.report({'INFO'}, rpt_("Shortcut {:d} is not assigned to a Viewer node yet").format(self.viewer_index)) return {'CANCELLED'} with bpy.context.temp_override(node=viewer_node): From 00d9d58d77e538cede250496dfeb997e66a6e2aa Mon Sep 17 00:00:00 2001 From: Damien Picard Date: Thu, 5 Jun 2025 18:54:21 +0200 Subject: [PATCH 3/4] I18n: Fix strange report formattings A few reports used string formatting in a way which made translation awkward, by splitting the message into several individually translated submessages, instead of just one message with formatting markers. Pull Request: https://projects.blender.org/blender/blender/pulls/139895 --- source/blender/editors/object/object_add.cc | 18 ++++++++---------- .../blender/editors/object/object_transform.cc | 11 +++++------ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/source/blender/editors/object/object_add.cc b/source/blender/editors/object/object_add.cc index 57a78bbb96c..b4830f98fb4 100644 --- a/source/blender/editors/object/object_add.cc +++ b/source/blender/editors/object/object_add.cc @@ -4227,18 +4227,16 @@ static wmOperatorStatus object_convert_exec(bContext *C, wmOperator *op) if (incompatible_count == selected_editable_bases.size()) { BKE_reportf(op->reports, RPT_INFO, - "%s \"%s\"", - RPT_("None of the objects are compatible of conversion to"), - IFACE_(target_type_name)); + "None of the objects are compatible with a conversion to \"%s\"", + RPT_(target_type_name)); } else { - BKE_reportf(op->reports, - RPT_INFO, - "%s %d %s \"%s\"", - RPT_("The selection included"), - incompatible_count, - RPT_("object(s) types which don't support conversion to"), - IFACE_(target_type_name)); + BKE_reportf( + op->reports, + RPT_INFO, + "The selection included %d object type(s) which do not support conversion to \"%s\"", + incompatible_count, + RPT_(target_type_name)); } } diff --git a/source/blender/editors/object/object_transform.cc b/source/blender/editors/object/object_transform.cc index e21e49e9ecf..14fafdc8d1e 100644 --- a/source/blender/editors/object/object_transform.cc +++ b/source/blender/editors/object/object_transform.cc @@ -835,12 +835,11 @@ static wmOperatorStatus apply_objects_internal(bContext *C, /* correct for scale, note mul_m3_m3m3 has swapped args! */ BKE_object_scale_to_mat3(ob, tmat); if (!invert_m3_m3(timat, tmat)) { - BKE_reportf(reports, - RPT_WARNING, - "%s \"%s\" %s", - RPT_("Object"), - ob->id.name + 2, - RPT_("have non-invertable transformation matrix, not applying transform.")); + BKE_reportf( + reports, + RPT_WARNING, + "Object \"%s\" has a non-invertible transformation matrix, not applying transform", + ob->id.name + 2); has_non_invertable_matrix = true; continue; } From 6ccdb8c06e276c65c47db2fdf35410ea6517157b Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 16 Jun 2025 12:41:20 +0200 Subject: [PATCH 4/4] I18N: Add comment about required manual sync in nodes code. Nodes code uses direct access to enum property values definition (outside of the RNA API) to generate UI (translated) messages, this is risky/messy since the i18n context potentailly defined for the actual RNA property is not available in these cases. --- source/blender/nodes/function/nodes/node_fn_bit_math.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/source/blender/nodes/function/nodes/node_fn_bit_math.cc b/source/blender/nodes/function/nodes/node_fn_bit_math.cc index 744f5bb1edb..02e544bb80d 100644 --- a/source/blender/nodes/function/nodes/node_fn_bit_math.cc +++ b/source/blender/nodes/function/nodes/node_fn_bit_math.cc @@ -121,6 +121,8 @@ static void node_label(const bNodeTree * /*ntree*/, const bNode *node, char *lab { char name[64] = {0}; const char *operation_name = IFACE_("Unknown"); + /* NOTE: This assumes that the matching RNA enum property also uses the default i18n context, and + * needs to be kept manually in sync. */ RNA_enum_name_gettexted( bit_math_operation_items.data(), node->custom1, BLT_I18NCONTEXT_DEFAULT, &operation_name); SNPRINTF(name, IFACE_("Bitwise %s"), operation_name);