Move pseudolocalization into TranslationDomain

Also adds command-line option `--editor-pseudolocalization`
This commit is contained in:
Haoyu Qiu 2024-08-28 23:03:23 +08:00
parent e4e024ab88
commit cca54ba4db
7 changed files with 421 additions and 236 deletions

View file

@ -33,6 +33,170 @@
#include "core/string/translation.h"
#include "core/string/translation_server.h"
struct _character_accent_pair {
const char32_t character;
const char32_t *accented_character;
};
static _character_accent_pair _character_to_accented[] = {
{ 'A', U"Å" },
{ 'B', U"ß" },
{ 'C', U"Ç" },
{ 'D', U"Ð" },
{ 'E', U"É" },
{ 'F', U"" },
{ 'G', U"Ĝ" },
{ 'H', U"Ĥ" },
{ 'I', U"Ĩ" },
{ 'J', U"Ĵ" },
{ 'K', U"ĸ" },
{ 'L', U"Ł" },
{ 'M', U"" },
{ 'N', U"й" },
{ 'O', U"Ö" },
{ 'P', U"" },
{ 'Q', U"" },
{ 'R', U"Ř" },
{ 'S', U"Ŝ" },
{ 'T', U"Ŧ" },
{ 'U', U"Ũ" },
{ 'V', U"" },
{ 'W', U"Ŵ" },
{ 'X', U"" },
{ 'Y', U"Ÿ" },
{ 'Z', U"Ž" },
{ 'a', U"á" },
{ 'b', U"" },
{ 'c', U"ć" },
{ 'd', U"" },
{ 'e', U"é" },
{ 'f', U"" },
{ 'g', U"ǵ" },
{ 'h', U"" },
{ 'i', U"í" },
{ 'j', U"ǰ" },
{ 'k', U"" },
{ 'l', U"ł" },
{ 'm', U"" },
{ 'n', U"" },
{ 'o', U"ô" },
{ 'p', U"" },
{ 'q', U"" },
{ 'r', U"ŕ" },
{ 's', U"š" },
{ 't', U"ŧ" },
{ 'u', U"ü" },
{ 'v', U"" },
{ 'w', U"ŵ" },
{ 'x', U"" },
{ 'y', U"ý" },
{ 'z', U"ź" },
};
String TranslationDomain::_get_override_string(const String &p_message) const {
String res;
for (int i = 0; i < p_message.length(); i++) {
if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
res += p_message[i];
res += p_message[i + 1];
i++;
continue;
}
res += '*';
}
return res;
}
String TranslationDomain::_double_vowels(const String &p_message) const {
String res;
for (int i = 0; i < p_message.length(); i++) {
if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
res += p_message[i];
res += p_message[i + 1];
i++;
continue;
}
res += p_message[i];
if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
res += p_message[i];
}
}
return res;
};
String TranslationDomain::_replace_with_accented_string(const String &p_message) const {
String res;
for (int i = 0; i < p_message.length(); i++) {
if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
res += p_message[i];
res += p_message[i + 1];
i++;
continue;
}
const char32_t *accented = _get_accented_version(p_message[i]);
if (accented) {
res += accented;
} else {
res += p_message[i];
}
}
return res;
}
String TranslationDomain::_wrap_with_fakebidi_characters(const String &p_message) const {
String res;
char32_t fakebidiprefix = U'\u202e';
char32_t fakebidisuffix = U'\u202c';
res += fakebidiprefix;
// The fake bidi unicode gets popped at every newline so pushing it back at every newline.
for (int i = 0; i < p_message.length(); i++) {
if (p_message[i] == '\n') {
res += fakebidisuffix;
res += p_message[i];
res += fakebidiprefix;
} else if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
res += fakebidisuffix;
res += p_message[i];
res += p_message[i + 1];
res += fakebidiprefix;
i++;
} else {
res += p_message[i];
}
}
res += fakebidisuffix;
return res;
}
String TranslationDomain::_add_padding(const String &p_message, int p_length) const {
String underscores = String("_").repeat(p_length * pseudolocalization.expansion_ratio / 2);
String prefix = pseudolocalization.prefix + underscores;
String suffix = underscores + pseudolocalization.suffix;
return prefix + p_message + suffix;
}
const char32_t *TranslationDomain::_get_accented_version(char32_t p_character) const {
if (!is_ascii_alphabet_char(p_character)) {
return nullptr;
}
for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
if (_character_to_accented[i].character == p_character) {
return _character_to_accented[i].accented_character;
}
}
return nullptr;
}
bool TranslationDomain::_is_placeholder(const String &p_message, int p_index) const {
return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
(p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
}
StringName TranslationDomain::get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_context) const {
StringName res;
int best_score = 0;
@ -129,9 +293,9 @@ StringName TranslationDomain::translate(const StringName &p_message, const Strin
}
if (!res) {
return p_message;
return pseudolocalization.enabled ? pseudolocalize(p_message) : p_message;
}
return res;
return pseudolocalization.enabled ? pseudolocalize(res) : res;
}
StringName TranslationDomain::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
@ -152,6 +316,100 @@ StringName TranslationDomain::translate_plural(const StringName &p_message, cons
return res;
}
bool TranslationDomain::is_pseudolocalization_enabled() const {
return pseudolocalization.enabled;
}
void TranslationDomain::set_pseudolocalization_enabled(bool p_enabled) {
pseudolocalization.enabled = p_enabled;
}
bool TranslationDomain::is_pseudolocalization_accents_enabled() const {
return pseudolocalization.accents_enabled;
}
void TranslationDomain::set_pseudolocalization_accents_enabled(bool p_enabled) {
pseudolocalization.accents_enabled = p_enabled;
}
bool TranslationDomain::is_pseudolocalization_double_vowels_enabled() const {
return pseudolocalization.double_vowels_enabled;
}
void TranslationDomain::set_pseudolocalization_double_vowels_enabled(bool p_enabled) {
pseudolocalization.double_vowels_enabled = p_enabled;
}
bool TranslationDomain::is_pseudolocalization_fake_bidi_enabled() const {
return pseudolocalization.fake_bidi_enabled;
}
void TranslationDomain::set_pseudolocalization_fake_bidi_enabled(bool p_enabled) {
pseudolocalization.fake_bidi_enabled = p_enabled;
}
bool TranslationDomain::is_pseudolocalization_override_enabled() const {
return pseudolocalization.override_enabled;
}
void TranslationDomain::set_pseudolocalization_override_enabled(bool p_enabled) {
pseudolocalization.override_enabled = p_enabled;
}
bool TranslationDomain::is_pseudolocalization_skip_placeholders_enabled() const {
return pseudolocalization.skip_placeholders_enabled;
}
void TranslationDomain::set_pseudolocalization_skip_placeholders_enabled(bool p_enabled) {
pseudolocalization.skip_placeholders_enabled = p_enabled;
}
float TranslationDomain::get_pseudolocalization_expansion_ratio() const {
return pseudolocalization.expansion_ratio;
}
void TranslationDomain::set_pseudolocalization_expansion_ratio(float p_ratio) {
pseudolocalization.expansion_ratio = p_ratio;
}
String TranslationDomain::get_pseudolocalization_prefix() const {
return pseudolocalization.prefix;
}
void TranslationDomain::set_pseudolocalization_prefix(const String &p_prefix) {
pseudolocalization.prefix = p_prefix;
}
String TranslationDomain::get_pseudolocalization_suffix() const {
return pseudolocalization.suffix;
}
void TranslationDomain::set_pseudolocalization_suffix(const String &p_suffix) {
pseudolocalization.suffix = p_suffix;
}
StringName TranslationDomain::pseudolocalize(const StringName &p_message) const {
String message = p_message;
int length = message.length();
if (pseudolocalization.override_enabled) {
message = _get_override_string(message);
}
if (pseudolocalization.double_vowels_enabled) {
message = _double_vowels(message);
}
if (pseudolocalization.accents_enabled) {
message = _replace_with_accented_string(message);
}
if (pseudolocalization.fake_bidi_enabled) {
message = _wrap_with_fakebidi_characters(message);
}
return _add_padding(message, length);
}
void TranslationDomain::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationDomain::get_translation_object);
ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationDomain::add_translation);
@ -159,6 +417,36 @@ void TranslationDomain::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear"), &TranslationDomain::clear);
ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationDomain::translate, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("translate_plural", "message", "message_plural", "n", "context"), &TranslationDomain::translate_plural, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationDomain::is_pseudolocalization_enabled);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_enabled);
ClassDB::bind_method(D_METHOD("is_pseudolocalization_accents_enabled"), &TranslationDomain::is_pseudolocalization_accents_enabled);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_accents_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_accents_enabled);
ClassDB::bind_method(D_METHOD("is_pseudolocalization_double_vowels_enabled"), &TranslationDomain::is_pseudolocalization_double_vowels_enabled);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_double_vowels_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_double_vowels_enabled);
ClassDB::bind_method(D_METHOD("is_pseudolocalization_fake_bidi_enabled"), &TranslationDomain::is_pseudolocalization_fake_bidi_enabled);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_fake_bidi_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_fake_bidi_enabled);
ClassDB::bind_method(D_METHOD("is_pseudolocalization_override_enabled"), &TranslationDomain::is_pseudolocalization_override_enabled);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_override_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_override_enabled);
ClassDB::bind_method(D_METHOD("is_pseudolocalization_skip_placeholders_enabled"), &TranslationDomain::is_pseudolocalization_skip_placeholders_enabled);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_skip_placeholders_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_skip_placeholders_enabled);
ClassDB::bind_method(D_METHOD("get_pseudolocalization_expansion_ratio"), &TranslationDomain::get_pseudolocalization_expansion_ratio);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_expansion_ratio", "ratio"), &TranslationDomain::set_pseudolocalization_expansion_ratio);
ClassDB::bind_method(D_METHOD("get_pseudolocalization_prefix"), &TranslationDomain::get_pseudolocalization_prefix);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_prefix", "prefix"), &TranslationDomain::set_pseudolocalization_prefix);
ClassDB::bind_method(D_METHOD("get_pseudolocalization_suffix"), &TranslationDomain::get_pseudolocalization_suffix);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_suffix", "suffix"), &TranslationDomain::set_pseudolocalization_suffix);
ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationDomain::pseudolocalize);
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_accents_enabled"), "set_pseudolocalization_accents_enabled", "is_pseudolocalization_accents_enabled");
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_double_vowels_enabled"), "set_pseudolocalization_double_vowels_enabled", "is_pseudolocalization_double_vowels_enabled");
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_fake_bidi_enabled"), "set_pseudolocalization_fake_bidi_enabled", "is_pseudolocalization_fake_bidi_enabled");
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_override_enabled"), "set_pseudolocalization_override_enabled", "is_pseudolocalization_override_enabled");
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_skip_placeholders_enabled"), "set_pseudolocalization_skip_placeholders_enabled", "is_pseudolocalization_skip_placeholders_enabled");
ADD_PROPERTY(PropertyInfo(Variant::Type::FLOAT, "pseudolocalization_expansion_ratio"), "set_pseudolocalization_expansion_ratio", "get_pseudolocalization_expansion_ratio");
ADD_PROPERTY(PropertyInfo(Variant::Type::STRING, "pseudolocalization_prefix"), "set_pseudolocalization_prefix", "get_pseudolocalization_prefix");
ADD_PROPERTY(PropertyInfo(Variant::Type::STRING, "pseudolocalization_suffix"), "set_pseudolocalization_suffix", "get_pseudolocalization_suffix");
}
TranslationDomain::TranslationDomain() {

View file

@ -38,7 +38,28 @@ class Translation;
class TranslationDomain : public RefCounted {
GDCLASS(TranslationDomain, RefCounted);
struct PseudolocalizationConfig {
bool enabled = false;
bool accents_enabled = true;
bool double_vowels_enabled = false;
bool fake_bidi_enabled = false;
bool override_enabled = false;
bool skip_placeholders_enabled = true;
float expansion_ratio = 0.0;
String prefix = "[";
String suffix = "]";
};
HashSet<Ref<Translation>> translations;
PseudolocalizationConfig pseudolocalization;
String _get_override_string(const String &p_message) const;
String _double_vowels(const String &p_message) const;
String _replace_with_accented_string(const String &p_message) const;
String _wrap_with_fakebidi_characters(const String &p_message) const;
String _add_padding(const String &p_message, int p_length) const;
const char32_t *_get_accented_version(char32_t p_character) const;
bool _is_placeholder(const String &p_message, int p_index) const;
protected:
static void _bind_methods();
@ -59,6 +80,27 @@ public:
StringName translate(const StringName &p_message, const StringName &p_context) const;
StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const;
bool is_pseudolocalization_enabled() const;
void set_pseudolocalization_enabled(bool p_enabled);
bool is_pseudolocalization_accents_enabled() const;
void set_pseudolocalization_accents_enabled(bool p_enabled);
bool is_pseudolocalization_double_vowels_enabled() const;
void set_pseudolocalization_double_vowels_enabled(bool p_enabled);
bool is_pseudolocalization_fake_bidi_enabled() const;
void set_pseudolocalization_fake_bidi_enabled(bool p_enabled);
bool is_pseudolocalization_override_enabled() const;
void set_pseudolocalization_override_enabled(bool p_enabled);
bool is_pseudolocalization_skip_placeholders_enabled() const;
void set_pseudolocalization_skip_placeholders_enabled(bool p_enabled);
float get_pseudolocalization_expansion_ratio() const;
void set_pseudolocalization_expansion_ratio(float p_ratio);
String get_pseudolocalization_prefix() const;
void set_pseudolocalization_prefix(const String &p_prefix);
String get_pseudolocalization_suffix() const;
void set_pseudolocalization_suffix(const String &p_suffix);
StringName pseudolocalize(const StringName &p_message) const;
TranslationDomain();
};

View file

@ -39,66 +39,6 @@
#include "main/main.h"
#endif
struct _character_accent_pair {
const char32_t character;
const char32_t *accented_character;
};
static _character_accent_pair _character_to_accented[] = {
{ 'A', U"Å" },
{ 'B', U"ß" },
{ 'C', U"Ç" },
{ 'D', U"Ð" },
{ 'E', U"É" },
{ 'F', U"" },
{ 'G', U"Ĝ" },
{ 'H', U"Ĥ" },
{ 'I', U"Ĩ" },
{ 'J', U"Ĵ" },
{ 'K', U"ĸ" },
{ 'L', U"Ł" },
{ 'M', U"" },
{ 'N', U"й" },
{ 'O', U"Ö" },
{ 'P', U"" },
{ 'Q', U"" },
{ 'R', U"Ř" },
{ 'S', U"Ŝ" },
{ 'T', U"Ŧ" },
{ 'U', U"Ũ" },
{ 'V', U"" },
{ 'W', U"Ŵ" },
{ 'X', U"" },
{ 'Y', U"Ÿ" },
{ 'Z', U"Ž" },
{ 'a', U"á" },
{ 'b', U"" },
{ 'c', U"ć" },
{ 'd', U"" },
{ 'e', U"é" },
{ 'f', U"" },
{ 'g', U"ǵ" },
{ 'h', U"" },
{ 'i', U"í" },
{ 'j', U"ǰ" },
{ 'k', U"" },
{ 'l', U"ł" },
{ 'm', U"" },
{ 'n', U"" },
{ 'o', U"ô" },
{ 'p', U"" },
{ 'q', U"" },
{ 'r', U"ŕ" },
{ 's', U"š" },
{ 't', U"ŧ" },
{ 'u', U"ü" },
{ 'v', U"" },
{ 'w', U"ŵ" },
{ 'x', U"" },
{ 'y', U"ý" },
{ 'z', U"ź" },
};
Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
HashMap<String, String> TranslationServer::language_map;
@ -433,8 +373,7 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin
return p_message;
}
const StringName res = main_domain->translate(p_message, p_context);
return pseudolocalization_enabled ? pseudolocalize(res) : res;
return main_domain->translate(p_message, p_context);
}
StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
@ -510,15 +449,15 @@ void TranslationServer::setup() {
}
fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false);
pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false);
pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false);
expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
main_domain->set_pseudolocalization_enabled(GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false));
main_domain->set_pseudolocalization_accents_enabled(GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true));
main_domain->set_pseudolocalization_double_vowels_enabled(GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false));
main_domain->set_pseudolocalization_fake_bidi_enabled(GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false));
main_domain->set_pseudolocalization_override_enabled(GLOBAL_DEF("internationalization/pseudolocalization/override", false));
main_domain->set_pseudolocalization_expansion_ratio(GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0));
main_domain->set_pseudolocalization_prefix(GLOBAL_DEF("internationalization/pseudolocalization/prefix", "["));
main_domain->set_pseudolocalization_suffix(GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]"));
main_domain->set_pseudolocalization_skip_placeholders_enabled(GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true));
#ifdef TOOLS_ENABLED
ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
@ -567,11 +506,11 @@ StringName TranslationServer::doc_translate_plural(const StringName &p_message,
}
bool TranslationServer::is_pseudolocalization_enabled() const {
return pseudolocalization_enabled;
return main_domain->is_pseudolocalization_enabled();
}
void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
pseudolocalization_enabled = p_enabled;
main_domain->set_pseudolocalization_enabled(p_enabled);
ResourceLoader::reload_translation_remaps();
@ -581,14 +520,14 @@ void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
}
void TranslationServer::reload_pseudolocalization() {
pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents");
pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels");
pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi");
pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override");
expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
main_domain->set_pseudolocalization_accents_enabled(GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents"));
main_domain->set_pseudolocalization_double_vowels_enabled(GLOBAL_GET("internationalization/pseudolocalization/double_vowels"));
main_domain->set_pseudolocalization_fake_bidi_enabled(GLOBAL_GET("internationalization/pseudolocalization/fake_bidi"));
main_domain->set_pseudolocalization_override_enabled(GLOBAL_GET("internationalization/pseudolocalization/override"));
main_domain->set_pseudolocalization_expansion_ratio(GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio"));
main_domain->set_pseudolocalization_prefix(GLOBAL_GET("internationalization/pseudolocalization/prefix"));
main_domain->set_pseudolocalization_suffix(GLOBAL_GET("internationalization/pseudolocalization/suffix"));
main_domain->set_pseudolocalization_skip_placeholders_enabled(GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders"));
ResourceLoader::reload_translation_remaps();
@ -598,138 +537,7 @@ void TranslationServer::reload_pseudolocalization() {
}
StringName TranslationServer::pseudolocalize(const StringName &p_message) const {
String message = p_message;
int length = message.length();
if (pseudolocalization_override_enabled) {
message = get_override_string(message);
}
if (pseudolocalization_double_vowels_enabled) {
message = double_vowels(message);
}
if (pseudolocalization_accents_enabled) {
message = replace_with_accented_string(message);
}
if (pseudolocalization_fake_bidi_enabled) {
message = wrap_with_fakebidi_characters(message);
}
StringName res = add_padding(message, length);
return res;
}
StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const {
String message = p_message;
message = double_vowels(message);
message = replace_with_accented_string(message);
StringName res = "[!!! " + message + " !!!]";
return res;
}
String TranslationServer::get_override_string(String &p_message) const {
String res;
for (int i = 0; i < p_message.length(); i++) {
if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
res += p_message[i];
res += p_message[i + 1];
i++;
continue;
}
res += '*';
}
return res;
}
String TranslationServer::double_vowels(String &p_message) const {
String res;
for (int i = 0; i < p_message.length(); i++) {
if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
res += p_message[i];
res += p_message[i + 1];
i++;
continue;
}
res += p_message[i];
if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
res += p_message[i];
}
}
return res;
};
String TranslationServer::replace_with_accented_string(String &p_message) const {
String res;
for (int i = 0; i < p_message.length(); i++) {
if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
res += p_message[i];
res += p_message[i + 1];
i++;
continue;
}
const char32_t *accented = get_accented_version(p_message[i]);
if (accented) {
res += accented;
} else {
res += p_message[i];
}
}
return res;
}
String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
String res;
char32_t fakebidiprefix = U'\u202e';
char32_t fakebidisuffix = U'\u202c';
res += fakebidiprefix;
// The fake bidi unicode gets popped at every newline so pushing it back at every newline.
for (int i = 0; i < p_message.length(); i++) {
if (p_message[i] == '\n') {
res += fakebidisuffix;
res += p_message[i];
res += fakebidiprefix;
} else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
res += fakebidisuffix;
res += p_message[i];
res += p_message[i + 1];
res += fakebidiprefix;
i++;
} else {
res += p_message[i];
}
}
res += fakebidisuffix;
return res;
}
String TranslationServer::add_padding(const String &p_message, int p_length) const {
String underscores = String("_").repeat(p_length * expansion_ratio / 2);
String prefix = pseudolocalization_prefix + underscores;
String suffix = underscores + pseudolocalization_suffix;
return prefix + p_message + suffix;
}
const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
if (!is_ascii_alphabet_char(p_character)) {
return nullptr;
}
for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
if (_character_to_accented[i].character == p_character) {
return _character_to_accented[i].accented_character;
}
}
return nullptr;
}
bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
(p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
return main_domain->pseudolocalize(p_message);
}
#ifdef TOOLS_ENABLED

