Summary:
This feature proposes to implement internationalization in O3DE to support other languages besides English on UI.
What is the relevance of this feature?
O3DE only supports English on UI currently, which is not so friendly to users from other countries. Especially for new users who had never done 3D product development before, support for their native language might help them start with O3DE and understand the terminologies more easily.
The proposed solution implements internationalization support in O3DE. After this work, developers only need to add a new language type and translate strings that have already been extracted to support any other language as they want.
Feature design description:
Mechanism of QT's internationalization support is used in this feature. The basic flow is as follows (Chinese translation for example).
- Tag —— Tag all strings to be translated on UI in source code.
// Before tagged
AZ_Printf("Editor", "Hello");
// After tagged
AZ_Printf("Editor", tr("Hello"));
AZ_Printf("Editor", QObject::tr("Hello"));
AZ_Printf("Editor", QT_TRANSLATE_NOOP("xxx", "Hello"));
- Extract —— Use
lupdate in QT to extract tagged strings into translation files with .ts suffix.
- Translate —— Use
Linguist Tool in QT to translate tagged strings in translation files. Developers can also modify TS files directly.
- Chinese translation will be added, and the type will be marked as finished.
<message>
<location filename="../relative_path/Example.cpp" line="100"/>
<source>Hello</source>
<translation>你好</translation>
</message>
- Compile —— Use
lrelease in QT to compile the translation files with .qm suffix.
-
Directory Structure:
>Translations
>en
>Editor_en.qm
>qtbase_en.qm
>zh_CN
>Editor_zh_CN.qm
>qtbase_zh_CN.qm
- Set Translators —— Set translators for every module needs to be translated.
- Translations are seperated into different files for every module. In particular,
qtbase is added to locate translations for basic Qt modules, such as Qt Core, Qt GUI, Qt Network, and Qt Widgets
QStringList translatorNames = {
"Editor",
"qtbase",
// More modules like AssetProcessor, AzToolsFramework, and every Gem...
};
- Load/Unload Translator —— Load translators before UI is initialized, and unload translators before UI exits.
Technical design description:
Internationalization with QT
Internationalization with QT supports most languages in use today, and and will work on all platforms as long as the system has fonts to render these writing systems installed.
Following APIs are mainly used:
| API |
description |
| QTranslator |
Provides internationalization support for text output. |
| QLocale |
Converts between numbers and their string representations in various languages. |
| QTextCodec |
Provides conversions between text encodings. |
Three tools in QT are used to generate translation files automatically:
- lupdate is used to generate translation source (TS) files with all the user-visible text (context, file path, line number etc.) but no translations.
- QT Linguist is used to add translations in the TS files.
- lrelease is used to read the TS files and produce the QM files used by the application at runtime.
Implementation in O3DE
Currently, some strings to be translated have been tagged using Object::tr(). Translators are loaded in EditorQtApplication::Initialize().
void EditorQtApplication::Initialize()
{
// Install QTranslator
InstallEditorTranslators();
}
void EditorQtApplication::InstallEditorTranslators()
{
m_editorTranslator = CreateAndInitializeTranslator("editor_en-us.qm", ":/Translations");
m_assetBrowserTranslator = CreateAndInitializeTranslator("assetbrowser_en-us.qm", ":/Translations");
}
QTranslator* EditorQtApplication::CreateAndInitializeTranslator(const QString& filename, const QString& directory) { }
void EditorQtApplication::UninstallEditorTranslators() { }
However, the current implementation only supports localization in Editor and Asset Browser, and strings not in subclasses of QObject can't be translated.
Step 1. Set Translators
Based on what has been done in O3DE, this proposal uses m_translators with type QVector to store translators.
QVector<QTranslator*> m_translators;
void EditorQtApplication::InstallTranslators()
{
QTextCodec::setCodecForLocale(QTextCodec::codecForName("utf-8"));
QStringList translatorNames = { "Editor", "qtbase", "AzToolsFramework" }; // Translators of Editor for example
// QStringList translatorNames = { "qtbase", "AssetProcessor", "AzToolsFramework", "AzQtComponents" }; // Translators of Asset Processor for example
for (QString trans : translatorNames)
{
m_translators.append(AzToolsFramework::CreateAndLoadTranslator(trans));
}
}
Step 2. Load & Unload Translators
Since the startup time of each process are actually different, translators are loaded in every module separately.
- Common functions are defined in file
Translation.h.
enum class Language
{
English,
Chinese
// ... Other languages will be added later.
};
Language GetLanguage()
{
QSettings settings("O3DE", "O3DE Editor");
int language = settings.value("Settings/Language").toInt();
return static_cast<Language>(language);
}
inline QTranslator* CreateAndLoadTranslator(const QString& modulename)
{
QString filename;
switch (GetLanguage())
{
case Language::Chinese:
filename = "zh_CN/" + modulename + "_zh_CN.qm";
break;
// ... Other languages will be added later.
default:
break;
}
return LoadTranslator(filename);
}
inline QTranslator* LoadTranslator(const QString& filename)
{
QString translationFilePath;
QTranslator *translator = new QTranslator();
translator->load(translationFilePath);
qApp->installTranslator(translator);
return translator;
}
inline void UnloadTranslator(QTranslator* translator)
{
qApp->removeTranslator(translator);
delete translator;
translator = nullptr;
}
- Translators are loaded/unloaded in every module separately.
// Editor: Code/Editor/CryEdit.cpp
extern "C" int AZ_DLL_EXPORT CryEditMain(int argc, char* argv[])
{
// ...
Editor::EditorQtApplication::instance()->InstallEditorTranslators();
// ...
Editor::EditorQtApplication::instance()->UninstallEditorTranslators();
}
// Asset Processor: Code/Tools/AssetProcessor/native/utilities/GUIApplicationManager.cpp
bool GUIApplicationManager::Run()
{
// ...
InstallTranslators();
// ...
UninstallTranslators();
}
Step 3. Tag
There are three types of strings to translate:
- If the string is in a subclass of QObject, just wrap it with
tr().
- If the string is in an environment of QT, it can be wrapped with
QObject::tr() or SubclassOfQObject::tr(). In particular, QT can't be used in code of runtime.
- For other strings, tags should be added using
QT_TRANSLATE_NOOP macro.
#ifndef QT_TRANSLATE_NOOP
#define QT_TRANSLATE_NOOP(scope, x) (x)
#endif
Step 4. Extract
Strings tagged are extracted into corresponding TS files automatically.
- A cmake file is used to generate TS files automatically.
function(add_translation_module target_source_dir module_name)
if (PAL_TRAIT_BUILD_HOST_TOOLS)
find_package(Qt5 COMPONENTS LinguistTools REQUIRED) # Linguist
find_program(LUPDATE_EXECUTABLE lupdate PATHS "${Qt5_DIR}/../../../bin" REQUIRED) # lupdate
set(LANGUAGES zh_CN) # Other languages will be added later.
foreach(_language ${LANGUAGES})
execute_process(COMMAND ${LUPDATE_EXECUTABLE} ${target_source_dir} -ts ${TS_FILE} -silent)
endforeach()
endif()
endfunction()
- Then, call
add_translation_module in cmake file of every module.
add_translation_module(${CMAKE_CURRENT_SOURCE_DIR} Editor) // For Editor: Code/Editor/CMakeLists.txt
add_translation_module(${CMAKE_CURRENT_SOURCE_DIR} AssetProcessor) // For Asset Processor: Code/Tools/AssetProcessor/CMakeLists.txt
- After built, information of the tagged string with file path and line number will be added into TS files like Editor_zh_CN.ts and AssetProcessor_zh_CN.ts.
<message>
<location filename="../relative_path/Example.cpp" line="100"/>
<source>Hello</source>
<translation type="unfinished"></translation>
</message>
Step 5. Translate
TS files are in type of XML. Translations can be added manually or using Qt Linguist tool.
- Translation will be added into
<translation> </translation>, and the type will be removed indicating that translation of this string has been finished.
<message>
<location filename="../relative_path/Example.cpp" line="100"/>
<source>Hello</source>
<translation>你好</translation>
</message>
- Once a string in TS file is retranslated, the corresponding TS file needs to be reuploaded into the repository.
- Everytime the source code is recompiled, line numbers in TS files will be updated automatically. In this case, there is no need to reupload TS files.
After translation, TS files will be simplified and compiled into QM files with suffix .qm automatically. QM files are fast compact versions of TS files.
- A cmake file is used to generate QM files automatically.
find_package(Qt5 COMPONENTS LinguistTools REQUIRED) # Linguist
find_program(LRELEASE_EXECUTABLE lrelease PATHS "${Qt5_DIR}/../../../bin" REQUIRED) # lrelease
set(LANGUAGES zh_CN) # Other languages will be added later.
foreach(_language ${LANGUAGES})
foreach(_ts_file ${TS_FILES})
execute_process(COMMAND ${LRELEASE_EXECUTABLE} ${_ts_file} -qm "${QM_DIR}/${_language}/${QM_NAME}.qm" -silent)
endforeach()
endforeach()
- QM files are generated by build system and they don't need to be uploaded into the repository.
Step 6. Read
- Translations of strings tagged by
tr() or Object::tr() can be read straightly.
- Translations of strings tagged by
QT_TRANSLATE_NOOP macro need to be called by QCoreApplication::translate().
// Tag text and tooltip of the button.
void CEditorPreferencesPage_ViewportCamera::CameraMovementSettings::Reflect(AZ::SerializeContext& serialize)
{
editContext->Class<CameraMovementSettings>("Camera Movement Settings", "")
->DataElement(AZ::Edit::UIHandlers::Button, &CameraMovementSettings::m_resetButton, "", QT_TRANSLATE_NOOP("Reflect", "Restore camera movement settings to defaults"))
->Attribute(AZ::Edit::Attributes::ButtonText, QT_TRANSLATE_NOOP("Reflect", "Restore defaults"))
}
// Read translation of buttons' text.
void PropertyButtonCtrl::SetButtonText(const char* text)
{
m_button->setText(QCoreApplication::translate("Reflect", text));
}
// Read translation of buttons' tooltip.
void PropertyButtonCtrl::SetButtonToolTip(const char* description)
{
m_button->setToolTip(QCoreApplication::translate("Reflect", description));
}
Language Setting
A new setting for language is also needed in Editor Settings for users to switch language. It's designed like the setting named Console Background.
- It's added in
Editor Settings - General Editor Preferences - General Settings. Other languages will be added later.
- It's stored in Settings Registry.
Work Plan
- Firt PR: Basic architeture of localization & Chinese translation of Editor and Asset Processor
Tag strings in Editor and Asset Processor.
- Add cmake files to
generate translation files (TS and QM) automatically.
- Translate tagged strings into
Chinese.
- Provide back-end code to
load the translation files at startup and unload them at teardown.
- Add a setting for language in
Editor Settings.
- Follow-up documentation: Documents about how to use and how to contribute to this feature.
- Incremental PRs: Chinese translations of other modules like Gems.
- Translations in other languages contributed by the community.
What are the advantages of the feature?
- Users can choose their preferred language freely when use O3DE.
- The barrier to entry is lowered for those who had never done 3D product development before.
- Localization of other languages can be added easily in the future.
How will this be implemented or integrated into the O3DE environment?
CMake build files will be added in the first PR. No additional processing is required by developers.
How will users learn this feature?
Once this proposal is passed, documents will be provided for users and developers to learn this feature.
- For users, this feature can be added in USER GUIDE to let them know how to switch language on UI in O3DE.
- For developers, documents about how to add localization for other modules or other languages can be added in TOOLS UI.
Are there any open questions?
- How can developers add support for other languages?
- Once we support localization for Chinese, there will be no need to tag strings in the future. Developers just need to add language type in settings and translations in corresponding TS files to support localization for any other language.
- Is parameterized template supported in strings to be translated?
- Yes, it is. For example,
QString str = tr("Loading level %1 ...").arg(levelName) will be extracted and translated in TS file as:
<message>
<location filename="../CryEdit.cpp" line="1240"/>
<source>Loading level %1 ...</source>
<translation>正在加载关卡%1...</translation>
</message>
Potential User Experience Improvements
According to the user experience we have collected, there are two questions worth considered.
-
Editor needs to restart to change the display language currently. Since it takes a long time to launch Editor and Asset Processor, restarting may lead to poor user experience.
- Is there any way to achieve hot reload in O3DE to avoid restart when change genaral settings?
-
It's hard to translate some terminologies accurately for developers, and inappropriate translations may confuse users.
- Once hot reload is achieved, users wil be able to switch language to understand terminologies easily. But if not, is there any way to simplify users' operations when they want to know the meaning of the terminologies in other languages?
- Some experiment has been done in this scenario: Add description in English into tooltips when the display language is set to Chinese. However, this may lead to overly long tooltips and poor user experience. This will also increase the burden on developers who add translations.
// tooltip in English
Hello, O3DE
// tooltip in Chinese
Hello, O3DE
你好,ODE
- Another option is to add a button in every component which allows users to change the language partially.
Both of the above two questions need UX design and technical support.
-
Title of window's name is used as the key to store its layout. When the display language changed, key of the window will also change and information of its layout will become invalid.
// Information of layout in English
["Asset Browser", struct_of_layout]
// Information of layout in English
["资产浏览器", struct_of_layout]
- Is it possible to decouple the identifier and title of every module?
Summary:
This feature proposes to implement internationalization in O3DE to support other languages besides English on UI.
What is the relevance of this feature?
O3DE only supports English on UI currently, which is not so friendly to users from other countries. Especially for new users who had never done 3D product development before, support for their native language might help them start with O3DE and understand the terminologies more easily.
The proposed solution implements internationalization support in O3DE. After this work, developers only need to add a new language type and translate strings that have already been extracted to support any other language as they want.
Feature design description:
Mechanism of QT's internationalization support is used in this feature. The basic flow is as follows (Chinese translation for example).
lupdatein QT to extract tagged strings into translation files with .ts suffix.Directory Structure:
Information of the tagged string with file path and line number will be added into TS file.
LinguistTool in QT to translate tagged strings in translation files. Developers can also modify TS files directly.lreleasein QT to compile the translation files with .qm suffix.Directory Structure:
qtbaseis added to locate translations for basic Qt modules, such as Qt Core, Qt GUI, Qt Network, and Qt WidgetsQStringList translatorNames = { "Editor", "qtbase", // More modules like AssetProcessor, AzToolsFramework, and every Gem... };Technical design description:
Internationalization with QT
Internationalization with QT supports most languages in use today, and and will work on all platforms as long as the system has fonts to render these writing systems installed.
Following APIs are mainly used:
Three tools in QT are used to generate translation files automatically:
Implementation in O3DE
Currently, some strings to be translated have been tagged using Object::tr(). Translators are loaded in EditorQtApplication::Initialize().
However, the current implementation only supports localization in Editor and Asset Browser, and strings not in subclasses of QObject can't be translated.
Step 1. Set Translators
Based on what has been done in O3DE, this proposal uses
m_translatorswith typeQVectorto store translators.Step 2. Load & Unload Translators
Since the startup time of each process are actually different, translators are loaded in every module separately.
Translation.h.Step 3. Tag
There are three types of strings to translate:
tr().QObject::tr()orSubclassOfQObject::tr(). In particular, QT can't be used in code of runtime.QT_TRANSLATE_NOOPmacro.Step 4. Extract
Strings tagged are extracted into corresponding TS files automatically.
add_translation_modulein cmake file of every module.Step 5. Translate
TS files are in type of XML. Translations can be added manually or using
Qt Linguisttool.<translation> </translation>, and the type will be removed indicating that translation of this string has been finished.After translation, TS files will be simplified and compiled into QM files with suffix
.qmautomatically. QM files are fast compact versions of TS files.Step 6. Read
tr()orObject::tr()can be read straightly.QT_TRANSLATE_NOOPmacro need to be called byQCoreApplication::translate().Language Setting
A new setting for language is also needed in
Editor Settingsfor users to switch language. It's designed like the setting namedConsole Background.Editor Settings-General Editor Preferences-General Settings. Other languages will be added later.Work Plan
Tag stringsin Editor and Asset Processor.generate translation files(TS and QM) automatically.Chinese.loadthe translation files at startup andunloadthem at teardown.Editor Settings.What are the advantages of the feature?
How will this be implemented or integrated into the O3DE environment?
CMake build files will be added in the first PR. No additional processing is required by developers.
How will users learn this feature?
Once this proposal is passed, documents will be provided for users and developers to learn this feature.
Are there any open questions?
QString str = tr("Loading level %1 ...").arg(levelName)will be extracted and translated in TS file as:Potential User Experience Improvements
According to the user experience we have collected, there are two questions worth considered.
Editor needs to restart to change the display language currently. Since it takes a long time to launch Editor and Asset Processor, restarting may lead to poor user experience.
It's hard to translate some terminologies accurately for developers, and inappropriate translations may confuse users.
Both of the above two questions need UX design and technical support.
Title of window's name is used as the key to store its layout. When the display language changed, key of the window will also change and information of its layout will become invalid.