Skip to content

A tutorial to help you implement multi-languages support for your blender addon

Notifications You must be signed in to change notification settings

FhyTan/blender-addon-translation-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

English | 中文

Blender Add-on Internationalization Tutorial

If you want to provide multi-language support for your Blender add-on so that international users can use it, but find the official documentation to be brief, this tutorial will help you.

Official Documentation: https://docs.blender.org/api/current/bpy.app.translations.html#

Prerequisites

This tutorial uses Blender 4.2, VS Code, and the Blender Development extension for demonstration. It assumes you are already familiar with this development setup, so we won't go into detail here (●ˇ∀ˇ●).

Building a Simple Add-on

First, let's build a simple add-on, simulating a typical development process without considering translation initially.

Open an empty folder in VS Code, press ctrl + shift + p to run the command Blender: New Addon, and fill in the add-on name and other parameters. The necessary files will be created for you automatically.

Then, add the following code to __init__.py to create a simple panel.

# __init__.py
import bpy


class ExamplePanel(bpy.types.Panel):
    bl_idname = "VIEW3D_PT_example_panel"
    bl_label = "Example Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Example"

    def draw(self, context):
        layout = self.layout
        layout.label(text="Add Cube")


def register():
    bpy.utils.register_class(ExamplePanel)


def unregister():
    bpy.utils.unregister_class(ExamplePanel)


if __name__ == "__main__":
    register()

In VS Code, press ctrl + shift + p and run the command Blender: Start to launch Blender. You will find the Example Panel in the sidebar of the 3D View.

original_addon

Manual Translation

Let's first implement translation manually to understand how it works. In practice, you'll likely use a script to automatically collect all translatable text, which we'll cover later.

We need to handle translations without modifying the source code, separating development from translation work. Blender provides a solution for this. We just need to provide a Python dictionary that maps the original text to the translated text, and Blender will apply it automatically.

Create a new file named manual_translations.py and define a translation dictionary:

# manual_translations.py
manual_translations_dict = {
    "zh_HANS": {
        ("*", "Example Panel"): "示例面板",
        ("*", "Add Cube"): "添加立方体",
    }
}

We also need to tell Blender to use this dictionary for translation. Modify the __init__.py file to import the dictionary and register it when the add-on is registered.

# __init__.py
import bpy

from .manual_translations import manual_translations_dict


class ExamplePanel(bpy.types.Panel):
    bl_idname = "VIEW3D_PT_example_panel"
    bl_label = "Example Panel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Example"

    def draw(self, context):
        layout = self.layout
        layout.label(text="Add Cube")


def register():
    bpy.utils.register_class(ExamplePanel)
    bpy.app.translations.register(__name__, manual_translations_dict)


def unregister():
    bpy.utils.unregister_class(ExamplePanel)
    bpy.app.translations.unregister(__name__)


if __name__ == "__main__":
    register()

Now, in VS Code, run Blender: Reload Addons using ctrl + shift + p to update the add-on. Switch the language in Blender to Chinese, and you will see that the add-on is now translated.

manual_translations

Understanding the Dictionary

The structure of this dictionary is quite intuitive. For each language, it provides a set of key-value pairs where the key is the original English text and the value is the translated text, except for the strange * symbol.

In fact, the same word or phrase can have different meanings and thus different translations in various contexts. The key here is a tuple (context, original_text) to handle different translations for the same text in different situations. The context can be any string you define, and * is used to indicate the default context.

For example, let's create a few more label objects in the draw function and set different contexts for them.

# __init__.py
def draw(self, context):
    layout = self.layout
    layout.label(text="Add Cube")
    layout.label(text="Add Cube", text_ctxt="condition1")
    layout.label(text="Add Cube", text_ctxt="condition2")

And update the translation dictionary accordingly:

# manual_translations.py
manual_translations_dict = {
    "zh_HANS": {
        ("*", "Example Panel"): "示例面板",
        ("*", "Add Cube"): "添加立方体",
        ("condition1", "Add Cube"): "新增立方体",
    }
}

After updating the add-on, you will see the following result:

context_translations

As you can see, the same original text can have different translations. Both the context and the original text are needed to uniquely determine the meaning of a sentence.

This is a common feature in most internationalization systems.

Dynamic Strings