View file

@ -48,25 +48,6 @@ class TranslationServer : public Object {
bool enabled = true;
bool pseudolocalization_enabled = false;
bool pseudolocalization_accents_enabled = false;
bool pseudolocalization_double_vowels_enabled = false;
bool pseudolocalization_fake_bidi_enabled = false;
bool pseudolocalization_override_enabled = false;
bool pseudolocalization_skip_placeholders_enabled = false;
float expansion_ratio = 0.0;
String pseudolocalization_prefix;
String pseudolocalization_suffix;
StringName tool_pseudolocalize(const StringName &p_message) const;
String get_override_string(String &p_message) const;
String double_vowels(String &p_message) const;
String replace_with_accented_string(String &p_message) const;
String wrap_with_fakebidi_characters(String &p_message) const;
String add_padding(const String &p_message, int p_length) const;
const char32_t *get_accented_version(char32_t p_character) const;
bool is_placeholder(String &p_message, int p_index) const;
static TranslationServer *singleton;
bool _load_translations(const String &p_from);
String _standardize_locale(const String &p_locale, bool p_add_defaults) const;
@ -93,6 +74,8 @@ class TranslationServer : public Object {
public:
_FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
Ref<TranslationDomain> get_editor_domain() const { return editor_domain; }
void set_enabled(bool p_enabled) { enabled = p_enabled; }
_FORCE_INLINE_ bool is_enabled() const { return enabled; }

View file

@ -30,6 +30,13 @@
Returns the [Translation] instance that best matches [param locale]. Returns [code]null[/code] if there are no matches.
</description>
</method>
<method name="pseudolocalize" qualifiers="const">
<return type="StringName" />
<param index="0" name="message" type="StringName" />
<description>
Returns the pseudolocalized string based on the [param message] passed in.
</description>
</method>
<method name="remove_translation">
<return type="void" />
<param index="0" name="translation" type="Translation" />
@ -57,4 +64,42 @@
</description>
</method>
</methods>
<members>
<member name="pseudolocalization_accents_enabled" type="bool" setter="set_pseudolocalization_accents_enabled" getter="is_pseudolocalization_accents_enabled" default="true">
Replace all characters with their accented variants during pseudolocalization.
[b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
</member>
<member name="pseudolocalization_double_vowels_enabled" type="bool" setter="set_pseudolocalization_double_vowels_enabled" getter="is_pseudolocalization_double_vowels_enabled" default="false">
Double vowels in strings during pseudolocalization to simulate the lengthening of text due to localization.
[b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
</member>
<member name="pseudolocalization_enabled" type="bool" setter="set_pseudolocalization_enabled" getter="is_pseudolocalization_enabled" default="false">
If [code]true[/code], enables pseudolocalization for the project. This can be used to spot untranslatable strings or layout issues that may occur once the project is localized to languages that have longer strings than the source language.
[b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
</member>
<member name="pseudolocalization_expansion_ratio" type="float" setter="set_pseudolocalization_expansion_ratio" getter="get_pseudolocalization_expansion_ratio" default="0.0">
The expansion ratio to use during pseudolocalization. A value of [code]0.3[/code] is sufficient for most practical purposes, and will increase the length of each string by 30%.
[b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
</member>
<member name="pseudolocalization_fake_bidi_enabled" type="bool" setter="set_pseudolocalization_fake_bidi_enabled" getter="is_pseudolocalization_fake_bidi_enabled" default="false">
If [code]true[/code], emulate bidirectional (right-to-left) text when pseudolocalization is enabled. This can be used to spot issues with RTL layout and UI mirroring that will crop up if the project is localized to RTL languages such as Arabic or Hebrew.
[b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
</member>
<member name="pseudolocalization_override_enabled" type="bool" setter="set_pseudolocalization_override_enabled" getter="is_pseudolocalization_override_enabled" default="false">
Replace all characters in the string with [code]*[/code]. Useful for finding non-localizable strings.
[b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
</member>
<member name="pseudolocalization_prefix" type="String" setter="set_pseudolocalization_prefix" getter="get_pseudolocalization_prefix" default="&quot;[&quot;">
Prefix that will be prepended to the pseudolocalized string.
[b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
</member>
<member name="pseudolocalization_skip_placeholders_enabled" type="bool" setter="set_pseudolocalization_skip_placeholders_enabled" getter="is_pseudolocalization_skip_placeholders_enabled" default="true">
Skip placeholders for string formatting like [code]%s[/code] or [code]%f[/code] during pseudolocalization. Useful to identify strings which need additional control characters to display correctly.
[b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
</member>
<member name="pseudolocalization_suffix" type="String" setter="set_pseudolocalization_suffix" getter="get_pseudolocalization_suffix" default="&quot;]&quot;">
Suffix that will be appended to the pseudolocalized string.
[b]Note:[/b] Updating this property does not automatically update texts in the scene tree. Please propagate the [constant MainLoop.NOTIFICATION_TRANSLATION_CHANGED] notification manually after you have finished modifying pseudolocalization related options.
</member>
</members>
</class>

View file

@ -125,12 +125,13 @@
<param index="0" name="message" type="StringName" />
<description>
Returns the pseudolocalized string based on the [param message] passed in.
[b]Note:[/b] This method always uses the main translation domain.
</description>
</method>
<method name="reload_pseudolocalization">
<return type="void" />
<description>
Reparses the pseudolocalization options and reloads the translation.
Reparses the pseudolocalization options and reloads the translation for the main translation domain.
</description>
</method>
<method name="remove_domain">
@ -187,7 +188,7 @@
</methods>
<members>
<member name="pseudolocalization_enabled" type="bool" setter="set_pseudolocalization_enabled" getter="is_pseudolocalization_enabled" default="false">
If [code]true[/code], enables the use of pseudolocalization. See [member ProjectSettings.internationalization/pseudolocalization/use_pseudolocalization] for details.
If [code]true[/code], enables the use of pseudolocalization on the main translation domain. See [member ProjectSettings.internationalization/pseudolocalization/use_pseudolocalization] for details.
</member>
</members>
</class>

View file

@ -243,6 +243,7 @@ static MovieWriter *movie_writer = nullptr;
static bool disable_vsync = false;
static bool print_fps = false;
#ifdef TOOLS_ENABLED
static bool editor_pseudolocalization = false;
static bool dump_gdextension_interface = false;
static bool dump_extension_api = false;
static bool include_docs_in_extension_api_dump = false;
@ -629,6 +630,9 @@ void Main::print_help(const char *p_binary) {
print_help_option("--fixed-fps <fps>", "Force a fixed number of frames per second. This setting disables real-time synchronization.\n");
print_help_option("--delta-smoothing <enable>", "Enable or disable frame delta smoothing [\"enable\", \"disable\"].\n");
print_help_option("--print-fps", "Print the frames per second to the stdout.\n");
#ifdef TOOLS_ENABLED
print_help_option("--editor-pseudolocalization", "Enable pseudolocalization for the editor and the project manager.\n");
#endif
print_help_title("Standalone tools");
print_help_option("-s, --script <script>", "Run a script.\n");
@ -1683,6 +1687,10 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
disable_vsync = true;
} else if (arg == "--print-fps") {
print_fps = true;
#ifdef TOOLS_ENABLED
} else if (arg == "--editor-pseudolocalization") {
editor_pseudolocalization = true;
#endif // TOOLS_ENABLED
} else if (arg == "--profile-gpu") {
profile_gpu = true;
} else if (arg == "--disable-crash-handler") {
@ -3963,6 +3971,11 @@ int Main::start() {
if (editor) {
OS::get_singleton()->benchmark_begin_measure("Startup", "Editor");
editor_node = memnew(EditorNode);
if (editor_pseudolocalization) {
translation_server->get_editor_domain()->set_pseudolocalization_enabled(true);
}
sml->get_root()->add_child(editor_node);
if (!_export_preset.is_empty()) {
@ -4158,6 +4171,11 @@ int Main::start() {
ProjectManager *pmanager = memnew(ProjectManager);
ProgressDialog *progress_dialog = memnew(ProgressDialog);
pmanager->add_child(progress_dialog);
if (editor_pseudolocalization) {
translation_server->get_editor_domain()->set_pseudolocalization_enabled(true);
}
sml->get_root()->add_child(pmanager);
OS::get_singleton()->benchmark_end_measure("Startup", "Project Manager");
}