summaryrefslogtreecommitdiff
path: root/core/string/ustring.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/string/ustring.cpp')
-rw-r--r--core/string/ustring.cpp305
1 files changed, 182 insertions, 123 deletions
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 658dbc98cf..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"
@@ -54,34 +55,14 @@
static const int MAX_DECIMALS = 32;
-static _FORCE_INLINE_ bool is_digit(char32_t c) {
- return (c >= '0' && c <= '9');
-}
-
-static _FORCE_INLINE_ bool is_hex_digit(char32_t c) {
- return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
-}
-
-static _FORCE_INLINE_ bool is_upper_case(char32_t c) {
- return (c >= 'A' && c <= 'Z');
-}
-
-static _FORCE_INLINE_ bool is_lower_case(char32_t c) {
- return (c >= 'a' && c <= 'z');
-}
-
static _FORCE_INLINE_ char32_t lower_case(char32_t c) {
- return (is_upper_case(c) ? (c + ('a' - 'A')) : c);
+ return (is_ascii_upper_case(c) ? (c + ('a' - 'A')) : c);
}
const char CharString::_null = 0;
const char16_t Char16String::_null = 0;
const char32_t String::_null = 0;
-bool is_symbol(char32_t c) {
- return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' ');
-}
-
bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) {
const String &s = p_s;
int beg = CLAMP(p_col, 0, s.length());
@@ -123,9 +104,12 @@ bool Char16String::operator<(const Char16String &p_right) const {
}
Char16String &Char16String::operator+=(char16_t p_char) {
- resize(size() ? size() + 1 : 2);
- set(length(), 0);
- set(length() - 1, p_char);
+ const int lhs_len = length();
+ resize(lhs_len + 2);
+
+ char16_t *dst = ptrw();
+ dst[lhs_len] = p_char;
+ dst[lhs_len + 1] = 0;
return *this;
}
@@ -178,9 +162,12 @@ bool CharString::operator<(const CharString &p_right) const {
}
CharString &CharString::operator+=(char p_char) {
- resize(size() ? size() + 1 : 2);
- set(length(), 0);
- set(length() - 1, p_char);
+ const int lhs_len = length();
+ resize(lhs_len + 2);
+
+ char *dst = ptrw();
+ dst[lhs_len] = p_char;
+ dst[lhs_len + 1] = 0;
return *this;
}
@@ -324,11 +311,7 @@ void String::copy_from(const char *p_cstr) {
return;
}
- int len = 0;
- const char *ptr = p_cstr;
- while (*(ptr++) != 0) {
- len++;
- }
+ const size_t len = strlen(p_cstr);
if (len == 0) {
resize(0);
@@ -339,7 +322,7 @@ void String::copy_from(const char *p_cstr) {
char32_t *dst = this->ptrw();
- for (int i = 0; i < len + 1; i++) {
+ for (size_t i = 0; i <= len; i++) {
dst[i] = p_cstr[i];
}
}
@@ -394,13 +377,14 @@ void String::copy_from(const wchar_t *p_cstr, const int p_clip_to) {
void String::copy_from(const char32_t &p_char) {
resize(2);
+ char32_t *dst = ptrw();
if ((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff)) {
print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(p_char, 16) + ".");
- set(0, 0xfffd);
+ dst[0] = 0xfffd;
} else {
- set(0, p_char);
+ dst[0] = p_char;
}
- set(1, 0);
+ dst[1] = 0;
}
void String::copy_from(const char32_t *p_cstr) {
@@ -449,9 +433,8 @@ void String::copy_from(const char32_t *p_cstr, const int p_clip_to) {
// p_length <= p_char strlen
void String::copy_from_unchecked(const char32_t *p_char, const int p_length) {
resize(p_length + 1);
- set(p_length, 0);
-
char32_t *dst = ptrw();
+ dst[p_length] = 0;
for (int i = 0; i < p_length; i++) {
if ((p_char[i] >= 0xd800 && p_char[i] <= 0xdfff) || (p_char[i] > 0x10ffff)) {
@@ -481,6 +464,12 @@ String String::operator+(const String &p_str) const {
return res;
}
+String String::operator+(char32_t p_char) const {
+ String res = *this;
+ res += p_char;
+ return res;
+}
+
String operator+(const char *p_chr, const String &p_str) {
String tmp = p_chr;
tmp += p_str;
@@ -504,27 +493,23 @@ String operator+(char32_t p_chr, const String &p_str) {
}
String &String::operator+=(const String &p_str) {
- if (is_empty()) {
+ const int lhs_len = length();
+ if (lhs_len == 0) {
*this = p_str;
return *this;
}
- if (p_str.is_empty()) {
+ const int rhs_len = p_str.length();
+ if (rhs_len == 0) {
return *this;
}
- int from = length();
-
- resize(length() + p_str.size());
+ resize(lhs_len + rhs_len + 1);
const char32_t *src = p_str.get_data();
- char32_t *dst = ptrw();
-
- set(length(), 0);
+ char32_t *dst = ptrw() + lhs_len;
- for (int i = 0; i < p_str.length(); i++) {
- dst[from + i] = src[i];
- }
+ memcpy(dst, src, (rhs_len + 1) * sizeof(char32_t));
return *this;
}
@@ -534,22 +519,15 @@ String &String::operator+=(const char *p_str) {
return *this;
}
- int src_len = 0;
- const char *ptr = p_str;
- while (*(ptr++) != 0) {
- src_len++;
- }
-
- int from = length();
+ const int lhs_len = length();
+ const size_t rhs_len = strlen(p_str);
- resize(from + src_len + 1);
-
- char32_t *dst = ptrw();
+ resize(lhs_len + rhs_len + 1);
- set(length(), 0);
+ char32_t *dst = ptrw() + lhs_len;
- for (int i = 0; i < src_len; i++) {
- dst[from + i] = p_str[i];
+ for (size_t i = 0; i <= rhs_len; i++) {
+ dst[i] = p_str[i];
}
return *this;
@@ -572,14 +550,16 @@ String &String::operator+=(const char32_t *p_str) {
}
String &String::operator+=(char32_t p_char) {
- resize(size() ? size() + 1 : 2);
- set(length(), 0);
+ const int lhs_len = length();
+ resize(lhs_len + 2);
+ char32_t *dst = ptrw();
if ((p_char >= 0xd800 && p_char <= 0xdfff) || (p_char > 0x10ffff)) {
print_error("Unicode parsing error: Invalid unicode codepoint " + num_int64(p_char, 16) + ".");
- set(length() - 1, 0xfffd);
+ dst[lhs_len] = 0xfffd;
} else {
- set(length() - 1, p_char);
+ dst[lhs_len] = p_char;
}
+ dst[lhs_len + 1] = 0;
return *this;
}
@@ -974,21 +954,21 @@ String String::camelcase_to_underscore(bool lowercase) const {
int start_index = 0;
for (int i = 1; i < this->size(); i++) {
- bool is_upper = is_upper_case(cstr[i]);
+ bool is_upper = is_ascii_upper_case(cstr[i]);
bool is_number = is_digit(cstr[i]);
bool are_next_2_lower = false;
bool is_next_lower = false;
bool is_next_number = false;
- bool was_precedent_upper = is_upper_case(cstr[i - 1]);
+ bool was_precedent_upper = is_ascii_upper_case(cstr[i - 1]);
bool was_precedent_number = is_digit(cstr[i - 1]);
if (i + 2 < this->size()) {
- are_next_2_lower = is_lower_case(cstr[i + 1]) && is_lower_case(cstr[i + 2]);
+ are_next_2_lower = is_ascii_lower_case(cstr[i + 1]) && is_ascii_lower_case(cstr[i + 2]);
}
if (i + 1 < this->size()) {
- is_next_lower = is_lower_case(cstr[i + 1]);
+ is_next_lower = is_ascii_lower_case(cstr[i + 1]);
is_next_number = is_digit(cstr[i + 1]);
}
@@ -1632,7 +1612,7 @@ String String::utf8(const char *p_utf8, int p_len) {
}
bool String::parse_utf8(const char *p_utf8, int p_len) {
-#define _UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-8?");
+#define UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-8?");
if (!p_utf8) {
return true;
@@ -1673,12 +1653,12 @@ bool String::parse_utf8(const char *p_utf8, int p_len) {
} else if ((c & 0xf8) == 0xf0) {
skip = 3;
} else {
- _UNICERROR("invalid skip at " + num_int64(cstr_size));
+ UNICERROR("invalid skip at " + num_int64(cstr_size));
return true; //invalid utf8
}
if (skip == 1 && (c & 0x1e) == 0) {
- _UNICERROR("overlong rejected at " + num_int64(cstr_size));
+ UNICERROR("overlong rejected at " + num_int64(cstr_size));
return true; //reject overlong
}
@@ -1693,7 +1673,7 @@ bool String::parse_utf8(const char *p_utf8, int p_len) {
}
if (skip) {
- _UNICERROR("no space left");
+ UNICERROR("no space left");
return true; //not enough space
}
}
@@ -1720,17 +1700,17 @@ bool String::parse_utf8(const char *p_utf8, int p_len) {
} else if ((*p_utf8 & 0xf8) == 0xf0) {
len = 4;
} else {
- _UNICERROR("invalid len");
+ UNICERROR("invalid len");
return true; //invalid UTF8
}
if (len > cstr_size) {
- _UNICERROR("no space left");
+ UNICERROR("no space left");
return true; //not enough space
}
if (len == 2 && (*p_utf8 & 0x1E) == 0) {
- _UNICERROR("no space left");
+ UNICERROR("no space left");
return true; //reject overlong
}
@@ -1745,18 +1725,18 @@ bool String::parse_utf8(const char *p_utf8, int p_len) {
for (int i = 1; i < len; i++) {
if ((p_utf8[i] & 0xc0) != 0x80) {
- _UNICERROR("invalid utf8");
+ UNICERROR("invalid utf8");
return true; //invalid utf8
}
if (unichar == 0 && i == 2 && ((p_utf8[i] & 0x7f) >> (7 - len)) == 0) {
- _UNICERROR("invalid utf8 overlong");
+ UNICERROR("invalid utf8 overlong");
return true; //no overlong
}
unichar = (unichar << 6) | (p_utf8[i] & 0x3f);
}
}
if (unichar >= 0xd800 && unichar <= 0xdfff) {
- _UNICERROR("invalid code point");
+ UNICERROR("invalid code point");
return CharString();
}
@@ -1766,7 +1746,7 @@ bool String::parse_utf8(const char *p_utf8, int p_len) {
}
return false;
-#undef _UNICERROR
+#undef UNICERROR
}
CharString String::utf8() const {
@@ -1840,7 +1820,7 @@ String String::utf16(const char16_t *p_utf16, int p_len) {
}
bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
-#define _UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-16?");
+#define UNICERROR(m_err) print_error("Unicode parsing error: " + String(m_err) + ". Is the string valid UTF-16?");
if (!p_utf16) {
return true;
@@ -1880,7 +1860,7 @@ bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
if ((c & 0xfffffc00) == 0xd800) {
skip = 1; // lead surrogate
} else if ((c & 0xfffffc00) == 0xdc00) {
- _UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size));
+ UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size));
return true; // invalid UTF16
} else {
skip = 0;
@@ -1890,7 +1870,7 @@ bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
if ((c & 0xfffffc00) == 0xdc00) { // trail surrogate
--skip;
} else {
- _UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size));
+ UNICERROR("invalid utf16 surrogate at " + num_int64(cstr_size));
return true; // invalid UTF16
}
}
@@ -1900,7 +1880,7 @@ bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
}
if (skip) {
- _UNICERROR("no space left");
+ UNICERROR("no space left");
return true; // not enough space
}
}
@@ -1925,7 +1905,7 @@ bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
}
if (len > cstr_size) {
- _UNICERROR("no space left");
+ UNICERROR("no space left");
return true; //not enough space
}
@@ -1943,7 +1923,7 @@ bool String::parse_utf16(const char16_t *p_utf16, int p_len) {
}
return false;
-#undef _UNICERROR
+#undef UNICERROR
}
Char16String String::utf16() const {
@@ -2212,7 +2192,7 @@ bool String::is_numeric() const {
return false;
}
dot = true;
- } else if (c < '0' || c > '9') {
+ } else if (!is_digit(c)) {
return false;
}
}
@@ -3080,7 +3060,7 @@ bool String::is_subsequence_of(const String &p_string) const {
return _base_is_subsequence_of(p_string, false);
}
-bool String::is_subsequence_ofi(const String &p_string) const {
+bool String::is_subsequence_ofn(const String &p_string) const {
return _base_is_subsequence_of(p_string, true);
}
@@ -3558,6 +3538,10 @@ String String::rstrip(const String &p_chars) const {
return substr(0, end + 1);
}
+bool String::is_network_share_path() const {
+ return begins_with("//") || begins_with("\\\\");
+}
+
String String::simplify_path() const {
String s = *this;
String drive;
@@ -3570,6 +3554,9 @@ String String::simplify_path() const {
} else if (s.begins_with("user://")) {
drive = "user://";
s = s.substr(7, s.length());
+ } else if (is_network_share_path()) {
+ drive = s.substr(0, 2);
+ s = s.substr(2, s.length() - 2);
} else if (s.begins_with("/") || s.begins_with("\\")) {
drive = s.substr(0, 1);
s = s.substr(1, s.length() - 1);
@@ -3684,7 +3671,7 @@ bool String::is_valid_identifier() const {
}
}
- bool valid_char = is_digit(str[i]) || is_lower_case(str[i]) || is_upper_case(str[i]) || str[i] == '_';
+ bool valid_char = is_ascii_identifier_char(str[i]);
if (!valid_char) {
return false;
@@ -3709,7 +3696,7 @@ String String::uri_encode() const {
String res;
for (int i = 0; i < temp.length(); ++i) {
char ord = temp[i];
- if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || is_lower_case(ord) || is_upper_case(ord) || is_digit(ord)) {
+ if (ord == '.' || ord == '-' || ord == '~' || is_ascii_identifier_char(ord)) {
res += ord;
} else {
char h_Val[3];
@@ -3731,9 +3718,9 @@ String String::uri_decode() const {
for (int i = 0; i < src.length(); ++i) {
if (src[i] == '%' && i + 2 < src.length()) {
char ord1 = src[i + 1];
- if (is_digit(ord1) || is_upper_case(ord1)) {
+ if (is_digit(ord1) || is_ascii_upper_case(ord1)) {
char ord2 = src[i + 2];
- if (is_digit(ord2) || is_upper_case(ord2)) {
+ if (is_digit(ord2) || is_ascii_upper_case(ord2)) {
char bytes[3] = { (char)ord1, (char)ord2, 0 };
res += (char)strtol(bytes, nullptr, 16);
i += 2;
@@ -3860,7 +3847,7 @@ static _FORCE_INLINE_ int _xml_unescape(const char32_t *p_src, int p_src_len, ch
for (int i = 2; i < p_src_len; i++) {
eat = i + 1;
char32_t ct = p_src[i];
- if (ct == ';' || ct < '0' || ct > '9') {
+ if (ct == ';' || !is_digit(ct)) {
break;
}
}
@@ -3990,7 +3977,7 @@ String String::pad_zeros(int p_digits) const {
int begin = 0;
- while (begin < end && (s[begin] < '0' || s[begin] > '9')) {
+ while (begin < end && !is_digit(s[begin])) {
begin++;
}
@@ -4035,7 +4022,7 @@ bool String::is_valid_int() const {
}
for (int i = from; i < len; i++) {
- if (operator[](i) < '0' || operator[](i) > '9') {
+ if (!is_digit(operator[](i))) {
return false; // no start with number plz
}
}
@@ -4137,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://", "/");
@@ -4160,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());
}
@@ -4271,13 +4253,13 @@ bool String::is_relative_path() const {
String String::get_base_dir() const {
int end = 0;
- // url scheme style base
+ // URL scheme style base.
int basepos = find("://");
if (basepos != -1) {
end = basepos + 3;
}
- // windows top level directory base
+ // Windows top level directory base.
if (end == 0) {
basepos = find(":/");
if (basepos == -1) {
@@ -4288,7 +4270,24 @@ String String::get_base_dir() const {
}
}
- // unix root directory base
+ // Windows UNC network share path.
+ if (end == 0) {
+ if (is_network_share_path()) {
+ basepos = find("/", 2);
+ if (basepos == -1) {
+ basepos = find("\\", 2);
+ }
+ int servpos = find("/", basepos + 1);
+ if (servpos == -1) {
+ servpos = find("\\", basepos + 1);
+ }
+ if (servpos != -1) {
+ end = servpos + 1;
+ }
+ }
+ }
+
+ // Unix root directory base.
if (end == 0) {
if (begins_with("/")) {
end = 1;
@@ -4354,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(" ");
@@ -4428,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;
@@ -4481,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 {
@@ -4489,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;
@@ -4521,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);
}
@@ -4538,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);
}
}
@@ -4627,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;
@@ -4676,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;
@@ -4777,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);
@@ -4785,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);
@@ -4797,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) {
@@ -4813,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();
@@ -4829,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);
@@ -4842,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);