-
Notifications
You must be signed in to change notification settings - Fork 27
Localized properties #393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Localized properties #393
Conversation
Create three constructor functions and use them from the catalog.
883aad6
to
7f704f6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed, thanks for the wording!
if has_property: | ||
return_classname = parent_class_name | ||
continue | ||
else: | ||
break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 return
ing from the body of the loop, rather than having a var return_classname
and return
ing 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
@@ -215,11 +215,11 @@ static func new_property_setter(_class_name: String, property: Dictionary, categ | |||
var block_definition: Resource = new( | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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