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

Translation infrastructure #295

Merged
merged 14 commits into from
Nov 4, 2024
Merged

Translation infrastructure #295

merged 14 commits into from
Nov 4, 2024

Conversation

dbnicholson
Copy link
Member

@dbnicholson dbnicholson commented Oct 29, 2024

This is a WIP branch I had to provide translations for the plugin. It would need some work regardless, but it turns out that editor translation plugins don't work, so it's a moot point. Just putting it here for reference and in the unlikely scenario that Godot does start supporting editor plugin translations.

This has now been updated to use TranslationDomain, which was added in Godot 4.4-dev3. If you run 4.4-dev3 and put some translations in the PO files, you'll see them in the editor.

Fixes: #146
Fixes: #152

https://phabricator.endlessm.com/T35705

Here's a tool script I was using to test that ultimately proved to me that translations weren't happening with TranslationServer:

@tool
extends EditorScript

func _run():
	var locale = TranslationServer.get_locale()
	var tool_locale = TranslationServer.get_tool_locale()
	print("locale: %s" % locale)
	print("tool locale: %s" % tool_locale)
	#print("languages: %s" % TranslationServer.get_all_languages())
	print("locales: %s" % TranslationServer.get_loaded_locales())

	var tx = TranslationServer.get_translation_object(locale)
	print("translation: %s" % tx)
	if tx:
		print("translation locale: %s" % tx.locale)
		print("translation message list: %s" % tx.get_translated_message_list())
	
	var tool_tx = TranslationServer.get_translation_object(tool_locale)
	print("tool translation: %s" % tool_tx)
	if tool_tx:
		print("tool translation locale: %s" % tool_tx.locale)
		print("tool translation message list: %s" % tool_tx.get_translated_message_list())
	
	print(TranslationServer.translate("Add the node into the group"))

@dbnicholson
Copy link
Member Author

Note that if you want to set the tool locale, you have to use LANG/LC_* environment variables or however you set the locale for a process. The Godot --language option only sets the runtime locale, not the editor locale.

@dbnicholson
Copy link
Member Author

Turns out you can do this in 4.4 using the new TranslationDomain support. I added a couple commits to do that and it seems to work. Still needs cleanup, though. I'm going to reopen and mark as a draft.

@dbnicholson dbnicholson reopened this Oct 30, 2024
@dbnicholson dbnicholson marked this pull request as draft October 30, 2024 00:30
@dbnicholson
Copy link
Member Author

This is really ready for review now. I tested with 4.3 and 4.4-dev3. The editor translations only show up in 4.4, but I added some bogus translations and they do work there.

@dbnicholson dbnicholson marked this pull request as ready for review October 31, 2024 12:12
@dbnicholson dbnicholson requested review from manuq and wjt October 31, 2024 12:17
Since BlockDefinitions are generic Resources, Godot doesn't
automatically extract translatable strings from them. In order to do
that, we need to provide an EditorTranslationParserPlugin that extracts
the desired fields so they're included in a POT file.
These add utilities for using translations with Godot 4.4's
TranslationDomain support. Besides helpers to access the translation
domain, there are also utilities to load the translations from PO files.
Much of the code is there to run on pre-4.4 Godot without causing
errors.
This will add the godot_block_coding translation domain and include
translations from any PO files in the locale directory. This is done
during plugin initialization so that the translations are loaded before
any component tries to translate a string.
Nodes inherit the translation domain of their parent by default, so
setting our translation domain on a few top level nodes will make all of
our nodes translated. The domain is set in _init() to ensure that child
nodes inherit the domain before they start translating strings.
Since the inspector plugin isn't a child of our UI, it needs to have the
translation domain set explicitly.
Copy link
Member

@wjt wjt left a comment

Choose a reason for hiding this comment

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

Very nice! Obviously I'm offended that there is no placeholder en_GB.po file.

addons/block_code/ui/blocks/block/block.gd Outdated Show resolved Hide resolved
push_warning('Unrecognized game result "%s"' % result)


func reset():
text = ""
# Workaround for POT generation extracting empty string.
text = str("")
Copy link
Member

Choose a reason for hiding this comment

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

Weird, so it extracts constant strings for translation even if not tagged with tr()?

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's actually a little uglier than that now that I've read through the GDScript translation parser. The assumption is that anything that assigns to a text property is a Label or similar, which should be automatically translated. In this case, it is a subclass of a Label, so that's a correct assumption. However, I don't think it should try to translate an empty string.

I wouldn't have cared except that msgmerge errored on a duplicate empty string msgid.

Copy link
Member

Choose a reason for hiding this comment

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

Weird. I can see the appeal of not having to litter the code base with tr() at some level, but it seems error-prone.

@@ -56,6 +56,7 @@ func _enter_tree():

# Custom Project->Tools menu items.
add_tool_menu_item(tr("Regenerate %s POT file") % "BlockCode", TxUtils.regenerate_pot_file)
add_tool_menu_item(tr("Update %s translated files") % "BlockCode", TxUtils.update_pot_files)
Copy link
Member

Choose a reason for hiding this comment

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

I wonder how feasible it would be to have a CI job that runs a (real, not headless) editor and then executes these...

Copy link
Member Author

@dbnicholson dbnicholson Nov 1, 2024

Choose a reason for hiding this comment

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

Good question. The TxUtils.update_pot_files one is independent of the editor, so you could write a headless SceneTree script that we could run from CI. I almost started doing it but decided to wait for now since TxUtils.regenerate_pot_file is very much dependent on the editor and I had no idea if that could be scripted.

Copy link
Member

Choose a reason for hiding this comment

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

One for another day I think.

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 figured this out (at least one way). It's kinda nasty, but I'll make a PR for it.

addons/block_code/locale/pt.po Show resolved Hide resolved
We want the strings in the BlockDefinition to stay untranslated and only
translate them when being displayed in the UI.
Neither the user's entered variable name nor the type string should be
translated, so set auto_translate_mode to disabled.
These represent block argument values and shouldn't be translated.
Workaround an issue with POT generation where the empty string is
extracted for translation. Using str() avoids POT generation since the
string isn't evaluated at compile time. See
godotengine/godot-proposals#10590.
Currently the only way to regenerate a POT file is to go through the
Regenerate POT dialog in the Localization tab of the Project Settings.
This adds a Project->Tools menu item that drives the dialog from code
using our POT file path. Hat tip to KoBeWi for the procedure. Hopefully
in the future we'll have a Godot CLI interface to do this.
The list of translated files to include in the POT file is stored in the
project settings. The primary way to update it is through the POT
Generation dialog when a new file is added, but we know that we
generally want to add all of the plugin's files for translation. This
adds a Project->Tools menu item that finds all the relevant files and
updates the project setting.
This is the result of running TxUtils.update_pot_files() and
TxUtils.regenerate_pot_file() via the Project->Tools menu. There are
definitely some strings extracted from scene files that we don't want
translated, but I'm not sure the best way to do that without completely
taking over scene file parsing. Just ignore the issue for now...
Create message catalogs for all the locales supported in the Godot
editor except for es_AR and pt_BR. Due to a bug in Godot's locale
matching[1], these would be preferred over the country-less variants for
all countries. For example, the pt_BR translation would be used for
pt_PT when the generic pt translation would be preferred.

The catalogs were created like so:

```
msginit --no-translator -l $locale -i godot_block_coding.pot -o $locale.po
```

The catalogs are also added to the project settings despite that setting
not being used in the editor. Nodes that are used in games such as the
simple nodes can still get translations via the main domain this way.

1. godotengine/godot#90677
A bit of info about working with the translations.
@wjt wjt merged commit 1e94e46 into main Nov 4, 2024
3 checks passed
@wjt wjt deleted the blockdef-translations branch November 4, 2024 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add place for Russian localisation Any localization of each text on the blocks?
2 participants