Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/core/string/translation_domain.cpp
20982 views
1
/**************************************************************************/
2
/* translation_domain.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "translation_domain.h"
32
33
#include "core/string/translation.h"
34
#include "core/string/translation_server.h"
35
#include "core/variant/typed_array.h"
36
37
struct _character_accent_pair {
38
const char32_t character;
39
const char32_t *accented_character;
40
};
41
42
static _character_accent_pair _character_to_accented[] = {
43
{ 'A', U"Å" },
44
{ 'B', U"ß" },
45
{ 'C', U"Ç" },
46
{ 'D', U"Ð" },
47
{ 'E', U"É" },
48
{ 'F', U"F́" },
49
{ 'G', U"Ĝ" },
50
{ 'H', U"Ĥ" },
51
{ 'I', U"Ĩ" },
52
{ 'J', U"Ĵ" },
53
{ 'K', U"ĸ" },
54
{ 'L', U"Ł" },
55
{ 'M', U"Ḿ" },
56
{ 'N', U"й" },
57
{ 'O', U"Ö" },
58
{ 'P', U"Ṕ" },
59
{ 'Q', U"Q́" },
60
{ 'R', U"Ř" },
61
{ 'S', U"Ŝ" },
62
{ 'T', U"Ŧ" },
63
{ 'U', U"Ũ" },
64
{ 'V', U"Ṽ" },
65
{ 'W', U"Ŵ" },
66
{ 'X', U"X́" },
67
{ 'Y', U"Ÿ" },
68
{ 'Z', U"Ž" },
69
{ 'a', U"á" },
70
{ 'b', U"ḅ" },
71
{ 'c', U"ć" },
72
{ 'd', U"d́" },
73
{ 'e', U"é" },
74
{ 'f', U"f́" },
75
{ 'g', U"ǵ" },
76
{ 'h', U"h̀" },
77
{ 'i', U"í" },
78
{ 'j', U"ǰ" },
79
{ 'k', U"ḱ" },
80
{ 'l', U"ł" },
81
{ 'm', U"m̀" },
82
{ 'n', U"ή" },
83
{ 'o', U"ô" },
84
{ 'p', U"ṕ" },
85
{ 'q', U"q́" },
86
{ 'r', U"ŕ" },
87
{ 's', U"š" },
88
{ 't', U"ŧ" },
89
{ 'u', U"ü" },
90
{ 'v', U"ṽ" },
91
{ 'w', U"ŵ" },
92
{ 'x', U"x́" },
93
{ 'y', U"ý" },
94
{ 'z', U"ź" },
95
};
96
97
String TranslationDomain::_get_override_string(const String &p_message) const {
98
String res;
99
for (int i = 0; i < p_message.length(); i++) {
100
if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
101
res += p_message[i];
102
res += p_message[i + 1];
103
i++;
104
continue;
105
}
106
res += '*';
107
}
108
return res;
109
}
110
111
String TranslationDomain::_double_vowels(const String &p_message) const {
112
String res;
113
for (int i = 0; i < p_message.length(); i++) {
114
if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
115
res += p_message[i];
116
res += p_message[i + 1];
117
i++;
118
continue;
119
}
120
res += p_message[i];
121
if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
122
p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
123
res += p_message[i];
124
}
125
}
126
return res;
127
}
128
129
String TranslationDomain::_replace_with_accented_string(const String &p_message) const {
130
String res;
131
for (int i = 0; i < p_message.length(); i++) {
132
if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
133
res += p_message[i];
134
res += p_message[i + 1];
135
i++;
136
continue;
137
}
138
const char32_t *accented = _get_accented_version(p_message[i]);
139
if (accented) {
140
res += accented;
141
} else {
142
res += p_message[i];
143
}
144
}
145
return res;
146
}
147
148
String TranslationDomain::_wrap_with_fakebidi_characters(const String &p_message) const {
149
String res;
150
char32_t fakebidiprefix = U'\u202e';
151
char32_t fakebidisuffix = U'\u202c';
152
res += fakebidiprefix;
153
// The fake bidi unicode gets popped at every newline so pushing it back at every newline.
154
for (int i = 0; i < p_message.length(); i++) {
155
if (p_message[i] == '\n') {
156
res += fakebidisuffix;
157
res += p_message[i];
158
res += fakebidiprefix;
159
} else if (pseudolocalization.skip_placeholders_enabled && _is_placeholder(p_message, i)) {
160
res += fakebidisuffix;
161
res += p_message[i];
162
res += p_message[i + 1];
163
res += fakebidiprefix;
164
i++;
165
} else {
166
res += p_message[i];
167
}
168
}
169
res += fakebidisuffix;
170
return res;
171
}
172
173
String TranslationDomain::_add_padding(const String &p_message, int p_length) const {
174
String underscores = String("_").repeat(p_length * pseudolocalization.expansion_ratio / 2);
175
String prefix = pseudolocalization.prefix + underscores;
176
String suffix = underscores + pseudolocalization.suffix;
177
178
return prefix + p_message + suffix;
179
}
180
181
const char32_t *TranslationDomain::_get_accented_version(char32_t p_character) const {
182
if (!is_ascii_alphabet_char(p_character)) {
183
return nullptr;
184
}
185
186
for (unsigned int i = 0; i < std_size(_character_to_accented); i++) {
187
if (_character_to_accented[i].character == p_character) {
188
return _character_to_accented[i].accented_character;
189
}
190
}
191
192
return nullptr;
193
}
194
195
bool TranslationDomain::_is_placeholder(const String &p_message, int p_index) const {
196
return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
197
(p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
198
p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
199
}
200
201
StringName TranslationDomain::get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_context) const {
202
StringName res;
203
int best_score = 0;
204
205
for (const Ref<Translation> &E : translations) {
206
int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
207
if (score > 0 && score >= best_score) {
208
const StringName r = E->get_message(p_message, p_context);
209
if (!r) {
210
continue;
211
}
212
res = r;
213
best_score = score;
214
if (score == 10) {
215
break; // Exact match, skip the rest.
216
}
217
}
218
}
219
220
return res;
221
}
222
223
StringName TranslationDomain::get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
224
StringName res;
225
int best_score = 0;
226
227
for (const Ref<Translation> &E : translations) {
228
int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
229
if (score > 0 && score >= best_score) {
230
const StringName r = E->get_plural_message(p_message, p_message_plural, p_n, p_context);
231
if (!r) {
232
continue;
233
}
234
res = r;
235
best_score = score;
236
if (score == 10) {
237
break; // Exact match, skip the rest.
238
}
239
}
240
}
241
242
return res;
243
}
244
245
PackedStringArray TranslationDomain::get_loaded_locales() const {
246
PackedStringArray locales;
247
for (const Ref<Translation> &E : translations) {
248
const String &locale = E->get_locale();
249
if (!locales.has(locale)) {
250
locales.push_back(locale);
251
}
252
}
253
return locales;
254
}
255
256
#ifndef DISABLE_DEPRECATED
257
Ref<Translation> TranslationDomain::get_translation_object(const String &p_locale) const {
258
Ref<Translation> res;
259
int best_score = 0;
260
261
for (const Ref<Translation> &E : translations) {
262
int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
263
if (score > 0 && score >= best_score) {
264
res = E;
265
best_score = score;
266
if (score == 10) {
267
break; // Exact match, skip the rest.
268
}
269
}
270
}
271
return res;
272
}
273
#endif
274
275
void TranslationDomain::add_translation(const Ref<Translation> &p_translation) {
276
ERR_FAIL_COND_MSG(p_translation.is_null(), "Invalid translation provided.");
277
translations.insert(p_translation);
278
}
279
280
void TranslationDomain::remove_translation(const Ref<Translation> &p_translation) {
281
translations.erase(p_translation);
282
}
283
284
void TranslationDomain::clear() {
285
translations.clear();
286
}
287
288
const HashSet<Ref<Translation>> TranslationDomain::get_translations() const {
289
return translations;
290
}
291
292
HashSet<Ref<Translation>> TranslationDomain::find_translations(const String &p_locale, bool p_exact) const {
293
HashSet<Ref<Translation>> res;
294
if (p_exact) {
295
for (const Ref<Translation> &E : translations) {
296
if (E->get_locale() == p_locale) {
297
res.insert(E);
298
}
299
}
300
} else {
301
for (const Ref<Translation> &E : translations) {
302
if (TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale()) > 0) {
303
res.insert(E);
304
}
305
}
306
}
307
return res;
308
}
309
310
bool TranslationDomain::has_translation(const Ref<Translation> &p_translation) const {
311
return translations.has(p_translation);
312
}
313
314
bool TranslationDomain::has_translation_for_locale(const String &p_locale, bool p_exact) const {
315
if (p_exact) {
316
for (const Ref<Translation> &E : translations) {
317
if (E->get_locale() == p_locale) {
318
return true;
319
}
320
}
321
} else {
322
for (const Ref<Translation> &E : translations) {
323
if (TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale()) > 0) {
324
return true;
325
}
326
}
327
}
328
return false;
329
}
330
331
TypedArray<Translation> TranslationDomain::get_translations_bind() const {
332
TypedArray<Translation> res;
333
res.reserve(translations.size());
334
for (const Ref<Translation> &E : translations) {
335
res.push_back(E);
336
}
337
return res;
338
}
339
340
TypedArray<Translation> TranslationDomain::find_translations_bind(const String &p_locale, bool p_exact) const {
341
const HashSet<Ref<Translation>> &found = find_translations(p_locale, p_exact);
342
343
TypedArray<Translation> res;
344
res.reserve(found.size());
345
for (const Ref<Translation> &E : found) {
346
res.push_back(E);
347
}
348
return res;
349
}
350
351
StringName TranslationDomain::translate(const StringName &p_message, const StringName &p_context) const {
352
if (!enabled) {
353
return p_message;
354
}
355
356
const String &locale = locale_override.is_empty() ? TranslationServer::get_singleton()->get_locale() : locale_override;
357
StringName res = get_message_from_translations(locale, p_message, p_context);
358
359
const String &fallback = TranslationServer::get_singleton()->get_fallback_locale();
360
if (!res && fallback.length() >= 2) {
361
res = get_message_from_translations(fallback, p_message, p_context);
362
}
363
364
if (!res) {
365
return pseudolocalization.enabled ? pseudolocalize(p_message) : p_message;
366
}
367
return pseudolocalization.enabled ? pseudolocalize(res) : res;
368
}
369
370
StringName TranslationDomain::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
371
if (!enabled) {
372
return p_n == 1 ? p_message : p_message_plural;
373
}
374
375
const String &locale = locale_override.is_empty() ? TranslationServer::get_singleton()->get_locale() : locale_override;
376
StringName res = get_message_from_translations(locale, p_message, p_message_plural, p_n, p_context);
377
378
const String &fallback = TranslationServer::get_singleton()->get_fallback_locale();
379
if (!res && fallback.length() >= 2) {
380
res = get_message_from_translations(fallback, p_message, p_message_plural, p_n, p_context);
381
}
382
383
if (!res) {
384
if (p_n == 1) {
385
return p_message;
386
}
387
return p_message_plural;
388
}
389
return res;
390
}
391
392
String TranslationDomain::get_locale_override() const {
393
return locale_override;
394
}
395
396
void TranslationDomain::set_locale_override(const String &p_locale) {
397
locale_override = p_locale.is_empty() ? p_locale : TranslationServer::get_singleton()->standardize_locale(p_locale);
398
}
399
400
bool TranslationDomain::is_enabled() const {
401
return enabled;
402
}
403
404
void TranslationDomain::set_enabled(bool p_enabled) {
405
enabled = p_enabled;
406
}
407
408
bool TranslationDomain::is_pseudolocalization_enabled() const {
409
return pseudolocalization.enabled;
410
}
411
412
void TranslationDomain::set_pseudolocalization_enabled(bool p_enabled) {
413
pseudolocalization.enabled = p_enabled;
414
}
415
416
bool TranslationDomain::is_pseudolocalization_accents_enabled() const {
417
return pseudolocalization.accents_enabled;
418
}
419
420
void TranslationDomain::set_pseudolocalization_accents_enabled(bool p_enabled) {
421
pseudolocalization.accents_enabled = p_enabled;
422
}
423
424
bool TranslationDomain::is_pseudolocalization_double_vowels_enabled() const {
425
return pseudolocalization.double_vowels_enabled;
426
}
427
428
void TranslationDomain::set_pseudolocalization_double_vowels_enabled(bool p_enabled) {
429
pseudolocalization.double_vowels_enabled = p_enabled;
430
}
431
432
bool TranslationDomain::is_pseudolocalization_fake_bidi_enabled() const {
433
return pseudolocalization.fake_bidi_enabled;
434
}
435
436
void TranslationDomain::set_pseudolocalization_fake_bidi_enabled(bool p_enabled) {
437
pseudolocalization.fake_bidi_enabled = p_enabled;
438
}
439
440
bool TranslationDomain::is_pseudolocalization_override_enabled() const {
441
return pseudolocalization.override_enabled;
442
}
443
444
void TranslationDomain::set_pseudolocalization_override_enabled(bool p_enabled) {
445
pseudolocalization.override_enabled = p_enabled;
446
}
447
448
bool TranslationDomain::is_pseudolocalization_skip_placeholders_enabled() const {
449
return pseudolocalization.skip_placeholders_enabled;
450
}
451
452
void TranslationDomain::set_pseudolocalization_skip_placeholders_enabled(bool p_enabled) {
453
pseudolocalization.skip_placeholders_enabled = p_enabled;
454
}
455
456
float TranslationDomain::get_pseudolocalization_expansion_ratio() const {
457
return pseudolocalization.expansion_ratio;
458
}
459
460
void TranslationDomain::set_pseudolocalization_expansion_ratio(float p_ratio) {
461
pseudolocalization.expansion_ratio = p_ratio;
462
}
463
464
String TranslationDomain::get_pseudolocalization_prefix() const {
465
return pseudolocalization.prefix;
466
}
467
468
void TranslationDomain::set_pseudolocalization_prefix(const String &p_prefix) {
469
pseudolocalization.prefix = p_prefix;
470
}
471
472
String TranslationDomain::get_pseudolocalization_suffix() const {
473
return pseudolocalization.suffix;
474
}
475
476
void TranslationDomain::set_pseudolocalization_suffix(const String &p_suffix) {
477
pseudolocalization.suffix = p_suffix;
478
}
479
480
StringName TranslationDomain::pseudolocalize(const StringName &p_message) const {
481
if (p_message.is_empty()) {
482
return p_message;
483
}
484
485
String message = p_message;
486
int length = message.length();
487
if (pseudolocalization.override_enabled) {
488
message = _get_override_string(message);
489
}
490
491
if (pseudolocalization.double_vowels_enabled) {
492
message = _double_vowels(message);
493
}
494
495
if (pseudolocalization.accents_enabled) {
496
message = _replace_with_accented_string(message);
497
}
498
499
if (pseudolocalization.fake_bidi_enabled) {
500
message = _wrap_with_fakebidi_characters(message);
501
}
502
503
return _add_padding(message, length);
504
}
505
506
void TranslationDomain::_bind_methods() {
507
#ifndef DISABLE_DEPRECATED
508
ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationDomain::get_translation_object);
509
#endif
510
511
ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationDomain::add_translation);
512
ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationDomain::remove_translation);
513
ClassDB::bind_method(D_METHOD("clear"), &TranslationDomain::clear);
514
ClassDB::bind_method(D_METHOD("get_translations"), &TranslationDomain::get_translations_bind);
515
ClassDB::bind_method(D_METHOD("has_translation_for_locale", "locale", "exact"), &TranslationDomain::has_translation_for_locale);
516
ClassDB::bind_method(D_METHOD("has_translation", "translation"), &TranslationDomain::has_translation);
517
ClassDB::bind_method(D_METHOD("find_translations", "locale", "exact"), &TranslationDomain::find_translations_bind);
518
ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationDomain::translate, DEFVAL(StringName()));
519
ClassDB::bind_method(D_METHOD("translate_plural", "message", "message_plural", "n", "context"), &TranslationDomain::translate_plural, DEFVAL(StringName()));
520
ClassDB::bind_method(D_METHOD("get_locale_override"), &TranslationDomain::get_locale_override);
521
ClassDB::bind_method(D_METHOD("set_locale_override", "locale"), &TranslationDomain::set_locale_override);
522
ClassDB::bind_method(D_METHOD("is_enabled"), &TranslationDomain::is_enabled);
523
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &TranslationDomain::set_enabled);
524
525
ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationDomain::is_pseudolocalization_enabled);
526
ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_enabled);
527
ClassDB::bind_method(D_METHOD("is_pseudolocalization_accents_enabled"), &TranslationDomain::is_pseudolocalization_accents_enabled);
528
ClassDB::bind_method(D_METHOD("set_pseudolocalization_accents_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_accents_enabled);
529
ClassDB::bind_method(D_METHOD("is_pseudolocalization_double_vowels_enabled"), &TranslationDomain::is_pseudolocalization_double_vowels_enabled);
530
ClassDB::bind_method(D_METHOD("set_pseudolocalization_double_vowels_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_double_vowels_enabled);
531
ClassDB::bind_method(D_METHOD("is_pseudolocalization_fake_bidi_enabled"), &TranslationDomain::is_pseudolocalization_fake_bidi_enabled);
532
ClassDB::bind_method(D_METHOD("set_pseudolocalization_fake_bidi_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_fake_bidi_enabled);
533
ClassDB::bind_method(D_METHOD("is_pseudolocalization_override_enabled"), &TranslationDomain::is_pseudolocalization_override_enabled);
534
ClassDB::bind_method(D_METHOD("set_pseudolocalization_override_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_override_enabled);
535
ClassDB::bind_method(D_METHOD("is_pseudolocalization_skip_placeholders_enabled"), &TranslationDomain::is_pseudolocalization_skip_placeholders_enabled);
536
ClassDB::bind_method(D_METHOD("set_pseudolocalization_skip_placeholders_enabled", "enabled"), &TranslationDomain::set_pseudolocalization_skip_placeholders_enabled);
537
ClassDB::bind_method(D_METHOD("get_pseudolocalization_expansion_ratio"), &TranslationDomain::get_pseudolocalization_expansion_ratio);
538
ClassDB::bind_method(D_METHOD("set_pseudolocalization_expansion_ratio", "ratio"), &TranslationDomain::set_pseudolocalization_expansion_ratio);
539
ClassDB::bind_method(D_METHOD("get_pseudolocalization_prefix"), &TranslationDomain::get_pseudolocalization_prefix);
540
ClassDB::bind_method(D_METHOD("set_pseudolocalization_prefix", "prefix"), &TranslationDomain::set_pseudolocalization_prefix);
541
ClassDB::bind_method(D_METHOD("get_pseudolocalization_suffix"), &TranslationDomain::get_pseudolocalization_suffix);
542
ClassDB::bind_method(D_METHOD("set_pseudolocalization_suffix", "suffix"), &TranslationDomain::set_pseudolocalization_suffix);
543
ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationDomain::pseudolocalize);
544
545
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "enabled"), "set_enabled", "is_enabled");
546
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
547
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_accents_enabled"), "set_pseudolocalization_accents_enabled", "is_pseudolocalization_accents_enabled");
548
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_double_vowels_enabled"), "set_pseudolocalization_double_vowels_enabled", "is_pseudolocalization_double_vowels_enabled");
549
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_fake_bidi_enabled"), "set_pseudolocalization_fake_bidi_enabled", "is_pseudolocalization_fake_bidi_enabled");
550
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_override_enabled"), "set_pseudolocalization_override_enabled", "is_pseudolocalization_override_enabled");
551
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_skip_placeholders_enabled"), "set_pseudolocalization_skip_placeholders_enabled", "is_pseudolocalization_skip_placeholders_enabled");
552
ADD_PROPERTY(PropertyInfo(Variant::Type::FLOAT, "pseudolocalization_expansion_ratio"), "set_pseudolocalization_expansion_ratio", "get_pseudolocalization_expansion_ratio");
553
ADD_PROPERTY(PropertyInfo(Variant::Type::STRING, "pseudolocalization_prefix"), "set_pseudolocalization_prefix", "get_pseudolocalization_prefix");
554
ADD_PROPERTY(PropertyInfo(Variant::Type::STRING, "pseudolocalization_suffix"), "set_pseudolocalization_suffix", "get_pseudolocalization_suffix");
555
}
556
557