summaryrefslogtreecommitdiff
path: root/core/string
diff options
context:
space:
mode:
Diffstat (limited to 'core/string')
-rw-r--r--core/string/optimized_translation.cpp10
-rw-r--r--core/string/print_string.cpp2
-rw-r--r--core/string/print_string.h12
-rw-r--r--core/string/string_name.cpp1
-rw-r--r--core/string/string_name.h15
-rw-r--r--core/string/translation.cpp38
-rw-r--r--core/string/translation.h20
-rw-r--r--core/string/translation_po.cpp43
-rw-r--r--core/string/ustring.cpp104
9 files changed, 154 insertions, 91 deletions
diff --git a/core/string/optimized_translation.cpp b/core/string/optimized_translation.cpp
index 07b58f2418..07302cc8c3 100644
--- a/core/string/optimized_translation.cpp
+++ b/core/string/optimized_translation.cpp
@@ -37,9 +37,9 @@ extern "C" {
}
struct CompressedString {
- int orig_len;
+ int orig_len = 0;
CharString compressed;
- int offset;
+ int offset = 0;
};
void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
@@ -53,7 +53,7 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
int size = Math::larger_prime(keys.size());
Vector<Vector<Pair<int, CharString>>> buckets;
- Vector<Map<uint32_t, int>> table;
+ Vector<HashMap<uint32_t, int>> table;
Vector<uint32_t> hfunc_table;
Vector<CompressedString> compressed;
@@ -108,7 +108,7 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
for (int i = 0; i < size; i++) {
const Vector<Pair<int, CharString>> &b = buckets[i];
- Map<uint32_t, int> &t = table.write[i];
+ HashMap<uint32_t, int> &t = table.write[i];
if (b.size() == 0) {
continue;
@@ -147,7 +147,7 @@ void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
int btindex = 0;
for (int i = 0; i < size; i++) {
- const Map<uint32_t, int> &t = table[i];
+ const HashMap<uint32_t, int> &t = table[i];
if (t.size() == 0) {
htw[i] = 0xFFFFFFFF; //nothing
continue;
diff --git a/core/string/print_string.cpp b/core/string/print_string.cpp
index 97e119bcf6..919c9e08e3 100644
--- a/core/string/print_string.cpp
+++ b/core/string/print_string.cpp
@@ -45,7 +45,7 @@ void add_print_handler(PrintHandlerList *p_handler) {
_global_unlock();
}
-void remove_print_handler(PrintHandlerList *p_handler) {
+void remove_print_handler(const PrintHandlerList *p_handler) {
_global_lock();
PrintHandlerList *prev = nullptr;
diff --git a/core/string/print_string.h b/core/string/print_string.h
index 669d2ea316..f7d0f25030 100644
--- a/core/string/print_string.h
+++ b/core/string/print_string.h
@@ -54,13 +54,21 @@ String stringify_variants(Variant p_var, Args... p_args) {
}
void add_print_handler(PrintHandlerList *p_handler);
-void remove_print_handler(PrintHandlerList *p_handler);
+void remove_print_handler(const PrintHandlerList *p_handler);
extern bool _print_line_enabled;
extern bool _print_error_enabled;
extern void __print_line(String p_string);
extern void print_error(String p_string);
extern void print_verbose(String p_string);
-#define print_line(...) __print_line(stringify_variants(__VA_ARGS__))
+
+inline void print_line(Variant v) {
+ __print_line(stringify_variants(v));
+}
+
+template <typename... Args>
+void print_line(Variant p_var, Args... p_args) {
+ __print_line(stringify_variants(p_var, p_args...));
+}
#endif // PRINT_STRING_H
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 2e941b8037..9c4fc4e1b7 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -247,6 +247,7 @@ StringName::StringName(const char *p_name, bool p_static) {
_data->cname = nullptr;
_data->next = _table[idx];
_data->prev = nullptr;
+
#ifdef DEBUG_ENABLED
if (unlikely(debug_stringname)) {
// Keep in memory, force static.
diff --git a/core/string/string_name.h b/core/string/string_name.h
index 6f08d32981..ff4c41af94 100644
--- a/core/string/string_name.h
+++ b/core/string/string_name.h
@@ -35,6 +35,8 @@
#include "core/string/ustring.h"
#include "core/templates/safe_refcount.h"
+#define UNIQUE_NODE_PREFIX "%"
+
class Main;
struct StaticCString {
@@ -70,7 +72,7 @@ class StringName {
_Data *_data = nullptr;
union _HashUnion {
- _Data *ptr;
+ _Data *ptr = nullptr;
uint32_t hash;
};
@@ -100,6 +102,17 @@ public:
bool operator==(const String &p_name) const;
bool operator==(const char *p_name) const;
bool operator!=(const String &p_name) const;
+
+ _FORCE_INLINE_ bool is_node_unique_name() const {
+ if (!_data) {
+ return false;
+ }
+ if (_data->cname != nullptr) {
+ return (char32_t)_data->cname[0] == (char32_t)UNIQUE_NODE_PREFIX[0];
+ } else {
+ return (char32_t)_data->name[0] == (char32_t)UNIQUE_NODE_PREFIX[0];
+ }
+ }
_FORCE_INLINE_ bool operator<(const StringName &p_name) const {
return _data < p_name._data;
}
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index c41828de05..c64f815563 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -95,12 +95,12 @@ StringName Translation::get_message(const StringName &p_src_text, const StringNa
WARN_PRINT("Translation class doesn't handle context. Using context in get_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class");
}
- const Map<StringName, StringName>::Element *E = translation_map.find(p_src_text);
+ HashMap<StringName, StringName>::ConstIterator E = translation_map.find(p_src_text);
if (!E) {
return StringName();
}
- return E->get();
+ return E->value;
}
StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
@@ -215,12 +215,12 @@ static _character_accent_pair _character_to_accented[] = {
Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
-Map<String, String> TranslationServer::language_map;
-Map<String, String> TranslationServer::script_map;
-Map<String, String> TranslationServer::locale_rename_map;
-Map<String, String> TranslationServer::country_name_map;
-Map<String, String> TranslationServer::variant_map;
-Map<String, String> TranslationServer::country_rename_map;
+HashMap<String, String> TranslationServer::language_map;
+HashMap<String, String> TranslationServer::script_map;
+HashMap<String, String> TranslationServer::locale_rename_map;
+HashMap<String, String> TranslationServer::country_name_map;
+HashMap<String, String> TranslationServer::variant_map;
+HashMap<String, String> TranslationServer::country_rename_map;
void TranslationServer::init_locale_info() {
// Init locale info.
@@ -452,8 +452,8 @@ String TranslationServer::get_locale_name(const String &p_locale) const {
Vector<String> TranslationServer::get_all_languages() const {
Vector<String> languages;
- for (const Map<String, String>::Element *E = language_map.front(); E; E = E->next()) {
- languages.push_back(E->key());
+ for (const KeyValue<String, String> &E : language_map) {
+ languages.push_back(E.key);
}
return languages;
@@ -466,8 +466,8 @@ String TranslationServer::get_language_name(const String &p_language) const {
Vector<String> TranslationServer::get_all_scripts() const {
Vector<String> scripts;
- for (const Map<String, String>::Element *E = script_map.front(); E; E = E->next()) {
- scripts.push_back(E->key());
+ for (const KeyValue<String, String> &E : script_map) {
+ scripts.push_back(E.key);
}
return scripts;
@@ -480,8 +480,8 @@ String TranslationServer::get_script_name(const String &p_script) const {
Vector<String> TranslationServer::get_all_countries() const {
Vector<String> countries;
- for (const Map<String, String>::Element *E = country_name_map.front(); E; E = E->next()) {
- countries.push_back(E->key());
+ for (const KeyValue<String, String> &E : country_name_map) {
+ countries.push_back(E.key);
}
return countries;
@@ -507,7 +507,7 @@ String TranslationServer::get_locale() const {
Array TranslationServer::get_loaded_locales() const {
Array locales;
- for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ for (const RBSet<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
const Ref<Translation> &t = E->get();
ERR_FAIL_COND_V(t.is_null(), Array());
String l = t->get_locale();
@@ -530,7 +530,7 @@ Ref<Translation> TranslationServer::get_translation_object(const String &p_local
Ref<Translation> res;
int best_score = 0;
- for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ for (const RBSet<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
const Ref<Translation> &t = E->get();
ERR_FAIL_COND_V(t.is_null(), nullptr);
String l = t->get_locale();
@@ -599,7 +599,7 @@ StringName TranslationServer::_get_message_from_translations(const StringName &p
StringName res;
int best_score = 0;
- for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ for (const RBSet<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
const Ref<Translation> &t = E->get();
ERR_FAIL_COND_V(t.is_null(), p_message);
String l = t->get_locale();
@@ -891,7 +891,7 @@ String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const
return res;
}
-String TranslationServer::add_padding(String &p_message, int p_length) const {
+String TranslationServer::add_padding(const String &p_message, int p_length) const {
String res;
String prefix = pseudolocalization_prefix;
String suffix;
@@ -921,7 +921,7 @@ const char32_t *TranslationServer::get_accented_version(char32_t p_character) co
}
bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
- return p_message[p_index] == '%' && p_index < p_message.size() - 1 &&
+ return p_index < p_message.size() - 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');
}
diff --git a/core/string/translation.h b/core/string/translation.h
index 947ca4c6d8..f58f6f91a2 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -41,7 +41,7 @@ class Translation : public Resource {
RES_BASE_EXTENSION("translation");
String locale = "en";
- Map<StringName, StringName> translation_map;
+ HashMap<StringName, StringName> translation_map;
virtual Vector<String> _get_message_list() const;
virtual Dictionary _get_messages() const;
@@ -74,7 +74,7 @@ class TranslationServer : public Object {
String locale = "en";
String fallback;
- Set<Ref<Translation>> translations;
+ RBSet<Ref<Translation>> translations;
Ref<Translation> tool_translation;
Ref<Translation> doc_translation;
@@ -96,7 +96,7 @@ class TranslationServer : public Object {
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(String &p_message, int p_length) 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;
@@ -111,16 +111,16 @@ class TranslationServer : public Object {
String name;
String script;
String default_country;
- Set<String> supported_countries;
+ RBSet<String> supported_countries;
};
static Vector<LocaleScriptInfo> locale_script_info;
- static Map<String, String> language_map;
- static Map<String, String> script_map;
- static Map<String, String> locale_rename_map;
- static Map<String, String> country_name_map;
- static Map<String, String> country_rename_map;
- static Map<String, String> variant_map;
+ static HashMap<String, String> language_map;
+ static HashMap<String, String> script_map;
+ static HashMap<String, String> locale_rename_map;
+ static HashMap<String, String> country_name_map;
+ static HashMap<String, String> country_rename_map;
+ static HashMap<String, String> variant_map;
void init_locale_info();
diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp
index 1c991ee12d..fa656b634d 100644
--- a/core/string/translation_po.cpp
+++ b/core/string/translation_po.cpp
@@ -35,7 +35,7 @@
#ifdef DEBUG_TRANSLATION_PO
void TranslationPO::print_translation_map() {
Error err;
- FileAccess *file = FileAccess::open("translation_map_print_test.txt", FileAccess::WRITE, &err);
+ Ref<FileAccess> file = FileAccess::open("translation_map_print_test.txt", FileAccess::WRITE, &err);
if (err != OK) {
ERR_PRINT("Failed to open translation_map_print_test.txt");
return;
@@ -62,7 +62,6 @@ void TranslationPO::print_translation_map() {
file->store_line("");
}
}
- file->close();
}
#endif
@@ -71,21 +70,14 @@ Dictionary TranslationPO::_get_messages() const {
Dictionary d;
- List<StringName> context_l;
- translation_map.get_key_list(&context_l);
- for (const StringName &ctx : context_l) {
- const HashMap<StringName, Vector<StringName>> &id_str_map = translation_map[ctx];
-
+ for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) {
Dictionary d2;
- List<StringName> id_l;
- id_str_map.get_key_list(&id_l);
- // Save list of id and strs associated with a context in a temporary dictionary.
- for (List<StringName>::Element *E2 = id_l.front(); E2; E2 = E2->next()) {
- StringName id = E2->get();
- d2[id] = id_str_map[id];
+
+ for (const KeyValue<StringName, Vector<StringName>> &E2 : E.value) {
+ d2[E2.key] = E2.value;
}
- d[ctx] = d2;
+ d[E.key] = d2;
}
return d;
@@ -275,31 +267,24 @@ void TranslationPO::get_message_list(List<StringName> *r_messages) const {
// OptimizedTranslation uses this function to get the list of msgid.
// Return all the keys of translation_map under "" context.
- List<StringName> context_l;
- translation_map.get_key_list(&context_l);
-
- for (const StringName &E : context_l) {
- if (String(E) != "") {
+ for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) {
+ if (E.key != StringName()) {
continue;
}
- List<StringName> msgid_l;
- translation_map[E].get_key_list(&msgid_l);
-
- for (List<StringName>::Element *E2 = msgid_l.front(); E2; E2 = E2->next()) {
- r_messages->push_back(E2->get());
+ for (const KeyValue<StringName, Vector<StringName>> &E2 : E.value) {
+ r_messages->push_back(E2.key);
}
}
}
int TranslationPO::get_message_count() const {
- List<StringName> context_l;
- translation_map.get_key_list(&context_l);
-
int count = 0;
- for (const StringName &E : context_l) {
- count += translation_map[E].size();
+
+ for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) {
+ count += E.value.size();
}
+
return count;
}
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 7cfd34b53e..015dfbc651 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -35,6 +35,7 @@
#include "core/math/math_funcs.h"
#include "core/os/memory.h"
#include "core/string/print_string.h"
+#include "core/string/string_name.h"
#include "core/string/translation.h"
#include "core/string/ucaps.h"
#include "core/variant/variant.h"
@@ -4123,15 +4124,11 @@ String String::path_to(const String &p_path) const {
dst += "/";
}
- String base;
-
if (src.begins_with("res://") && dst.begins_with("res://")) {
- base = "res:/";
src = src.replace("res://", "/");
dst = dst.replace("res://", "/");
} else if (src.begins_with("user://") && dst.begins_with("user://")) {
- base = "user:/";
src = src.replace("user://", "/");
dst = dst.replace("user://", "/");
@@ -4146,7 +4143,6 @@ String String::path_to(const String &p_path) const {
return p_path; //impossible to do this
}
- base = src_begin;
src = src.substr(src_begin.length(), src.length());
dst = dst.substr(dst_begin.length(), dst.length());
}
@@ -4357,7 +4353,7 @@ String String::property_name_encode() const {
}
// Changes made to the set of invalid characters must also be reflected in the String documentation.
-const String String::invalid_node_name_characters = ". : @ / \"";
+const String String::invalid_node_name_characters = ". : @ / \" " UNIQUE_NODE_PREFIX;
String String::validate_node_name() const {
Vector<String> chars = String::invalid_node_name_characters.split(" ");
@@ -4431,7 +4427,7 @@ String String::sprintf(const Array &values, bool *error) const {
int min_chars = 0;
int min_decimals = 0;
bool in_decimals = false;
- bool pad_with_zeroes = false;
+ bool pad_with_zeros = false;
bool left_justified = false;
bool show_sign = false;
@@ -4484,7 +4480,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Padding.
int pad_chars_count = (value < 0 || show_sign) ? min_chars - 1 : min_chars;
- String pad_char = pad_with_zeroes ? String("0") : String(" ");
+ String pad_char = pad_with_zeros ? String("0") : String(" ");
if (left_justified) {
str = str.rpad(pad_chars_count, pad_char);
} else {
@@ -4492,10 +4488,13 @@ String String::sprintf(const Array &values, bool *error) const {
}
// Sign.
- if (show_sign && value >= 0) {
- str = str.insert(pad_with_zeroes ? 0 : str.length() - number_len, "+");
- } else if (value < 0) {
- str = str.insert(pad_with_zeroes ? 0 : str.length() - number_len, "-");
+ if (show_sign || value < 0) {
+ String sign_char = value < 0 ? "-" : "+";
+ if (left_justified) {
+ str = str.insert(0, sign_char);
+ } else {
+ str = str.insert(pad_with_zeros ? 0 : str.length() - number_len, sign_char);
+ }
}
formatted += str;
@@ -4524,13 +4523,9 @@ String String::sprintf(const Array &values, bool *error) const {
// Padding. Leave room for sign later if required.
int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars;
- String pad_char = pad_with_zeroes ? String("0") : String(" ");
+ String pad_char = pad_with_zeros ? String("0") : String(" ");
if (left_justified) {
- if (pad_with_zeroes) {
- return "left justification cannot be used with zeros as the padding";
- } else {
- str = str.rpad(pad_chars_count, pad_char);
- }
+ str = str.rpad(pad_chars_count, pad_char);
} else {
str = str.lpad(pad_chars_count, pad_char);
}
@@ -4541,7 +4536,7 @@ String String::sprintf(const Array &values, bool *error) const {
if (left_justified) {
str = str.insert(0, sign_char);
} else {
- str = str.insert(pad_with_zeroes ? 0 : str.length() - initial_len, sign_char);
+ str = str.insert(pad_with_zeros ? 0 : str.length() - initial_len, sign_char);
}
}
@@ -4630,7 +4625,11 @@ String String::sprintf(const Array &values, bool *error) const {
min_decimals += n;
} else {
if (c == '0' && min_chars == 0) {
- pad_with_zeroes = true;
+ if (left_justified) {
+ WARN_PRINT("'0' flag ignored with '-' flag in string format");
+ } else {
+ pad_with_zeros = true;
+ }
} else {
min_chars *= 10;
min_chars += n;
@@ -4679,7 +4678,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Back to defaults:
min_chars = 0;
min_decimals = 6;
- pad_with_zeroes = false;
+ pad_with_zeros = false;
left_justified = false;
show_sign = false;
in_decimals = false;
@@ -4780,6 +4779,17 @@ Vector<uint8_t> String::to_utf32_buffer() const {
}
#ifdef TOOLS_ENABLED
+/**
+ * "Tools TRanslate". Performs string replacement for internationalization
+ * within the editor. A translation context can optionally be specified to
+ * disambiguate between identical source strings in translations. When
+ * placeholders are desired, use `vformat(TTR("Example: %s"), some_string)`.
+ * If a string mentions a quantity (and may therefore need a dynamic plural form),
+ * use `TTRN()` instead of `TTR()`.
+ *
+ * NOTE: Only use `TTR()` in editor-only code (typically within the `editor/` folder).
+ * For translations that can be supplied by exported projects, use `RTR()` instead.
+ */
String TTR(const String &p_text, const String &p_context) {
if (TranslationServer::get_singleton()) {
return TranslationServer::get_singleton()->tool_translate(p_text, p_context);
@@ -4788,6 +4798,18 @@ String TTR(const String &p_text, const String &p_context) {
return p_text;
}
+/**
+ * "Tools TRanslate for N items". Performs string replacement for
+ * internationalization within the editor. A translation context can optionally
+ * be specified to disambiguate between identical source strings in
+ * translations. Use `TTR()` if the string doesn't need dynamic plural form.
+ * When placeholders are desired, use
+ * `vformat(TTRN("%d item", "%d items", some_integer), some_integer)`.
+ * The placeholder must be present in both strings to avoid run-time warnings in `vformat()`.
+ *
+ * NOTE: Only use `TTRN()` in editor-only code (typically within the `editor/` folder).
+ * For translations that can be supplied by exported projects, use `RTRN()` instead.
+ */
String TTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
if (TranslationServer::get_singleton()) {
return TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context);
@@ -4800,9 +4822,10 @@ String TTRN(const String &p_text, const String &p_text_plural, int p_n, const St
return p_text_plural;
}
-/* DTR and DTRN are used for the documentation, handling descriptions extracted
- * from the XML.
- * They also replace `$DOCS_URL` with the actual URL to the documentation's branch,
+/**
+ * "Docs TRanslate". Used for the editor class reference documentation,
+ * handling descriptions extracted from the XML.
+ * It also replaces `$DOCS_URL` with the actual URL to the documentation's branch,
* to allow dehardcoding it in the XML and doing proper substitutions everywhere.
*/
String DTR(const String &p_text, const String &p_context) {
@@ -4816,6 +4839,12 @@ String DTR(const String &p_text, const String &p_context) {
return text.replace("$DOCS_URL", VERSION_DOCS_URL);
}
+/**
+ * "Docs TRanslate for N items". Used for the editor class reference documentation
+ * (with support for plurals), handling descriptions extracted from the XML.
+ * It also replaces `$DOCS_URL` with the actual URL to the documentation's branch,
+ * to allow dehardcoding it in the XML and doing proper substitutions everywhere.
+ */
String DTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
const String text = p_text.dedent().strip_edges();
const String text_plural = p_text_plural.dedent().strip_edges();
@@ -4832,6 +4861,19 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St
}
#endif
+/**
+ * "Run-time TRanslate". Performs string replacement for internationalization
+ * within a running project. The translation string must be supplied by the
+ * project, as Godot does not provide built-in translations for `RTR()` strings
+ * to keep binary size low. A translation context can optionally be specified to
+ * disambiguate between identical source strings in translations. When
+ * placeholders are desired, use `vformat(RTR("Example: %s"), some_string)`.
+ * If a string mentions a quantity (and may therefore need a dynamic plural form),
+ * use `RTRN()` instead of `RTR()`.
+ *
+ * NOTE: Do not use `RTR()` in editor-only code (typically within the `editor/`
+ * folder). For editor translations, use `TTR()` instead.
+ */
String RTR(const String &p_text, const String &p_context) {
if (TranslationServer::get_singleton()) {
String rtr = TranslationServer::get_singleton()->tool_translate(p_text, p_context);
@@ -4845,6 +4887,20 @@ String RTR(const String &p_text, const String &p_context) {
return p_text;
}
+/**
+ * "Run-time TRanslate for N items". Performs string replacement for
+ * internationalization within a running project. The translation string must be
+ * supplied by the project, as Godot does not provide built-in translations for
+ * `RTRN()` strings to keep binary size low. A translation context can
+ * optionally be specified to disambiguate between identical source strings in
+ * translations. Use `RTR()` if the string doesn't need dynamic plural form.
+ * When placeholders are desired, use
+ * `vformat(RTRN("%d item", "%d items", some_integer), some_integer)`.
+ * The placeholder must be present in both strings to avoid run-time warnings in `vformat()`.
+ *
+ * NOTE: Do not use `RTRN()` in editor-only code (typically within the `editor/`
+ * folder). For editor translations, use `TTRN()` instead.
+ */
String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
if (TranslationServer::get_singleton()) {
String rtr = TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context);