godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.14k stars 96 forks source link

Expose POTGenerator to scripting #10986

Open dbnicholson opened 13 hours ago

dbnicholson commented 13 hours ago

Describe the project you are working on

A plugin with strings to translate.

Describe the problem or limitation you are having in your project

Currently the only way to regenerate POT files is via the localization editor's POT Generation tab. This is the only part of the gettext localization pipeline that can't be automated.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

If POTGenerator::generate_pot were exposed to GDScript, you could easily write a script that calls generate_pot with your desired POT file location. This would fill the hole where you'd normally use xgettext. After that, the rest of the gettext flow works as normal.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Presuming that the POTGenerator singleton and its generate_pot method were available, you could write a simple SceneTree script like:

extends SceneTree

func _init():
    POTGenerator::generate_pot("po/myproject.pot")
        quit()

Then you could run it like godot --path . --headless --script genpot.gd as part of your translation pipeline.

If this enhancement will not be used often, can it be worked around with a few lines of script?

I don't believe there's any way to manage this from a script since none of the necessary functionality is exposed to GDScript. Without the enhancement, you'd have to use the POT Generation tab in the editor whenever translatable strings change.

Is there a reason why this should be core and not an add-on in the asset library?

Until the core exposes POTGenerator, there's no way to provide this functionality from an add-on.

RedMser commented 11 hours ago

I would suggest the API not be strictly tied to POT generation, since it could be thinkable to allow generating other translation files in the future (e.g. for fluent I had to re-implement the system, and it is not integrated into the editor UI at all).

Something like UI changes can always have breaking changes. But once an API is public, you can't easily change it until Godot 5.0 release! So it must be carefully considered and thought through.

Cleanest implementation I can think of would be a EditorTranslationGenerator class which would be implemented for PO by default, and can be implemented by extensions to register custom generators. Draft:

class EditorTranslationGenerator:
    # PUBLIC API:

    # returns the contents of the generated translation file (e.g. contents of the .pot file) for the specified files
    static func generate(input_files: PackedStringArray) -> String

    # list of files that are part of the project settings -> pot generation dialog
    static func get_files() -> PackedStringArray

    # VIRTUAL METHODS:

    # used by the UI so it knows which file extension the user can save the file as.
    func _get_supported_extensions() -> PackedStringArray

    # given all translation ids, returns the contents of the generated translation file (e.g. contents of the .pot file)
    # this is basically a virtual version of POTGenerator::_write_to_pot
    func _generate(msgids: Array[String], msgids_context_plural: Array[Array]) -> String

class EditorPlugin:
    func add_translation_generator(generator: EditorTranslationGenerator) -> void
    func remove_translation_generator(generator: EditorTranslationGenerator) -> void

Usage:

var result = EditorTranslationGenerator.generate(EditorTranslationGenerator.get_files())
var f = FileAccess.open("res://my_file.pot", FileAccess.WRITE)
f.store_string(result)

It does seem like a bigger project than just exposing the POTGenerator, but I hope you understand my perspective as well.

Calinou commented 9 hours ago

Shouldn't we add a --generate-pot CLI argument instead? Most automation use cases involve using the command line, without requiring a dedicated GDScript to be written for this.

KoBeWi commented 8 hours ago

You can generate POT with this code:

@tool
extends EditorScript

func _run() -> void:
    var localization = EditorInterface.get_base_control().find_child("*Localization*", true, false)
    var file_dialog: EditorFileDialog = localization.get_child(5)
    file_dialog.file_selected.emit("res://POT File.pot")