Skip to content
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

Add translation domain #95787

Merged
merged 3 commits into from
Sep 20, 2024
Merged

Add translation domain #95787

merged 3 commits into from
Sep 20, 2024

Conversation

timothyqiu
Copy link
Member

@timothyqiu timothyqiu commented Aug 19, 2024

This PR is separated into 3 commits to make it easier to review.

1. Add TranslationDomain

l10n features like add_translation() and translate() are all moved from TranslationServer into the new TranslationDomain class.

Users can add/remove custom translation domains with get_or_add_domain()/remove_domain(). It's "get or add" because getting a null TranslationDomain is practically never desired.

Existing TranslationServer methods are kept as wrappers around the main translation domain.

2. Allow configuring which translation domain Object.tr() uses

This adds {get,set}_translation_domain() virtual methods on Object. It's by default a simple getter/setter, only affects later tr() calls on that object.

For a Node, its translation domain is inherited from the parent node by default. Once set_translation_domain() is called, the node's translation domain is fixed (calling set_translation_domain_inherited() makes it parent-dependent again).

3. Make editor use translation domains

The components from Weblate are all translation domains now: godot.editor, godot.properties, and godot.documentation. Note that tool translations and extractable translations are loaded into the same godot.editor domain.

EditorNode and ProjectManager are set to use the godot.editor translation domain. Nodes in the current edited scene don't do translation, so translation domains do not matter.

Nodes like EditorFileDialog should automatically fix their translation domain to godot.editor so they have the right translation when used by plugins.

How editor plugins do l10n

  1. Pick a unique translation domain name, e.g., com.example.my-plugin-name.
  2. Plugin _enter_tree(): load translations into that translation domain.
  3. Call set_translation_domain("com.example.my-plugin-name") on its root UI node.
  4. Plugin _exit_tree(): remove that translation domain.

Sample dock panel plugin to demonstrate the usage: test-4.zip

@timothyqiu timothyqiu added this to the 4.4 milestone Aug 19, 2024
@timothyqiu timothyqiu requested review from a team as code owners August 19, 2024 04:04
@KoBeWi
Copy link
Member

KoBeWi commented Aug 19, 2024

I'm confused on how to add translations for plugins. E.g. I have a plugin with custom strings that need to be translated, but it also has a file dialog. When I set a domain for the plugin, the dialog will lose translations. I have to either set a domain for every node, or modify an existing domain. The first option sounds rather cumbersome, the latter has the conflict problem this PR tries to fix.

Also, for whatever reason, when adding translation to godot.editor domain, the plugin will not be translated on launch. It needs to be disabled and enabled.

doc/classes/Node.xml Outdated Show resolved Hide resolved
<method name="get_translation_domain" qualifiers="const">
<return type="StringName" />
<description>
Returns the name of the translation domain used by [method tr] and [method tr_n]. See also [TranslationServer].
Copy link
Contributor

@Mickeon Mickeon Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am to assume it works like this

Suggested change
Returns the name of the translation domain used by [method tr] and [method tr_n]. See also [TranslationServer].
Returns the name of the translation domain used by [method tr] and [method tr_n], as registered on the [TranslationServer]. See also [TranslationDomain].

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this creates an ambiguity: what is registered on the TranslationServer?

  • "which translation domain this object uses" is registered
    • It's not registered on the translation server.
  • "the name of a translation domain" is registered
    • It's technically not required that a translation domain with the specified name exists.

@timothyqiu
Copy link
Member Author

timothyqiu commented Aug 19, 2024

@KoBeWi I made a sample dock panel plugin to demonstrate the usage: test-4.zip

The plugin has zh_CN and ja translations.

I only partially translated FileDialog strings. Uncomment the set_translation_domain() line in the script to make the file dialog use editor translation.

@KoBeWi
Copy link
Member

KoBeWi commented Aug 19, 2024

Uncomment the set_translation_domain() line in the script to make the file dialog use editor translation.

It would be more convenient if editor translation was a fallback.

@timothyqiu
Copy link
Member Author

It would be more convenient if editor translation was a fallback.

Plugins should use EditorFileDialog instead as it uses editor translation by default. The FileDialog in the example serves as a comparison.

