Skip to content

Translation infrastructure #295

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

Merged
merged 14 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions addons/block_code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,70 @@ Lean into animations! Godot's animations functionality goes beyond just simple a

Please share feedback in the [Godot Forum Block Coding thread](https://forum.godotengine.org/t/block-coding-high-level-block-based-visual-programming/68941).

## Localization

The plugin supports translations through Godot's [gettext][godot-gettext]
support. We welcome contributions to make the plugin work better in your
language! However, please note that translations in the Godot editor **will
only work with Godot 4.4 or newer**.

The gettext PO files are located in the `addons/block_code/locale` directory.
See the Godot [documentation][godot-gettext] for instructions on working with
PO files.

[godot-gettext]: https://docs.godotengine.org/en/stable/tutorials/i18n/localization_using_gettext.html

For developers, a few things need to be done to keep the translatable strings
up to date.

* If files are added or removed, the list of translatable files needs to be
updated. This can be done by using the **Add** dialog in the [POT
Generation][pot-generation] tab. Or you can use the **Project → Tools →
Update BlockCode translated files** menu item in the editor.

* If translatable strings have changed, the POT file needs to be updated. This
can be done by using the **Generate POT** dialog in the [POT
Generation][pot-generation] tab. Or you can use the **Project → Tools →
Regenerate BlockCode POT file** menu item in the editor.

* If the POT file has changed, the PO message files need to be updated. This
can be done using the gettext `msgmerge` tool in the
`addons/block_code/locale` directory:
```
for po in *.po; do
msgmerge --update --backup=none "$po" godot_block_coding.pot
done
```

[pot-generation]: https://docs.godotengine.org/en/stable/tutorials/i18n/localization_using_gettext.html#automatic-generation-using-the-editor

Strings added in scene files or block definition resources will usually be
extracted for localization and translated in the editor automatically. Strings
in scripts need more consideration.

* `Object`s or `Node`s that are not descendents of the Block Coding panel need
to have their translation domain set with the `set_block_translation_domain`
helper function. This should usually be done in the object's `_init` method
to make sure the translation domain is set before that object or any of its
descendents (which inherit the translation domain by default) try to use
localized strings.

* Usually [`tr`][object-tr] and [`tr_n`][object-tr-n] (or [`atr`][node-atr] and
[`atr_n`][node-atr-n] for `Node`s) should be used to mark translatable
strings. These will eventually call the domain's
[`translate`][domain-translate] or
[`translate_plural`][domain-translate-plural] methods, but the `tr` methods
respect translation settings on the object instances. The only time the
`translate` methods should be called directly is within a static context when
an object instance isn't available.

[object-tr]: https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-tr
[object-tr-n]: https://docs.godotengine.org/en/stable/classes/class_object.html#class-object-method-tr-n
[node-atr]: https://docs.godotengine.org/en/stable/classes/class_node.html#class-node-method-atr
[node-atr-n]: https://docs.godotengine.org/en/stable/classes/class_node.html#class-node-method-atr-n
[domain-translate]: https://docs.godotengine.org/en/latest/classes/class_translationdomain.html#class-translationdomain-method-translate
[domain-translate-plural]: https://docs.godotengine.org/en/latest/classes/class_translationdomain.html#class-translationdomain-method-translate-plural

## Development

### pre-commit
Expand Down
6 changes: 6 additions & 0 deletions addons/block_code/block_code_node/block_code.gd
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@
class_name BlockCode
extends Node

const TxUtils := preload("res://addons/block_code/translation/utils.gd")

@export var block_script: BlockScriptSerialization = null


func _init():
TxUtils.set_block_translation_domain(self)


func _ready():
if Engine.is_editor_hint():
return
Expand Down
18 changes: 18 additions & 0 deletions addons/block_code/block_code_plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extends EditorPlugin
const MainPanelScene := preload("res://addons/block_code/ui/main_panel.tscn")
const MainPanel = preload("res://addons/block_code/ui/main_panel.gd")
const Types = preload("res://addons/block_code/types/types.gd")
const TxUtils := preload("res://addons/block_code/translation/utils.gd")
const ScriptWindow := preload("res://addons/block_code/ui/script_window/script_window.tscn")

static var main_panel: MainPanel
Expand All @@ -12,6 +13,9 @@ static var block_code_button: Button
const BlockInspectorPlugin := preload("res://addons/block_code/inspector_plugin/block_script_inspector.gd")
var block_inspector_plugin: BlockInspectorPlugin

const BlockTranslationParserPlugin := preload("res://addons/block_code/translation/parser.gd")
var _tx_parser_plugin: BlockTranslationParserPlugin

var editor_inspector: EditorInspector

var _selected_block_code: BlockCode
Expand All @@ -29,6 +33,11 @@ const DISABLED_CLASSES := [
]


func _init():
TxUtils.load_translations()
TxUtils.set_block_translation_domain(self)


func _enter_tree():
Types.init_cast_graph()

Expand All @@ -41,6 +50,14 @@ func _enter_tree():
block_inspector_plugin = BlockInspectorPlugin.new()
add_inspector_plugin(block_inspector_plugin)

if not _tx_parser_plugin:
_tx_parser_plugin = BlockTranslationParserPlugin.new()
add_translation_parser_plugin(_tx_parser_plugin)

# 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.


# Remove unwanted class nodes from create node
old_feature_profile = EditorInterface.get_current_feature_profile()

Expand Down Expand Up @@ -69,6 +86,7 @@ func script_window_requested(script: String):


func _exit_tree():
remove_translation_parser_plugin(_tx_parser_plugin)
remove_inspector_plugin(block_inspector_plugin)

if block_code_button:
Expand Down
7 changes: 6 additions & 1 deletion addons/block_code/inspector_plugin/block_script_inspector.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
extends EditorInspectorPlugin

const BlockCodePlugin = preload("res://addons/block_code/block_code_plugin.gd")
const TxUtils := preload("res://addons/block_code/translation/utils.gd")


func _init():
TxUtils.set_block_translation_domain(self)


func _can_handle(object):
Expand All @@ -11,7 +16,7 @@ func _parse_begin(object):
var block_code := object as BlockCode

var button := Button.new()
button.text = "Open Block Script"
button.text = tr("Open Block Script")
button.pressed.connect(func(): BlockCodePlugin.main_panel.switch_block_code_node(block_code))

var container := MarginContainer.new()
Expand Down
Loading