However, there's an issue: the current translation mechanism relies on exact string matching. For a string constructed with format(), like "Hello {}, welcome".format(name), where name is a variable, different names result in different strings. This causes the translation to fail to match and revert to the original text. There's also no convenient regex-like feature for text matching.

A common solution is to first translate the template string "Hello {}, welcome", keeping the placeholder, into "你好 {},欢迎光临", and then use format() for text replacement. Although the variable part cannot be translated, the rest of the string can be translated correctly.

This is where the following set of Python API functions, described in the official documentation, comes in handy:

bpy.app.translations.pgettext(msgid, msgctxt=None)
bpy.app.translations.pgettext_tip(msgid, msgctxt=None)
bpy.app.translations.pgettext_iface(msgid, msgctxt=None)
bpy.app.translations.pgettext_rpt(msgid, msgctxt=None)
bpy.app.translations.pgettext_data(msgid, msgctxt=None)

The first function, pgettext, is used to find the corresponding translated text in your dictionary based on the msgid (message ID) and return it. For UI elements like layout.label(text="..."), Blender handles the translation internally, so we don't need to call it manually. We only need to call it ourselves when dealing with dynamic strings that Blender cannot handle automatically.

The other four functions decide whether to translate based on the text's location (UI, tooltips, etc.). They simply add a check before calling pgettext, which can be configured in Blender's preferences. It's best to use the function corresponding to your text's location rather than calling pgettext directly to respect user settings.

translation_preferences

Now we can add the following lines to test this:

# __init__.py
def draw(self, context):
    # ...
    layout.label(text=bpy.app.translations.pgettext_iface("Hello {}, welcome").format(bpy.context.scene.name))
# manual_translations.py
manual_translations_dict = {
    "zh_HANS": {
        # ...
        ("*", "Hello {}, welcome"): "你好 {},欢迎光临",
    }
}

After updating the add-on, you will see the following result:

dynamic_string

Automatic Dictionary Generation

If your add-on has many strings to translate, building the dictionary manually can be tedious and error-prone. This is where some tools can help.

Blender provides a built-in add-on called Manage UI translations specifically for handling multi-language support. You can enable it in the add-ons list, and its panel will appear in the Render Properties tab.

manage_ui_translations_addon manage_ui_translations_panel_disable

However, it might show that initialization failed, and clicking the buttons does nothing. If you open the console, you might see some error messages.

missing_folders

It seems some folders are missing. After creating these folders and returning to Blender, the panel should become usable.

manage_ui_translations_panel_enable

This add-on can be used not only to translate our add-on but also Blender itself. That's why it has a PO Work File Path pointing to Blender's main translation files, which we don't need. We only need to translate our add-on, so we'll use the buttons under the Add-ons: section.

Now, select only the languages you want to translate into, click the Refresh I18n Data... button, and select your add-on.

refresh_i18n_data

You might encounter another error. Just follow the error message and create the corresponding folders. (I have to say, this official Blender add-on could be better).

missing_folders2

After creating the folders, click the button again. You will find that a translations.py file has appeared next to your __init__.py. This is the translation dictionary automatically generated by the add-on. If you look at its logic, you'll see that its dictionary format is the same as the one we built manually.

translations_py

Currently, there are no translated texts. You could fill in the translations directly in the new Python file, but a better approach is to select the Export PO... button to export the translation content to a PO file.

export_po

PO files are usually placed in the locale folder at the project root, but you can put them anywhere else without any issue. You will then see that Blender has automatically generated a few files for you.

po_files po_content

This is a standard file format for software internationalization. The .pot file is a template and should not be modified. We need to edit the .po file. If you need to add another language, just create a corresponding .po file and copy the content from the .pot file to get started.

This approach makes it easy for translators who don't know how to code to help with translations, as they don't need to touch any Python files. They can even use specialized PO editing tools like Poedit to edit the files.

After editing, click the Import PO... button to import the PO file. You will see that the auto-generated translations.py file is updated automatically with the translations you've added.

import_po

Finally, modify the __init__.py file to use the translations module instead of the manual_translations module, and you're done.

Conclusion

Blender add-on internationalization seems to be a very useful feature, but for some reason, there is very little information available online. I figured it out after a lot of research, and I'm documenting the process here in the hope that it will help you.

About

A tutorial to help you implement multi-languages support for your blender addon

Topics

Resources

Stars

Watchers

Forks

Languages