Conversation
Create three constructor functions and use them from the catalog.
883aad6 to
7f704f6
Compare
wjt
left a comment
There was a problem hiding this comment.
Nice work! I spotted what looks like a logic error but I could be wrong – I haven't run the code to check.
| if definition.property_name: | ||
| var domain: TranslationDomain = TranslationServer.get_or_add_domain("godot.properties") | ||
| var translated_property: String = domain.translate(definition.property_name.capitalize()) | ||
| # TODO: Ideally we should be also passing the context. See: | ||
| # https://github.com/godotengine/godot/blob/978b38797ba8e8757592f21101e32e364d60662d/editor/editor_property_name_processor.cpp#L90 | ||
| return tr(definition.display_template) % translated_property.to_lower() | ||
|
|
||
| return tr(definition.display_template) |
There was a problem hiding this comment.
I might add a note to the display_template docstring to the effect that
## If [member property_name] is set, this template is assumed to be a format string with a `%s` placeholder; in this case, any literal `%` signs must be escaped as `%%`.You could imagine this being a problem because % is a unit that a property might have. (I think it's fine to do it this way.)
There was a problem hiding this comment.
Oh good point. It is a bit weird that this PR adds 2 paths for display_template, one that formats the translated string and one that doesn't. At least let's document it.
There was a problem hiding this comment.
Addressed, thanks for the wording!
| if has_property: | ||
| return_classname = parent_class_name | ||
| continue | ||
| else: | ||
| break |
There was a problem hiding this comment.
Are the continue and break here the wrong way around?
I think what you're trying to do here is to walk up the ancestor classes and return the first class that defines a property of that name. If D is a subclass of C is a subclass of B is a subclass of A, and B and A both define property foo then _get_classname_for_property("C", "foo") should return "B", as should _get_classname_for_property("D", "foo").
If that's correct then I believe this function is wrong:
_get_classname_for_property("C", "foo")will return"A"_get_classname_for_property("D", "foo")will return""because it breaks out of the loop at the first class that does not have the property
Assuming I'm right here, you can swap continue and break and the function will behave correctly.
Personally I find the control flow easier to read by returning from the body of the loop, rather than having a var return_classname and returning only at the end of the function:
static func _get_classname_for_property(_class_name: StringName, property_name: StringName) -> StringName:
for parent_class_name in BlocksCatalog.get_parents(_class_name):
var property_list := ClassDB.class_get_property_list(parent_class_name, false)
var has_property = property_list.any(func(dict): return dict.name == property_name)
if has_property:
return parent_class_name
return &""There was a problem hiding this comment.
This is doing the right thing but in a weird way. I'm listing with inheritance (passing to the 2nd argument no_inheritance = false ), and then recording the last class that had the property, then breaking. Instead I should list without inheritance (passing true) and return the first class that has the property. The parameter no_inheritance being negated confused me.
This function also screams for a unit test so I will try to add one.
There was a problem hiding this comment.
Oh! I completely missed the false parameter. I don't like the design of that API: boolean parameter with negated sense!
There was a problem hiding this comment.
I did this in a TDD fashion: first I wrote the unit test, then I changed the implementation and confirmed that the test was still passing.
| PROPERTY_SETTER_NAME_FORMAT % [_class_name, property.name], | ||
| _class_name, | ||
| "Set the %s property" % property.name, | ||
| Engine.tr("Set the %s property") % property.name, |
There was a problem hiding this comment.
TIL. ¡Gracias, Godot Meetup Argentina!
| const PROPERTY_SETTER_NAME_FORMAT = &"%s_property_set_%s" | ||
| const PROPERTY_CHANGER_NAME_PATTERN = "(?<class_name>[^\\s]*)_property_change_(?<property_name>[^\\s]+)" | ||
| const PROPERTY_CHANGER_NAME_FORMAT = &"%s_property_change_%s" | ||
| const PROPERTY_GETTER_NAME_PATTERN = "(?<class_name>[^\\s]*)_property_get_(?<property_name>[^\\s]+)" | ||
| const PROPERTY_GETTER_NAME_FORMAT = &"%s_property_get_%s" |
There was a problem hiding this comment.
Now that I read the current unit tests I remember that we have a migration for the spawn blocks. I wonder if I should either add a migration to the previous block names like CLASSNAME_set_PROPERTYNAME, or just go back to those block names and avoid the breaking change. I think I'll go with the latter.
There was a problem hiding this comment.
So yes, I managed to make the PR not having any breaking changes, and rebased.
Display localized properties whenever possible. Just like the Inspector does when "Localized" is set from the dropdown menu. For this the property name must be stored in the block definition. Once translated, it is added to the display template.
So they use the localized property name. And also, reuse the setter or getter if it's already in the catalog.
Since this is a static function, tr() can't be called from it. But Engine.tr() can. Thanks to Godot Meetup Argentina for unblocking me on this.
Create two constructor functions and use them from the catalog.
Set auto translate to disabled in this scene. This scene has a debug label that is not worth translating.
And log an error when a persisted block can't be parsed.
Mark display template and description as translatable.
These files are not meant to be translated.
There are no breaking changes, so the blocks stay the same.
7f704f6 to
e800b5e
Compare


Grabacion.de.pantalla.desde.2025-06-05.16-00-33.webm