Currently, all plugin translations fall back to editor translations. It's also technically not hard to add fallback domain support for a TranslationDomain. But I think this falls into the category of sacrificing correctness for temporary convenience.

  • Editor translation is created for the editor itself. Strings are added and removed (and even change meaning) based on how the editor needs them to be. Falling back to editor translation creates an assumption that the editor does provide the strings the plugin needs. It's the similar reason why we don't automatically fallback missing Godot editor translations to Blender editor translations :P Although there may be a correlation in content, they are two different domains.
  • Falling back could easily make the plugin partially translated, which is usually not desired.
  • Authors have to deliberately exclude strings found in editor translation in order to not translating them. But why? This takes much more effort than including extractable strings in the POT.

@KoBeWi
Copy link
Member

KoBeWi commented Aug 19, 2024

Plugins should use EditorFileDialog instead as it uses editor translation by default.

Unfortunately EditorFileDialog is not easily available. Most people don't even know that you can use it. Though I guess it's a separate issue.

scene/main/node.cpp Outdated Show resolved Hide resolved
@@ -206,6 +206,10 @@ void EditorFileDialog::_update_theme_item_cache() {

void EditorFileDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
set_translation_domain("godot.editor");
Copy link
Member

@KoBeWi KoBeWi Aug 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it's not in the constructor?
EDIT:
Also you could use SNAME for nodes that have multiple instances.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was in the constructor, but then I got this warning:

WARNING: Attempting to access theme items too early in EditorFileDialog; prefer NOTIFICATION_POSTINITIALIZE and NOTIFICATION_THEME_CHANGED

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's weird, because it's unrelated to theme. Also you set it in the constructor in ProjectManager.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EditorFileDialog updates its icons in NOTIFICATION_TRANSLATION_CHANGED, maybe some icons have language dependent variants (?)

Copy link
Member

@KoBeWi KoBeWi Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They don't, looks like a bug. It handles 3 notifications at once. Translation change should only call invalidate().

Copy link
Member Author

@timothyqiu timothyqiu Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_update_icons() is indeed locale-dependent. A change in language potentially changes the return value of is_layout_rtl():

void EditorFileDialog::_update_icons() {
// Update icons.
mode_thumbnails->set_icon(theme_cache.mode_thumbnails);
mode_list->set_icon(theme_cache.mode_list);
if (is_layout_rtl()) {
dir_prev->set_icon(theme_cache.forward_folder);
dir_next->set_icon(theme_cache.back_folder);
} else {

Yeah, maybe icon update could be moved to invalidate() too.

Copy link
Member

@KoBeWi KoBeWi Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that's what NOTIFICATION_LAYOUT_DIRECTION_CHANGED is for. Unless RTL can change based on locale and don't send the notification?

In any case, changing language without restarting the editor is generally not supported, so this is pretty much irrelevant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the implementation, NOTIFICATION_LAYOUT_DIRECTION_CHANGED is only emitted when the control changes between inherit-parent, locale-dependent, LTR, and RTL. It's not used when locale changes.

doc/classes/TranslationServer.xml Outdated Show resolved Hide resolved
How editor plugins use this feature:
1. Pick a unique translation domain name.
2. `_enter_tree()`: load translations into that translation domain.
3. Call `set_translation_domain()` for its root UI node.
4. `_exit_tree()`: remove that translation domain.

Plugins can also set the translation domain to `godot.editor` for
nested nodes that should use editor translations. `EditorFileDialog`
automatically does this.
Comment on lines +210 to +212
case NOTIFICATION_POSTINITIALIZE: {
set_translation_domain(SNAME("godot.editor"));
} break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably do the same for EditorSpinSlider?
I didn't check if it has translatable strings but I assume maybe some tooltips at least.

Copy link
Member Author

@timothyqiu timothyqiu Sep 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EditorSpinSlider currently does not use auto translation. Text translations, including tooltip translations, are done via TTR macros (always use the godot.editor domain).

Copy link
Member

@akien-mga akien-mga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read through the proposals and the PR discussion, it makes sense to me.

I haven't checked the implementation in depth nor tested but overall the exposed API seems pretty good.

@akien-mga akien-mga merged commit 7e62565 into godotengine:master Sep 20, 2024
20 checks passed
@akien-mga
Copy link
Member

Thanks! Great work 🎉

@timothyqiu timothyqiu deleted the domestic branch September 20, 2024 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants