feat: updated engine version to 4.4-rc1

This commit is contained in:
Sara 2025-02-23 14:38:14 +01:00
parent ee00efde1f
commit 21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
from misc.utility.scons_hints import *
Import("env")

File diff suppressed because it is too large Load diff

View file

@ -38,97 +38,97 @@
#define BSEARCH_CHAR_RANGE(m_array) \
int low = 0; \
int high = sizeof(m_array) / sizeof(m_array[0]) - 1; \
int middle; \
int middle = (low + high) / 2; \
\
while (low <= high) { \
middle = (low + high) / 2; \
\
if (c < m_array[middle].start) { \
if (p_char < m_array[middle].start) { \
high = middle - 1; \
} else if (c > m_array[middle].end) { \
} else if (p_char > m_array[middle].end) { \
low = middle + 1; \
} else { \
return true; \
} \
\
middle = (low + high) / 2; \
} \
\
return false
static _FORCE_INLINE_ bool is_unicode_identifier_start(char32_t c) {
constexpr bool is_unicode_identifier_start(char32_t p_char) {
BSEARCH_CHAR_RANGE(xid_start);
}
static _FORCE_INLINE_ bool is_unicode_identifier_continue(char32_t c) {
constexpr bool is_unicode_identifier_continue(char32_t p_char) {
BSEARCH_CHAR_RANGE(xid_continue);
}
static _FORCE_INLINE_ bool is_unicode_upper_case(char32_t c) {
constexpr bool is_unicode_upper_case(char32_t p_char) {
BSEARCH_CHAR_RANGE(uppercase_letter);
}
static _FORCE_INLINE_ bool is_unicode_lower_case(char32_t c) {
constexpr bool is_unicode_lower_case(char32_t p_char) {
BSEARCH_CHAR_RANGE(lowercase_letter);
}
static _FORCE_INLINE_ bool is_unicode_letter(char32_t c) {
constexpr bool is_unicode_letter(char32_t p_char) {
BSEARCH_CHAR_RANGE(unicode_letter);
}
#undef BSEARCH_CHAR_RANGE
static _FORCE_INLINE_ bool is_ascii_upper_case(char32_t c) {
return (c >= 'A' && c <= 'Z');
constexpr bool is_ascii_upper_case(char32_t p_char) {
return (p_char >= 'A' && p_char <= 'Z');
}
static _FORCE_INLINE_ bool is_ascii_lower_case(char32_t c) {
return (c >= 'a' && c <= 'z');
constexpr bool is_ascii_lower_case(char32_t p_char) {
return (p_char >= 'a' && p_char <= 'z');
}
static _FORCE_INLINE_ bool is_digit(char32_t c) {
return (c >= '0' && c <= '9');
constexpr bool is_digit(char32_t p_char) {
return (p_char >= '0' && p_char <= '9');
}
static _FORCE_INLINE_ bool is_hex_digit(char32_t c) {
return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
constexpr bool is_hex_digit(char32_t p_char) {
return (is_digit(p_char) || (p_char >= 'a' && p_char <= 'f') || (p_char >= 'A' && p_char <= 'F'));
}
static _FORCE_INLINE_ bool is_binary_digit(char32_t c) {
return (c == '0' || c == '1');
constexpr bool is_binary_digit(char32_t p_char) {
return (p_char == '0' || p_char == '1');
}
static _FORCE_INLINE_ bool is_ascii_alphabet_char(char32_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
constexpr bool is_ascii_alphabet_char(char32_t p_char) {
return (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z');
}
static _FORCE_INLINE_ bool is_ascii_alphanumeric_char(char32_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
constexpr bool is_ascii_alphanumeric_char(char32_t p_char) {
return (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z') || (p_char >= '0' && p_char <= '9');
}
static _FORCE_INLINE_ bool is_ascii_identifier_char(char32_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
constexpr bool is_ascii_identifier_char(char32_t p_char) {
return (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z') || (p_char >= '0' && p_char <= '9') || p_char == '_';
}
static _FORCE_INLINE_ bool is_symbol(char32_t c) {
return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' ');
constexpr bool is_symbol(char32_t p_char) {
return p_char != '_' && ((p_char >= '!' && p_char <= '/') || (p_char >= ':' && p_char <= '@') || (p_char >= '[' && p_char <= '`') || (p_char >= '{' && p_char <= '~') || p_char == '\t' || p_char == ' ');
}
static _FORCE_INLINE_ bool is_control(char32_t p_char) {
constexpr bool is_control(char32_t p_char) {
return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009f);
}
static _FORCE_INLINE_ bool is_whitespace(char32_t p_char) {
return (p_char == ' ') || (p_char == 0x00a0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085);
constexpr bool is_whitespace(char32_t p_char) {
return (p_char == ' ') || (p_char == 0x00a0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200b) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085);
}
static _FORCE_INLINE_ bool is_linebreak(char32_t p_char) {
constexpr bool is_linebreak(char32_t p_char) {
return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029);
}
static _FORCE_INLINE_ bool is_punct(char32_t p_char) {
constexpr bool is_punct(char32_t p_char) {
return (p_char >= ' ' && p_char <= '/') || (p_char >= ':' && p_char <= '@') || (p_char >= '[' && p_char <= '^') || (p_char == '`') || (p_char >= '{' && p_char <= '~') || (p_char >= 0x2000 && p_char <= 0x206f) || (p_char >= 0x3000 && p_char <= 0x303f);
}
static _FORCE_INLINE_ bool is_underscore(char32_t p_char) {
constexpr bool is_underscore(char32_t p_char) {
return (p_char == '_');
}

View file

@ -0,0 +1,349 @@
/**************************************************************************/
/* fuzzy_search.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "fuzzy_search.h"
constexpr float cull_factor = 0.1f;
constexpr float cull_cutoff = 30.0f;
const String boundary_chars = "/\\-_.";
static bool _is_valid_interval(const Vector2i &p_interval) {
// Empty intervals are represented as (-1, -1).
return p_interval.x >= 0 && p_interval.y >= p_interval.x;
}
static Vector2i _extend_interval(const Vector2i &p_a, const Vector2i &p_b) {
if (!_is_valid_interval(p_a)) {
return p_b;
}
if (!_is_valid_interval(p_b)) {
return p_a;
}
return Vector2i(MIN(p_a.x, p_b.x), MAX(p_a.y, p_b.y));
}
static bool _is_word_boundary(const String &p_str, int p_index) {
if (p_index == -1 || p_index == p_str.size()) {
return true;
}
return boundary_chars.find_char(p_str[p_index]) != -1;
}
bool FuzzySearchToken::try_exact_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset) const {
p_match.token_idx = idx;
p_match.token_length = string.length();
int match_idx = p_target.find(string, p_offset);
if (match_idx == -1) {
return false;
}
p_match.add_substring(match_idx, string.length());
return true;
}
bool FuzzySearchToken::try_fuzzy_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset, int p_miss_budget) const {
p_match.token_idx = idx;
p_match.token_length = string.length();
int run_start = -1;
int run_len = 0;
// Search for the subsequence p_token in p_target starting from p_offset, recording each substring for
// later scoring and display.
for (int i = 0; i < string.length(); i++) {
int new_offset = p_target.find_char(string[i], p_offset);
if (new_offset < 0) {
p_miss_budget--;
if (p_miss_budget < 0) {
return false;
}
} else {
if (run_start == -1 || p_offset != new_offset) {
if (run_start != -1) {
p_match.add_substring(run_start, run_len);
}
run_start = new_offset;
run_len = 1;
} else {
run_len += 1;
}
p_offset = new_offset + 1;
}
}
if (run_start != -1) {
p_match.add_substring(run_start, run_len);
}
return true;
}
void FuzzyTokenMatch::add_substring(int p_substring_start, int p_substring_length) {
substrings.append(Vector2i(p_substring_start, p_substring_length));
matched_length += p_substring_length;
Vector2i substring_interval = { p_substring_start, p_substring_start + p_substring_length - 1 };
interval = _extend_interval(interval, substring_interval);
}
bool FuzzyTokenMatch::intersects(const Vector2i &p_other_interval) const {
if (!_is_valid_interval(interval) || !_is_valid_interval(p_other_interval)) {
return false;
}
return interval.y >= p_other_interval.x && interval.x <= p_other_interval.y;
}
bool FuzzySearchResult::can_add_token_match(const FuzzyTokenMatch &p_match) const {
if (p_match.get_miss_count() > miss_budget) {
return false;
}
if (p_match.intersects(match_interval)) {
if (token_matches.size() == 1) {
return false;
}
for (const FuzzyTokenMatch &existing_match : token_matches) {
if (existing_match.intersects(p_match.interval)) {
return false;
}
}
}
return true;
}
bool FuzzyTokenMatch::is_case_insensitive(const String &p_original, const String &p_adjusted) const {
for (const Vector2i &substr : substrings) {
const int end = substr.x + substr.y;
for (int i = substr.x; i < end; i++) {
if (p_original[i] != p_adjusted[i]) {
return true;
}
}
}
return false;
}
void FuzzySearchResult::score_token_match(FuzzyTokenMatch &p_match, bool p_case_insensitive) const {
// This can always be tweaked more. The intuition is that exact matches should almost always
// be prioritized over broken up matches, and other criteria more or less act as tie breakers.
p_match.score = -20 * p_match.get_miss_count() - (p_case_insensitive ? 3 : 0);
for (const Vector2i &substring : p_match.substrings) {
// Score longer substrings higher than short substrings.
int substring_score = substring.y * substring.y;
// Score matches deeper in path higher than shallower matches
if (substring.x > dir_index) {
substring_score *= 2;
}
// Score matches on a word boundary higher than matches within a word
if (_is_word_boundary(target, substring.x - 1) || _is_word_boundary(target, substring.x + substring.y)) {
substring_score += 4;
}
// Score exact query matches higher than non-compact subsequence matches
if (substring.y == p_match.token_length) {
substring_score += 100;
}
p_match.score += substring_score;
}
}
void FuzzySearchResult::maybe_apply_score_bonus() {
// This adds a small bonus to results which match tokens in the same order they appear in the query.
int *token_range_starts = (int *)alloca(sizeof(int) * token_matches.size());
for (const FuzzyTokenMatch &match : token_matches) {
token_range_starts[match.token_idx] = match.interval.x;
}
int last = token_range_starts[0];
for (int i = 1; i < token_matches.size(); i++) {
if (last > token_range_starts[i]) {
return;
}
last = token_range_starts[i];
}
score += 1;
}
void FuzzySearchResult::add_token_match(const FuzzyTokenMatch &p_match) {
score += p_match.score;
match_interval = _extend_interval(match_interval, p_match.interval);
miss_budget -= p_match.get_miss_count();
token_matches.append(p_match);
}
void remove_low_scores(Vector<FuzzySearchResult> &p_results, float p_cull_score) {
// Removes all results with score < p_cull_score in-place.
int i = 0;
int j = p_results.size() - 1;
FuzzySearchResult *results = p_results.ptrw();
while (true) {
// Advances i to an element to remove and j to an element to keep.
while (j >= i && results[j].score < p_cull_score) {
j--;
}
while (i < j && results[i].score >= p_cull_score) {
i++;
}
if (i >= j) {
break;
}
results[i++] = results[j--];
}
p_results.resize(j + 1);
}
void FuzzySearch::sort_and_filter(Vector<FuzzySearchResult> &p_results) const {
if (p_results.is_empty()) {
return;
}
float avg_score = 0;
float max_score = 0;
for (const FuzzySearchResult &result : p_results) {
avg_score += result.score;
max_score = MAX(max_score, result.score);
}
// TODO: Tune scoring and culling here to display fewer subsequence soup matches when good matches
// are available.
avg_score /= p_results.size();
float cull_score = MIN(cull_cutoff, Math::lerp(avg_score, max_score, cull_factor));
remove_low_scores(p_results, cull_score);
struct FuzzySearchResultComparator {
bool operator()(const FuzzySearchResult &p_lhs, const FuzzySearchResult &p_rhs) const {
// Sort on (score, length, alphanumeric) to ensure consistent ordering.
if (p_lhs.score == p_rhs.score) {
if (p_lhs.target.length() == p_rhs.target.length()) {
return p_lhs.target < p_rhs.target;
}
return p_lhs.target.length() < p_rhs.target.length();
}
return p_lhs.score > p_rhs.score;
}
};
SortArray<FuzzySearchResult, FuzzySearchResultComparator> sorter;
if (p_results.size() > max_results) {
sorter.partial_sort(0, p_results.size(), max_results, p_results.ptrw());
p_results.resize(max_results);
} else {
sorter.sort(p_results.ptrw(), p_results.size());
}
}
void FuzzySearch::set_query(const String &p_query) {
tokens.clear();
for (const String &string : p_query.split(" ", false)) {
tokens.append({ static_cast<int>(tokens.size()), string });
}
case_sensitive = !p_query.is_lowercase();
struct TokenComparator {
bool operator()(const FuzzySearchToken &A, const FuzzySearchToken &B) const {
if (A.string.length() == B.string.length()) {
return A.idx < B.idx;
}
return A.string.length() > B.string.length();
}
};
// Prioritize matching longer tokens before shorter ones since match overlaps are not accepted.
tokens.sort_custom<TokenComparator>();
}
bool FuzzySearch::search(const String &p_target, FuzzySearchResult &p_result) const {
p_result.target = p_target;
p_result.dir_index = p_target.rfind_char('/');
p_result.miss_budget = max_misses;
String adjusted_target = case_sensitive ? p_target : p_target.to_lower();
// For each token, eagerly generate subsequences starting from index 0 and keep the best scoring one
// which does not conflict with prior token matches. This is not ensured to find the highest scoring
// combination of matches, or necessarily the highest scoring single subsequence, as it only considers
// eager subsequences for a given index, and likewise eagerly finds matches for each token in sequence.
for (const FuzzySearchToken &token : tokens) {
FuzzyTokenMatch best_match;
int offset = start_offset;
while (true) {
FuzzyTokenMatch match;
if (allow_subsequences) {
if (!token.try_fuzzy_match(match, adjusted_target, offset, p_result.miss_budget)) {
break;
}
} else {
if (!token.try_exact_match(match, adjusted_target, offset)) {
break;
}
}
if (p_result.can_add_token_match(match)) {
p_result.score_token_match(match, match.is_case_insensitive(p_target, adjusted_target));
if (best_match.token_idx == -1 || best_match.score < match.score) {
best_match = match;
}
}
if (_is_valid_interval(match.interval)) {
offset = match.interval.x + 1;
} else {
break;
}
}
if (best_match.token_idx == -1) {
return false;
}
p_result.add_token_match(best_match);
}
p_result.maybe_apply_score_bonus();
return true;
}
void FuzzySearch::search_all(const PackedStringArray &p_targets, Vector<FuzzySearchResult> &p_results) const {
p_results.clear();
for (const String &target : p_targets) {
FuzzySearchResult result;
if (search(target, result)) {
p_results.append(result);
}
}
sort_and_filter(p_results);
}

View file

@ -0,0 +1,101 @@
/**************************************************************************/
/* fuzzy_search.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef FUZZY_SEARCH_H
#define FUZZY_SEARCH_H
#include "core/variant/variant.h"
class FuzzyTokenMatch;
struct FuzzySearchToken {
int idx = -1;
String string;
bool try_exact_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset) const;
bool try_fuzzy_match(FuzzyTokenMatch &p_match, const String &p_target, int p_offset, int p_miss_budget) const;
};
class FuzzyTokenMatch {
friend struct FuzzySearchToken;
friend class FuzzySearchResult;
friend class FuzzySearch;
int matched_length = 0;
int token_length = 0;
int token_idx = -1;
Vector2i interval = Vector2i(-1, -1); // x and y are both inclusive indices.
void add_substring(int p_substring_start, int p_substring_length);
bool intersects(const Vector2i &p_other_interval) const;
bool is_case_insensitive(const String &p_original, const String &p_adjusted) const;
int get_miss_count() const { return token_length - matched_length; }
public:
int score = 0;
Vector<Vector2i> substrings; // x is start index, y is length.
};
class FuzzySearchResult {
friend class FuzzySearch;
int miss_budget = 0;
Vector2i match_interval = Vector2i(-1, -1);
bool can_add_token_match(const FuzzyTokenMatch &p_match) const;
void score_token_match(FuzzyTokenMatch &p_match, bool p_case_insensitive) const;
void add_token_match(const FuzzyTokenMatch &p_match);
void maybe_apply_score_bonus();
public:
String target;
int score = 0;
int dir_index = -1;
Vector<FuzzyTokenMatch> token_matches;
};
class FuzzySearch {
Vector<FuzzySearchToken> tokens;
void sort_and_filter(Vector<FuzzySearchResult> &p_results) const;
public:
int start_offset = 0;
bool case_sensitive = false;
int max_results = 100;
int max_misses = 2;
bool allow_subsequences = true;
void set_query(const String &p_query);
bool search(const String &p_target, FuzzySearchResult &p_result) const;
void search_all(const PackedStringArray &p_targets, Vector<FuzzySearchResult> &p_results) const;
};
#endif // FUZZY_SEARCH_H

View file

@ -30,7 +30,7 @@
#include "node_path.h"
#include "core/string/print_string.h"
#include "core/variant/variant.h"
void NodePath::_update_hash_cache() const {
uint32_t h = data->absolute ? 1 : 0;
@ -215,7 +215,10 @@ StringName NodePath::get_concatenated_names() const {
String concatenated;
const StringName *sn = data->path.ptr();
for (int i = 0; i < pc; i++) {
concatenated += i == 0 ? sn[i].operator String() : "/" + sn[i];
if (i > 0) {
concatenated += "/";
}
concatenated += sn[i].operator String();
}
data->concatenated_path = concatenated;
}
@ -230,7 +233,10 @@ StringName NodePath::get_concatenated_subnames() const {
String concatenated;
const StringName *ssn = data->subpath.ptr();
for (int i = 0; i < spc; i++) {
concatenated += i == 0 ? ssn[i].operator String() : ":" + ssn[i];
if (i > 0) {
concatenated += ":";
}
concatenated += ssn[i].operator String();
}
data->concatenated_subpath = concatenated;
}
@ -249,7 +255,7 @@ NodePath NodePath::slice(int p_begin, int p_end) const {
if (end < 0) {
end += total_count;
}
const int sub_begin = MAX(begin - name_count - 1, 0);
const int sub_begin = MAX(begin - name_count, 0);
const int sub_end = MAX(end - name_count, 0);
const Vector<StringName> names = get_names().slice(begin, end);
@ -401,7 +407,7 @@ NodePath::NodePath(const String &p_path) {
bool absolute = (path[0] == '/');
bool last_is_slash = true;
int slices = 0;
int subpath_pos = path.find(":");
int subpath_pos = path.find_char(':');
if (subpath_pos != -1) {
int from = subpath_pos + 1;
@ -414,7 +420,7 @@ NodePath::NodePath(const String &p_path) {
continue; // Allow end-of-path :
}
ERR_FAIL_MSG("Invalid NodePath '" + p_path + "'.");
ERR_FAIL_MSG(vformat("Invalid NodePath '%s'.", p_path));
}
subpath.push_back(str);

View file

@ -33,8 +33,6 @@
#include "core/core_globals.h"
#include "core/os/os.h"
#include <stdio.h>
static PrintHandlerList *print_handler_list = nullptr;
void add_print_handler(PrintHandlerList *p_handler) {
@ -93,80 +91,178 @@ void __print_line_rich(const String &p_string) {
// Convert a subset of BBCode tags to ANSI escape codes for correct display in the terminal.
// Support of those ANSI escape codes varies across terminal emulators,
// especially for italic and strikethrough.
String p_string_ansi = p_string;
p_string_ansi = p_string_ansi.replace("[b]", "\u001b[1m");
p_string_ansi = p_string_ansi.replace("[/b]", "\u001b[22m");
p_string_ansi = p_string_ansi.replace("[i]", "\u001b[3m");
p_string_ansi = p_string_ansi.replace("[/i]", "\u001b[23m");
p_string_ansi = p_string_ansi.replace("[u]", "\u001b[4m");
p_string_ansi = p_string_ansi.replace("[/u]", "\u001b[24m");
p_string_ansi = p_string_ansi.replace("[s]", "\u001b[9m");
p_string_ansi = p_string_ansi.replace("[/s]", "\u001b[29m");
String output;
int pos = 0;
while (pos <= p_string.length()) {
int brk_pos = p_string.find_char('[', pos);
p_string_ansi = p_string_ansi.replace("[indent]", " ");
p_string_ansi = p_string_ansi.replace("[/indent]", "");
p_string_ansi = p_string_ansi.replace("[code]", "\u001b[2m");
p_string_ansi = p_string_ansi.replace("[/code]", "\u001b[22m");
p_string_ansi = p_string_ansi.replace("[url]", "");
p_string_ansi = p_string_ansi.replace("[/url]", "");
p_string_ansi = p_string_ansi.replace("[center]", "\n\t\t\t");
p_string_ansi = p_string_ansi.replace("[/center]", "");
p_string_ansi = p_string_ansi.replace("[right]", "\n\t\t\t\t\t\t");
p_string_ansi = p_string_ansi.replace("[/right]", "");
if (brk_pos < 0) {
brk_pos = p_string.length();
}
if (p_string_ansi.contains("[color")) {
p_string_ansi = p_string_ansi.replace("[color=black]", "\u001b[30m");
p_string_ansi = p_string_ansi.replace("[color=red]", "\u001b[91m");
p_string_ansi = p_string_ansi.replace("[color=green]", "\u001b[92m");
p_string_ansi = p_string_ansi.replace("[color=lime]", "\u001b[92m");
p_string_ansi = p_string_ansi.replace("[color=yellow]", "\u001b[93m");
p_string_ansi = p_string_ansi.replace("[color=blue]", "\u001b[94m");
p_string_ansi = p_string_ansi.replace("[color=magenta]", "\u001b[95m");
p_string_ansi = p_string_ansi.replace("[color=pink]", "\u001b[38;5;218m");
p_string_ansi = p_string_ansi.replace("[color=purple]", "\u001b[38;5;98m");
p_string_ansi = p_string_ansi.replace("[color=cyan]", "\u001b[96m");
p_string_ansi = p_string_ansi.replace("[color=white]", "\u001b[97m");
p_string_ansi = p_string_ansi.replace("[color=orange]", "\u001b[38;5;208m");
p_string_ansi = p_string_ansi.replace("[color=gray]", "\u001b[90m");
p_string_ansi = p_string_ansi.replace("[/color]", "\u001b[39m");
}
if (p_string_ansi.contains("[bgcolor")) {
p_string_ansi = p_string_ansi.replace("[bgcolor=black]", "\u001b[40m");
p_string_ansi = p_string_ansi.replace("[bgcolor=red]", "\u001b[101m");
p_string_ansi = p_string_ansi.replace("[bgcolor=green]", "\u001b[102m");
p_string_ansi = p_string_ansi.replace("[bgcolor=lime]", "\u001b[102m");
p_string_ansi = p_string_ansi.replace("[bgcolor=yellow]", "\u001b[103m");
p_string_ansi = p_string_ansi.replace("[bgcolor=blue]", "\u001b[104m");
p_string_ansi = p_string_ansi.replace("[bgcolor=magenta]", "\u001b[105m");
p_string_ansi = p_string_ansi.replace("[bgcolor=pink]", "\u001b[48;5;218m");
p_string_ansi = p_string_ansi.replace("[bgcolor=purple]", "\u001b[48;5;98m");
p_string_ansi = p_string_ansi.replace("[bgcolor=cyan]", "\u001b[106m");
p_string_ansi = p_string_ansi.replace("[bgcolor=white]", "\u001b[107m");
p_string_ansi = p_string_ansi.replace("[bgcolor=orange]", "\u001b[48;5;208m");
p_string_ansi = p_string_ansi.replace("[bgcolor=gray]", "\u001b[100m");
p_string_ansi = p_string_ansi.replace("[/bgcolor]", "\u001b[49m");
}
if (p_string_ansi.contains("[fgcolor")) {
p_string_ansi = p_string_ansi.replace("[fgcolor=black]", "\u001b[30;40m");
p_string_ansi = p_string_ansi.replace("[fgcolor=red]", "\u001b[91;101m");
p_string_ansi = p_string_ansi.replace("[fgcolor=green]", "\u001b[92;102m");
p_string_ansi = p_string_ansi.replace("[fgcolor=lime]", "\u001b[92;102m");
p_string_ansi = p_string_ansi.replace("[fgcolor=yellow]", "\u001b[93;103m");
p_string_ansi = p_string_ansi.replace("[fgcolor=blue]", "\u001b[94;104m");
p_string_ansi = p_string_ansi.replace("[fgcolor=magenta]", "\u001b[95;105m");
p_string_ansi = p_string_ansi.replace("[fgcolor=pink]", "\u001b[38;5;218;48;5;218m");
p_string_ansi = p_string_ansi.replace("[fgcolor=purple]", "\u001b[38;5;98;48;5;98m");
p_string_ansi = p_string_ansi.replace("[fgcolor=cyan]", "\u001b[96;106m");
p_string_ansi = p_string_ansi.replace("[fgcolor=white]", "\u001b[97;107m");
p_string_ansi = p_string_ansi.replace("[fgcolor=orange]", "\u001b[38;5;208;48;5;208m");
p_string_ansi = p_string_ansi.replace("[fgcolor=gray]", "\u001b[90;100m");
p_string_ansi = p_string_ansi.replace("[/fgcolor]", "\u001b[39;49m");
String txt = brk_pos > pos ? p_string.substr(pos, brk_pos - pos) : "";
if (brk_pos == p_string.length()) {
output += txt;
break;
}
int brk_end = p_string.find_char(']', brk_pos + 1);
if (brk_end == -1) {
txt += p_string.substr(brk_pos, p_string.length() - brk_pos);
output += txt;
break;
}
pos = brk_end + 1;
output += txt;
String tag = p_string.substr(brk_pos + 1, brk_end - brk_pos - 1);
if (tag == "b") {
output += "\u001b[1m";
} else if (tag == "/b") {
output += "\u001b[22m";
} else if (tag == "i") {
output += "\u001b[3m";
} else if (tag == "/i") {
output += "\u001b[23m";
} else if (tag == "u") {
output += "\u001b[4m";
} else if (tag == "/u") {
output += "\u001b[24m";
} else if (tag == "s") {
output += "\u001b[9m";
} else if (tag == "/s") {
output += "\u001b[29m";
} else if (tag == "indent") {
output += " ";
} else if (tag == "/indent") {
output += "";
} else if (tag == "code") {
output += "\u001b[2m";
} else if (tag == "/code") {
output += "\u001b[22m";
} else if (tag == "url") {
output += "";
} else if (tag == "/url") {
output += "";
} else if (tag == "center") {
output += "\n\t\t\t";
} else if (tag == "center") {
output += "";
} else if (tag == "right") {
output += "\n\t\t\t\t\t\t";
} else if (tag == "/right") {
output += "";
} else if (tag.begins_with("color=")) {
String color_name = tag.trim_prefix("color=");
if (color_name == "black") {
output += "\u001b[30m";
} else if (color_name == "red") {
output += "\u001b[91m";
} else if (color_name == "green") {
output += "\u001b[92m";
} else if (color_name == "lime") {
output += "\u001b[92m";
} else if (color_name == "yellow") {
output += "\u001b[93m";
} else if (color_name == "blue") {
output += "\u001b[94m";
} else if (color_name == "magenta") {
output += "\u001b[95m";
} else if (color_name == "pink") {
output += "\u001b[38;5;218m";
} else if (color_name == "purple") {
output += "\u001b[38;5;98m";
} else if (color_name == "cyan") {
output += "\u001b[96m";
} else if (color_name == "white") {
output += "\u001b[97m";
} else if (color_name == "orange") {
output += "\u001b[38;5;208m";
} else if (color_name == "gray") {
output += "\u001b[90m";
} else {
Color c = Color::from_string(color_name, Color());
output += vformat("\u001b[38;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255);
}
} else if (tag == "/color") {
output += "\u001b[39m";
} else if (tag.begins_with("bgcolor=")) {
String color_name = tag.trim_prefix("bgcolor=");
if (color_name == "black") {
output += "\u001b[40m";
} else if (color_name == "red") {
output += "\u001b[101m";
} else if (color_name == "green") {
output += "\u001b[102m";
} else if (color_name == "lime") {
output += "\u001b[102m";
} else if (color_name == "yellow") {
output += "\u001b[103m";
} else if (color_name == "blue") {
output += "\u001b[104m";
} else if (color_name == "magenta") {
output += "\u001b[105m";
} else if (color_name == "pink") {
output += "\u001b[48;5;218m";
} else if (color_name == "purple") {
output += "\u001b[48;5;98m";
} else if (color_name == "cyan") {
output += "\u001b[106m";
} else if (color_name == "white") {
output += "\u001b[107m";
} else if (color_name == "orange") {
output += "\u001b[48;5;208m";
} else if (color_name == "gray") {
output += "\u001b[100m";
} else {
Color c = Color::from_string(color_name, Color());
output += vformat("\u001b[48;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255);
}
} else if (tag == "/bgcolor") {
output += "\u001b[49m";
} else if (tag.begins_with("fgcolor=")) {
String color_name = tag.trim_prefix("fgcolor=");
if (color_name == "black") {
output += "\u001b[30;40m";
} else if (color_name == "red") {
output += "\u001b[91;101m";
} else if (color_name == "green") {
output += "\u001b[92;102m";
} else if (color_name == "lime") {
output += "\u001b[92;102m";
} else if (color_name == "yellow") {
output += "\u001b[93;103m";
} else if (color_name == "blue") {
output += "\u001b[94;104m";
} else if (color_name == "magenta") {
output += "\u001b[95;105m";
} else if (color_name == "pink") {
output += "\u001b[38;5;218;48;5;218m";
} else if (color_name == "purple") {
output += "\u001b[38;5;98;48;5;98m";
} else if (color_name == "cyan") {
output += "\u001b[96;106m";
} else if (color_name == "white") {
output += "\u001b[97;107m";
} else if (color_name == "orange") {
output += "\u001b[38;5;208;48;5;208m";
} else if (color_name == "gray") {
output += "\u001b[90;100m";
} else {
Color c = Color::from_string(color_name, Color());
output += vformat("\u001b[38;2;%d;%d;%d;48;2;%d;%d;%dm", c.r * 255, c.g * 255, c.b * 255, c.r * 255, c.g * 255, c.b * 255);
}
} else if (tag == "/fgcolor") {
output += "\u001b[39;49m";
} else {
output += vformat("[%s]", tag);
}
}
output += "\u001b[0m"; // Reset.
p_string_ansi += "\u001b[0m"; // Reset.
OS::get_singleton()->print_rich("%s\n", p_string_ansi.utf8().get_data());
OS::get_singleton()->print_rich("%s\n", output.utf8().get_data());
_global_lock();
PrintHandlerList *l = print_handler_list;

View file

@ -118,7 +118,7 @@ StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const c
template <int SHORT_BUFFER_SIZE>
StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::reserve(int p_size) {
if (p_size < SHORT_BUFFER_SIZE || p_size < buffer.size()) {
if (p_size < SHORT_BUFFER_SIZE || p_size < buffer.size() || !p_size) {
return *this;
}

View file

@ -61,15 +61,19 @@ String StringBuilder::as_string() const {
return "";
}
char32_t *buffer = memnew_arr(char32_t, string_length);
String string;
string.resize(string_length + 1);
char32_t *buffer = string.ptrw();
int current_position = 0;
int godot_string_elem = 0;
int c_string_elem = 0;
for (int i = 0; i < appended_strings.size(); i++) {
if (appended_strings[i] == -1) {
for (uint32_t i = 0; i < appended_strings.size(); i++) {
const int32_t str_len = appended_strings[i];
if (str_len == -1) {
// Godot string
const String &s = strings[godot_string_elem];
@ -81,19 +85,16 @@ String StringBuilder::as_string() const {
} else {
const char *s = c_strings[c_string_elem];
for (int32_t j = 0; j < appended_strings[i]; j++) {
for (int32_t j = 0; j < str_len; j++) {
buffer[current_position + j] = s[j];
}
current_position += appended_strings[i];
current_position += str_len;
c_string_elem++;
}
}
buffer[current_position] = 0;
String final_string = String(buffer, string_length);
memdelete_arr(buffer);
return final_string;
return string;
}

View file

@ -32,17 +32,17 @@
#define STRING_BUILDER_H
#include "core/string/ustring.h"
#include "core/templates/vector.h"
#include "core/templates/local_vector.h"
class StringBuilder {
uint32_t string_length = 0;
Vector<String> strings;
Vector<const char *> c_strings;
LocalVector<String> strings;
LocalVector<const char *> c_strings;
// -1 means it's a Godot String
// a natural number means C string.
Vector<int32_t> appended_strings;
LocalVector<int32_t> appended_strings;
public:
StringBuilder &append(const String &p_string);

View file

@ -39,19 +39,34 @@ StaticCString StaticCString::create(const char *p_ptr) {
return scs;
}
StringName::_Data *StringName::_table[STRING_TABLE_LEN];
bool StringName::_Data::operator==(const String &p_name) const {
if (cname) {
return p_name == cname;
} else {
return name == p_name;
}
}
bool StringName::_Data::operator!=(const String &p_name) const {
return !operator==(p_name);
}
bool StringName::_Data::operator==(const char *p_name) const {
if (cname) {
return strcmp(cname, p_name) == 0;
} else {
return name == p_name;
}
}
bool StringName::_Data::operator!=(const char *p_name) const {
return !operator==(p_name);
}
StringName _scs_create(const char *p_chr, bool p_static) {
return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName());
}
bool StringName::configured = false;
Mutex StringName::mutex;
#ifdef DEBUG_ENABLED
bool StringName::debug_stringname = false;
#endif
void StringName::setup() {
ERR_FAIL_COND(configured);
for (int i = 0; i < STRING_TABLE_LEN; i++) {
@ -147,20 +162,25 @@ void StringName::unref() {
_data = nullptr;
}
uint32_t StringName::get_empty_hash() {
static uint32_t empty_hash = String::hash("");
return empty_hash;
}
bool StringName::operator==(const String &p_name) const {
if (!_data) {
return (p_name.length() == 0);
if (_data) {
return _data->operator==(p_name);
}
return (_data->get_name() == p_name);
return p_name.is_empty();
}
bool StringName::operator==(const char *p_name) const {
if (!_data) {
return (p_name[0] == 0);
if (_data) {
return _data->operator==(p_name);
}
return (_data->get_name() == p_name);
return p_name[0] == 0;
}
bool StringName::operator!=(const String &p_name) const {
@ -171,15 +191,47 @@ bool StringName::operator!=(const char *p_name) const {
return !(operator==(p_name));
}
bool StringName::operator!=(const StringName &p_name) const {
// the real magic of all this mess happens here.
// this is why path comparisons are very fast
return _data != p_name._data;
char32_t StringName::operator[](int p_index) const {
if (_data) {
if (_data->cname) {
CRASH_BAD_INDEX(p_index, static_cast<long>(strlen(_data->cname)));
return _data->cname[p_index];
} else {
return _data->name[p_index];
}
}
CRASH_BAD_INDEX(p_index, 0);
return 0;
}
void StringName::operator=(const StringName &p_name) {
int StringName::length() const {
if (_data) {
if (_data->cname) {
return strlen(_data->cname);
} else {
return _data->name.length();
}
}
return 0;
}
bool StringName::is_empty() const {
if (_data) {
if (_data->cname) {
return _data->cname[0] == 0;
} else {
return _data->name.is_empty();
}
}
return true;
}
StringName &StringName::operator=(const StringName &p_name) {
if (this == &p_name) {
return;
return *this;
}
unref();
@ -187,6 +239,8 @@ void StringName::operator=(const StringName &p_name) {
if (p_name._data && p_name._data->refcount.ref()) {
_data = p_name._data;
}
return *this;
}
StringName::StringName(const StringName &p_name) {
@ -200,11 +254,10 @@ StringName::StringName(const StringName &p_name) {
}
void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) {
mutex.lock();
MutexLock lock(mutex);
if (*ptr == StringName()) {
*ptr = StringName(p_name, true);
}
mutex.unlock();
}
StringName::StringName(const char *p_name, bool p_static) {
@ -216,17 +269,15 @@ StringName::StringName(const char *p_name, bool p_static) {
return; //empty, ignore
}
const uint32_t hash = String::hash(p_name);
const uint32_t idx = hash & STRING_TABLE_MASK;
MutexLock lock(mutex);
uint32_t hash = String::hash(p_name);
uint32_t idx = hash & STRING_TABLE_MASK;
_data = _table[idx];
while (_data) {
// compare hash first
if (_data->hash == hash && _data->get_name() == p_name) {
if (_data->hash == hash && _data->operator==(p_name)) {
break;
}
_data = _data->next;
@ -275,17 +326,15 @@ StringName::StringName(const StaticCString &p_static_string, bool p_static) {
ERR_FAIL_COND(!p_static_string.ptr || !p_static_string.ptr[0]);
const uint32_t hash = String::hash(p_static_string.ptr);
const uint32_t idx = hash & STRING_TABLE_MASK;
MutexLock lock(mutex);
uint32_t hash = String::hash(p_static_string.ptr);
uint32_t idx = hash & STRING_TABLE_MASK;
_data = _table[idx];
while (_data) {
// compare hash first
if (_data->hash == hash && _data->get_name() == p_static_string.ptr) {
if (_data->hash == hash && _data->operator==(p_static_string.ptr)) {
break;
}
_data = _data->next;
@ -335,15 +384,14 @@ StringName::StringName(const String &p_name, bool p_static) {
return;
}
const uint32_t hash = p_name.hash();
const uint32_t idx = hash & STRING_TABLE_MASK;
MutexLock lock(mutex);
uint32_t hash = p_name.hash();
uint32_t idx = hash & STRING_TABLE_MASK;
_data = _table[idx];
while (_data) {
if (_data->hash == hash && _data->get_name() == p_name) {
if (_data->hash == hash && _data->operator==(p_name)) {
break;
}
_data = _data->next;
@ -393,16 +441,15 @@ StringName StringName::search(const char *p_name) {
return StringName();
}
const uint32_t hash = String::hash(p_name);
const uint32_t idx = hash & STRING_TABLE_MASK;
MutexLock lock(mutex);
uint32_t hash = String::hash(p_name);
uint32_t idx = hash & STRING_TABLE_MASK;
_Data *_data = _table[idx];
while (_data) {
// compare hash first
if (_data->hash == hash && _data->get_name() == p_name) {
if (_data->hash == hash && _data->operator==(p_name)) {
break;
}
_data = _data->next;
@ -429,17 +476,15 @@ StringName StringName::search(const char32_t *p_name) {
return StringName();
}
const uint32_t hash = String::hash(p_name);
const uint32_t idx = hash & STRING_TABLE_MASK;
MutexLock lock(mutex);
uint32_t hash = String::hash(p_name);
uint32_t idx = hash & STRING_TABLE_MASK;
_Data *_data = _table[idx];
while (_data) {
// compare hash first
if (_data->hash == hash && _data->get_name() == p_name) {
if (_data->hash == hash && _data->operator==(p_name)) {
break;
}
_data = _data->next;
@ -455,17 +500,15 @@ StringName StringName::search(const char32_t *p_name) {
StringName StringName::search(const String &p_name) {
ERR_FAIL_COND_V(p_name.is_empty(), StringName());
const uint32_t hash = p_name.hash();
const uint32_t idx = hash & STRING_TABLE_MASK;
MutexLock lock(mutex);
uint32_t hash = p_name.hash();
uint32_t idx = hash & STRING_TABLE_MASK;
_Data *_data = _table[idx];
while (_data) {
// compare hash first
if (_data->hash == hash && p_name == _data->get_name()) {
if (_data->hash == hash && _data->operator==(p_name)) {
break;
}
_data = _data->next;
@ -484,15 +527,15 @@ StringName StringName::search(const String &p_name) {
}
bool operator==(const String &p_name, const StringName &p_string_name) {
return p_name == p_string_name.operator String();
return p_string_name.operator==(p_name);
}
bool operator!=(const String &p_name, const StringName &p_string_name) {
return p_name != p_string_name.operator String();
return p_string_name.operator!=(p_name);
}
bool operator==(const char *p_name, const StringName &p_string_name) {
return p_name == p_string_name.operator String();
return p_string_name.operator==(p_name);
}
bool operator!=(const char *p_name, const StringName &p_string_name) {
return p_name != p_string_name.operator String();
return p_string_name.operator!=(p_name);
}

View file

@ -60,6 +60,11 @@ class StringName {
uint32_t debug_references = 0;
#endif
String get_name() const { return cname ? String(cname) : name; }
bool operator==(const String &p_name) const;
bool operator!=(const String &p_name) const;
bool operator==(const char *p_name) const;
bool operator!=(const char *p_name) const;
int idx = 0;
uint32_t hash = 0;
_Data *prev = nullptr;
@ -67,7 +72,7 @@ class StringName {
_Data() {}
};
static _Data *_table[STRING_TABLE_LEN];
static inline _Data *_table[STRING_TABLE_LEN];
_Data *_data = nullptr;
@ -75,10 +80,11 @@ class StringName {
friend void register_core_types();
friend void unregister_core_types();
friend class Main;
static Mutex mutex;
static inline Mutex mutex;
static void setup();
static void cleanup();
static bool configured;
static uint32_t get_empty_hash();
static inline bool configured = false;
#ifdef DEBUG_ENABLED
struct DebugSortReferences {
bool operator()(const _Data *p_left, const _Data *p_right) const {
@ -86,7 +92,7 @@ class StringName {
}
};
static bool debug_stringname;
static inline bool debug_stringname = false;
#endif
StringName(_Data *p_data) { _data = p_data; }
@ -99,6 +105,10 @@ public:
bool operator!=(const String &p_name) const;
bool operator!=(const char *p_name) const;
char32_t operator[](int p_index) const;
int length() const;
bool is_empty() const;
_FORCE_INLINE_ bool is_node_unique_name() const {
if (!_data) {
return false;
@ -122,21 +132,23 @@ public:
return _data >= p_name._data;
}
_FORCE_INLINE_ bool operator==(const StringName &p_name) const {
// the real magic of all this mess happens here.
// this is why path comparisons are very fast
// The real magic of all this mess happens here.
// This is why path comparisons are very fast.
return _data == p_name._data;
}
_FORCE_INLINE_ bool operator!=(const StringName &p_name) const {
return _data != p_name._data;
}
_FORCE_INLINE_ uint32_t hash() const {
if (_data) {
return _data->hash;
} else {
return 0;
return get_empty_hash();
}
}
_FORCE_INLINE_ const void *data_unique_pointer() const {
return (void *)_data;
}
bool operator!=(const StringName &p_name) const;
_FORCE_INLINE_ operator String() const {
if (_data) {
@ -175,9 +187,23 @@ public:
}
};
void operator=(const StringName &p_name);
StringName &operator=(const StringName &p_name);
StringName &operator=(StringName &&p_name) {
if (_data == p_name._data) {
return *this;
}
unref();
_data = p_name._data;
p_name._data = nullptr;
return *this;
}
StringName(const char *p_name, bool p_static = false);
StringName(const StringName &p_name);
StringName(StringName &&p_name) {
_data = p_name._data;
p_name._data = nullptr;
}
StringName(const String &p_name, bool p_static = false);
StringName(const StaticCString &p_static_string, bool p_static = false);
StringName() {}

View file

@ -29,16 +29,10 @@
/**************************************************************************/
#include "translation.h"
#include "translation.compat.inc"
#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
#include "core/os/os.h"
#include "core/string/locales.h"
#ifdef TOOLS_ENABLED
#include "main/main.h"
#endif
#include "core/os/thread.h"
#include "core/string/translation_server.h"
Dictionary Translation::_get_messages() const {
Dictionary d;
@ -86,8 +80,10 @@ void Translation::set_locale(const String &p_locale) {
if (Thread::is_main_thread()) {
_notify_translation_changed_if_applies();
} else {
// Avoid calling non-thread-safe functions here.
callable_mp(this, &Translation::_notify_translation_changed_if_applies).call_deferred();
// This has to happen on the main thread (bypassing the ResourceLoader per-thread call queue)
// because it interacts with the generally non-thread-safe window management, leading to
// different issues across platforms otherwise.
MessageQueue::get_main_singleton()->push_callable(callable_mp(this, &Translation::_notify_translation_changed_if_applies));
}
}
@ -173,911 +169,3 @@ void Translation::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale");
}
///////////////////////////////////////////////
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;
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.
language_map.clear();
int idx = 0;
while (language_list[idx][0] != nullptr) {
language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]);
idx++;
}
// Init locale-script map.
locale_script_info.clear();
idx = 0;
while (locale_scripts[idx][0] != nullptr) {
LocaleScriptInfo info;
info.name = locale_scripts[idx][0];
info.script = locale_scripts[idx][1];
info.default_country = locale_scripts[idx][2];
Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false);
for (int i = 0; i < supported_countries.size(); i++) {
info.supported_countries.insert(supported_countries[i]);
}
locale_script_info.push_back(info);
idx++;
}
// Init supported script list.
script_map.clear();
idx = 0;
while (script_list[idx][0] != nullptr) {
script_map[script_list[idx][1]] = String::utf8(script_list[idx][0]);
idx++;
}
// Init regional variant map.
variant_map.clear();
idx = 0;
while (locale_variants[idx][0] != nullptr) {
variant_map[locale_variants[idx][0]] = locale_variants[idx][1];
idx++;
}
// Init locale renames.
locale_rename_map.clear();
idx = 0;
while (locale_renames[idx][0] != nullptr) {
if (!String(locale_renames[idx][1]).is_empty()) {
locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1];
}
idx++;
}
// Init country names.
country_name_map.clear();
idx = 0;
while (country_names[idx][0] != nullptr) {
country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]);
idx++;
}
// Init country renames.
country_rename_map.clear();
idx = 0;
while (country_renames[idx][0] != nullptr) {
if (!String(country_renames[idx][1]).is_empty()) {
country_rename_map[country_renames[idx][0]] = country_renames[idx][1];
}
idx++;
}
}
String TranslationServer::standardize_locale(const String &p_locale) const {
return _standardize_locale(p_locale, false);
}
String TranslationServer::_standardize_locale(const String &p_locale, bool p_add_defaults) const {
// Replaces '-' with '_' for macOS style locales.
String univ_locale = p_locale.replace("-", "_");
// Extract locale elements.
String lang_name, script_name, country_name, variant_name;
Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_");
lang_name = locale_elements[0];
if (locale_elements.size() >= 2) {
if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
script_name = locale_elements[1];
}
if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
country_name = locale_elements[1];
}
}
if (locale_elements.size() >= 3) {
if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
country_name = locale_elements[2];
} else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang_name) {
variant_name = locale_elements[2].to_lower();
}
}
if (locale_elements.size() >= 4) {
if (variant_map.has(locale_elements[3].to_lower()) && variant_map[locale_elements[3].to_lower()] == lang_name) {
variant_name = locale_elements[3].to_lower();
}
}
// Try extract script and variant from the extra part.
Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";");
for (int i = 0; i < script_extra.size(); i++) {
if (script_extra[i].to_lower() == "cyrillic") {
script_name = "Cyrl";
break;
} else if (script_extra[i].to_lower() == "latin") {
script_name = "Latn";
break;
} else if (script_extra[i].to_lower() == "devanagari") {
script_name = "Deva";
break;
} else if (variant_map.has(script_extra[i].to_lower()) && variant_map[script_extra[i].to_lower()] == lang_name) {
variant_name = script_extra[i].to_lower();
}
}
// Handles known non-ISO language names used e.g. on Windows.
if (locale_rename_map.has(lang_name)) {
lang_name = locale_rename_map[lang_name];
}
// Handle country renames.
if (country_rename_map.has(country_name)) {
country_name = country_rename_map[country_name];
}
// Remove unsupported script codes.
if (!script_map.has(script_name)) {
script_name = "";
}
// Add script code base on language and country codes for some ambiguous cases.
if (p_add_defaults) {
if (script_name.is_empty()) {
for (int i = 0; i < locale_script_info.size(); i++) {
const LocaleScriptInfo &info = locale_script_info[i];
if (info.name == lang_name) {
if (country_name.is_empty() || info.supported_countries.has(country_name)) {
script_name = info.script;
break;
}
}
}
}
if (!script_name.is_empty() && country_name.is_empty()) {
// Add conntry code based on script for some ambiguous cases.
for (int i = 0; i < locale_script_info.size(); i++) {
const LocaleScriptInfo &info = locale_script_info[i];
if (info.name == lang_name && info.script == script_name) {
country_name = info.default_country;
break;
}
}
}
}
// Combine results.
String out = lang_name;
if (!script_name.is_empty()) {
out = out + "_" + script_name;
}
if (!country_name.is_empty()) {
out = out + "_" + country_name;
}
if (!variant_name.is_empty()) {
out = out + "_" + variant_name;
}
return out;
}
int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
String locale_a = _standardize_locale(p_locale_a, true);
String locale_b = _standardize_locale(p_locale_b, true);
if (locale_a == locale_b) {
// Exact match.
return 10;
}
Vector<String> locale_a_elements = locale_a.split("_");
Vector<String> locale_b_elements = locale_b.split("_");
if (locale_a_elements[0] == locale_b_elements[0]) {
// Matching language, both locales have extra parts.
// Return number of matching elements.
int matching_elements = 1;
for (int i = 1; i < locale_a_elements.size(); i++) {
for (int j = 1; j < locale_b_elements.size(); j++) {
if (locale_a_elements[i] == locale_b_elements[j]) {
matching_elements++;
}
}
}
return matching_elements;
} else {
// No match.
return 0;
}
}
String TranslationServer::get_locale_name(const String &p_locale) const {
String lang_name, script_name, country_name;
Vector<String> locale_elements = standardize_locale(p_locale).split("_");
lang_name = locale_elements[0];
if (locale_elements.size() >= 2) {
if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
script_name = locale_elements[1];
}
if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
country_name = locale_elements[1];
}
}
if (locale_elements.size() >= 3) {
if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
country_name = locale_elements[2];
}
}
String name = language_map[lang_name];
if (!script_name.is_empty()) {
name = name + " (" + script_map[script_name] + ")";
}
if (!country_name.is_empty()) {
name = name + ", " + country_name_map[country_name];
}
return name;
}
Vector<String> TranslationServer::get_all_languages() const {
Vector<String> languages;
for (const KeyValue<String, String> &E : language_map) {
languages.push_back(E.key);
}
return languages;
}
String TranslationServer::get_language_name(const String &p_language) const {
return language_map[p_language];
}
Vector<String> TranslationServer::get_all_scripts() const {
Vector<String> scripts;
for (const KeyValue<String, String> &E : script_map) {
scripts.push_back(E.key);
}
return scripts;
}
String TranslationServer::get_script_name(const String &p_script) const {
return script_map[p_script];
}
Vector<String> TranslationServer::get_all_countries() const {
Vector<String> countries;
for (const KeyValue<String, String> &E : country_name_map) {
countries.push_back(E.key);
}
return countries;
}
String TranslationServer::get_country_name(const String &p_country) const {
return country_name_map[p_country];
}
void TranslationServer::set_locale(const String &p_locale) {
String new_locale = standardize_locale(p_locale);
if (locale == new_locale) {
return;
}
locale = new_locale;
ResourceLoader::reload_translation_remaps();
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
}
}
String TranslationServer::get_locale() const {
return locale;
}
PackedStringArray TranslationServer::get_loaded_locales() const {
PackedStringArray locales;
for (const Ref<Translation> &E : translations) {
const Ref<Translation> &t = E;
ERR_FAIL_COND_V(t.is_null(), PackedStringArray());
String l = t->get_locale();
locales.push_back(l);
}
return locales;
}
void TranslationServer::add_translation(const Ref<Translation> &p_translation) {
translations.insert(p_translation);
}
void TranslationServer::remove_translation(const Ref<Translation> &p_translation) {
translations.erase(p_translation);
}
Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
Ref<Translation> res;
int best_score = 0;
for (const Ref<Translation> &E : translations) {
const Ref<Translation> &t = E;
ERR_FAIL_COND_V(t.is_null(), nullptr);
String l = t->get_locale();
int score = compare_locales(p_locale, l);
if (score > 0 && score >= best_score) {
res = t;
best_score = score;
if (score == 10) {
break; // Exact match, skip the rest.
}
}
}
return res;
}
void TranslationServer::clear() {
translations.clear();
}
StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const {
// Match given message against the translation catalog for the project locale.
if (!enabled) {
return p_message;
}
StringName res = _get_message_from_translations(p_message, p_context, locale, false);
if (!res && fallback.length() >= 2) {
res = _get_message_from_translations(p_message, p_context, fallback, false);
}
if (!res) {
return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message;
}
return pseudolocalization_enabled ? pseudolocalize(res) : res;
}
StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
if (!enabled) {
if (p_n == 1) {
return p_message;
}
return p_message_plural;
}
StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n);
if (!res && fallback.length() >= 2) {
res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n);
}
if (!res) {
if (p_n == 1) {
return p_message;
}
return p_message_plural;
}
return res;
}
StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const {
StringName res;
int best_score = 0;
for (const Ref<Translation> &E : translations) {
const Ref<Translation> &t = E;
ERR_FAIL_COND_V(t.is_null(), p_message);
String l = t->get_locale();
int score = compare_locales(p_locale, l);
if (score > 0 && score >= best_score) {
StringName r;
if (!plural) {
r = t->get_message(p_message, p_context);
} else {
r = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
}
if (!r) {
continue;
}
res = r;
best_score = score;
if (score == 10) {
break; // Exact match, skip the rest.
}
}
}
return res;
}
TranslationServer *TranslationServer::singleton = nullptr;
bool TranslationServer::_load_translations(const String &p_from) {
if (ProjectSettings::get_singleton()->has_setting(p_from)) {
const Vector<String> &translation_names = GLOBAL_GET(p_from);
int tcount = translation_names.size();
if (tcount) {
const String *r = translation_names.ptr();
for (int i = 0; i < tcount; i++) {
Ref<Translation> tr = ResourceLoader::load(r[i]);
if (tr.is_valid()) {
add_translation(tr);
}
}
}
return true;
}
return false;
}
void TranslationServer::setup() {
String test = GLOBAL_DEF("internationalization/locale/test", "");
test = test.strip_edges();
if (!test.is_empty()) {
set_locale(test);
} else {
set_locale(OS::get_singleton()->get_locale());
}
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);
#ifdef TOOLS_ENABLED
ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
#endif
}
void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) {
tool_translation = p_translation;
}
Ref<Translation> TranslationServer::get_tool_translation() const {
return tool_translation;
}
String TranslationServer::get_tool_locale() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
if (TranslationServer::get_singleton()->get_tool_translation().is_valid()) {
return tool_translation->get_locale();
} else {
return "en";
}
} else {
#else
{
#endif
// Look for best matching loaded translation.
String best_locale = "en";
int best_score = 0;
for (const Ref<Translation> &E : translations) {
const Ref<Translation> &t = E;
ERR_FAIL_COND_V(t.is_null(), best_locale);
String l = t->get_locale();
int score = compare_locales(locale, l);
if (score > 0 && score >= best_score) {
best_locale = l;
best_score = score;
if (score == 10) {
break; // Exact match, skip the rest.
}
}
}
return best_locale;
}
}
StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
if (tool_translation.is_valid()) {
StringName r = tool_translation->get_message(p_message, p_context);
if (r) {
return r;
}
}
return p_message;
}
StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
if (tool_translation.is_valid()) {
StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
if (r) {
return r;
}
}
if (p_n == 1) {
return p_message;
}
return p_message_plural;
}
void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) {
property_translation = p_translation;
}
StringName TranslationServer::property_translate(const StringName &p_message, const StringName &p_context) const {
if (property_translation.is_valid()) {
StringName r = property_translation->get_message(p_message, p_context);
if (r) {
return r;
}
}
return p_message;
}
void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
doc_translation = p_translation;
}
StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const {
if (doc_translation.is_valid()) {
StringName r = doc_translation->get_message(p_message, p_context);
if (r) {
return r;
}
}
return p_message;
}
StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
if (doc_translation.is_valid()) {
StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
if (r) {
return r;
}
}
if (p_n == 1) {
return p_message;
}
return p_message_plural;
}
void TranslationServer::set_extractable_translation(const Ref<Translation> &p_translation) {
extractable_translation = p_translation;
}
StringName TranslationServer::extractable_translate(const StringName &p_message, const StringName &p_context) const {
if (extractable_translation.is_valid()) {
StringName r = extractable_translation->get_message(p_message, p_context);
if (r) {
return r;
}
}
return p_message;
}
StringName TranslationServer::extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
if (extractable_translation.is_valid()) {
StringName r = extractable_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
if (r) {
return r;
}
}
if (p_n == 1) {
return p_message;
}
return p_message_plural;
}
bool TranslationServer::is_pseudolocalization_enabled() const {
return pseudolocalization_enabled;
}
void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
pseudolocalization_enabled = p_enabled;
ResourceLoader::reload_translation_remaps();
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
}
}
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");
ResourceLoader::reload_translation_remaps();
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
}
}
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');
}
#ifdef TOOLS_ENABLED
void TranslationServer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
const String pf = p_function;
if (p_idx == 0) {
HashMap<String, String> *target_hash_map = nullptr;
if (pf == "get_language_name") {
target_hash_map = &language_map;
} else if (pf == "get_script_name") {
target_hash_map = &script_map;
} else if (pf == "get_country_name") {
target_hash_map = &country_name_map;
}
if (target_hash_map) {
for (const KeyValue<String, String> &E : *target_hash_map) {
r_options->push_back(E.key.quote());
}
}
}
Object::get_argument_options(p_function, p_idx, r_options);
}
#endif // TOOLS_ENABLED
void TranslationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale);
ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales);
ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale);
ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages);
ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name);
ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts);
ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name);
ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries);
ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name);
ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
}
void TranslationServer::load_translations() {
_load_translations("internationalization/locale/translations"); //all
_load_translations("internationalization/locale/translations_" + locale.substr(0, 2));
if (locale.substr(0, 2) != locale) {
_load_translations("internationalization/locale/translations_" + locale);
}
}
TranslationServer::TranslationServer() {
singleton = this;
init_locale_info();
}

View file

@ -51,10 +51,6 @@ class Translation : public Resource {
protected:
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
static void _bind_compatibility_methods();
#endif
GDVIRTUAL2RC(StringName, _get_message, StringName, StringName);
GDVIRTUAL4RC(StringName, _get_plural_message, StringName, StringName, int, StringName);
@ -74,132 +70,4 @@ public:
Translation() {}
};
class TranslationServer : public Object {
GDCLASS(TranslationServer, Object);
String locale = "en";
String fallback;
HashSet<Ref<Translation>> translations;
Ref<Translation> tool_translation;
Ref<Translation> property_translation;
Ref<Translation> doc_translation;
Ref<Translation> extractable_translation;
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;
StringName _get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural = "", int p_n = 0) const;
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
static void _bind_compatibility_methods();
#endif
struct LocaleScriptInfo {
String name;
String script;
String default_country;
HashSet<String> supported_countries;
};
static Vector<LocaleScriptInfo> locale_script_info;
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();
public:
_FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
void set_enabled(bool p_enabled) { enabled = p_enabled; }
_FORCE_INLINE_ bool is_enabled() const { return enabled; }
void set_locale(const String &p_locale);
String get_locale() const;
Ref<Translation> get_translation_object(const String &p_locale);
Vector<String> get_all_languages() const;
String get_language_name(const String &p_language) const;
Vector<String> get_all_scripts() const;
String get_script_name(const String &p_script) const;
Vector<String> get_all_countries() const;
String get_country_name(const String &p_country) const;
String get_locale_name(const String &p_locale) const;
PackedStringArray get_loaded_locales() const;
void add_translation(const Ref<Translation> &p_translation);
void remove_translation(const Ref<Translation> &p_translation);
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;
StringName pseudolocalize(const StringName &p_message) const;
bool is_pseudolocalization_enabled() const;
void set_pseudolocalization_enabled(bool p_enabled);
void reload_pseudolocalization();
String standardize_locale(const String &p_locale) const;
int compare_locales(const String &p_locale_a, const String &p_locale_b) const;
String get_tool_locale();
void set_tool_translation(const Ref<Translation> &p_translation);
Ref<Translation> get_tool_translation() const;
StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const;
StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
void set_property_translation(const Ref<Translation> &p_translation);
StringName property_translate(const StringName &p_message, const StringName &p_context = "") const;
void set_doc_translation(const Ref<Translation> &p_translation);
StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const;
StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
void set_extractable_translation(const Ref<Translation> &p_translation);
StringName extractable_translate(const StringName &p_message, const StringName &p_context = "") const;
StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
void setup();
void clear();
void load_translations();
#ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif // TOOLS_ENABLED
TranslationServer();
};
#endif // TRANSLATION_H

View file

@ -0,0 +1,460 @@
/**************************************************************************/
/* translation_domain.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "translation_domain.h"
#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;
for (const Ref<Translation> &E : translations) {
ERR_CONTINUE(E.is_null());
int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
if (score > 0 && score >= best_score) {
const StringName r = E->get_message(p_message, p_context);
if (!r) {
continue;
}
res = r;
best_score = score;
if (score == 10) {
break; // Exact match, skip the rest.
}
}
}
return res;
}
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 {
StringName res;
int best_score = 0;
for (const Ref<Translation> &E : translations) {
ERR_CONTINUE(E.is_null());
int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
if (score > 0 && score >= best_score) {
const StringName r = E->get_plural_message(p_message, p_message_plural, p_n, p_context);
if (!r) {
continue;
}
res = r;
best_score = score;
if (score == 10) {
break; // Exact match, skip the rest.
}
}
}
return res;
}
PackedStringArray TranslationDomain::get_loaded_locales() const {
PackedStringArray locales;
for (const Ref<Translation> &E : translations) {
ERR_CONTINUE(E.is_null());
const String &locale = E->get_locale();
if (!locales.has(locale)) {
locales.push_back(locale);
}
}
return locales;
}
Ref<Translation> TranslationDomain::get_translation_object(const String &p_locale) const {
Ref<Translation> res;
int best_score = 0;
for (const Ref<Translation> &E : translations) {
ERR_CONTINUE(E.is_null());
int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
if (score > 0 && score >= best_score) {
res = E;
best_score = score;
if (score == 10) {
break; // Exact match, skip the rest.
}
}
}
return res;
}
void TranslationDomain::add_translation(const Ref<Translation> &p_translation) {
translations.insert(p_translation);
}
void TranslationDomain::remove_translation(const Ref<Translation> &p_translation) {
translations.erase(p_translation);
}
void TranslationDomain::clear() {
translations.clear();
}
StringName TranslationDomain::translate(const StringName &p_message, const StringName &p_context) const {
const String &locale = TranslationServer::get_singleton()->get_locale();
StringName res = get_message_from_translations(locale, p_message, p_context);
const String &fallback = TranslationServer::get_singleton()->get_fallback_locale();
if (!res && fallback.length() >= 2) {
res = get_message_from_translations(fallback, p_message, p_context);
}
if (!res) {
return pseudolocalization.enabled ? pseudolocalize(p_message) : p_message;
}
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 {
const String &locale = TranslationServer::get_singleton()->get_locale();
StringName res = get_message_from_translations(locale, p_message, p_message_plural, p_n, p_context);
const String &fallback = TranslationServer::get_singleton()->get_fallback_locale();
if (!res && fallback.length() >= 2) {
res = get_message_from_translations(fallback, p_message, p_message_plural, p_n, p_context);
}
if (!res) {
if (p_n == 1) {
return p_message;
}
return p_message_plural;
}
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 {
if (p_message.is_empty()) {
return p_message;
}
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);
ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationDomain::remove_translation);
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

@ -0,0 +1,107 @@
/**************************************************************************/
/* translation_domain.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TRANSLATION_DOMAIN_H
#define TRANSLATION_DOMAIN_H
#include "core/object/ref_counted.h"
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();
public:
// Methods in this section are not intended for scripting.
StringName get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_context) const;
StringName 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;
PackedStringArray get_loaded_locales() const;
public:
Ref<Translation> get_translation_object(const String &p_locale) const;
void add_translation(const Ref<Translation> &p_translation);
void remove_translation(const Ref<Translation> &p_translation);
void clear();
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();
};
#endif // TRANSLATION_DOMAIN_H

View file

@ -227,11 +227,11 @@ void TranslationPO::set_plural_rule(const String &p_plural_rule) {
// Set plural_forms and plural_rule.
// p_plural_rule passed in has the form "Plural-Forms: nplurals=2; plural=(n >= 2);".
int first_semi_col = p_plural_rule.find(";");
plural_forms = p_plural_rule.substr(p_plural_rule.find("=") + 1, first_semi_col - (p_plural_rule.find("=") + 1)).to_int();
int first_semi_col = p_plural_rule.find_char(';');
plural_forms = p_plural_rule.substr(p_plural_rule.find_char('=') + 1, first_semi_col - (p_plural_rule.find_char('=') + 1)).to_int();
int expression_start = p_plural_rule.find("=", first_semi_col) + 1;
int second_semi_col = p_plural_rule.rfind(";");
int expression_start = p_plural_rule.find_char('=', first_semi_col) + 1;
int second_semi_col = p_plural_rule.rfind_char(';');
plural_rule = p_plural_rule.substr(expression_start, second_semi_col - expression_start).strip_edges();
// Setup the cache to make evaluating plural rule faster later on.
@ -246,7 +246,7 @@ void TranslationPO::add_message(const StringName &p_src_text, const StringName &
HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
if (map_id_str.has(p_src_text)) {
WARN_PRINT("Double translations for \"" + String(p_src_text) + "\" under the same context \"" + String(p_context) + "\" for locale \"" + get_locale() + "\".\nThere should only be one unique translation for a given string under the same context.");
WARN_PRINT(vformat("Double translations for \"%s\" under the same context \"%s\" for locale \"%s\".\nThere should only be one unique translation for a given string under the same context.", String(p_src_text), String(p_context), get_locale()));
map_id_str[p_src_text].set(0, p_xlated_text);
} else {
map_id_str[p_src_text].push_back(p_xlated_text);
@ -254,12 +254,12 @@ void TranslationPO::add_message(const StringName &p_src_text, const StringName &
}
void TranslationPO::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, "Trying to add plural texts that don't match the required number of plural forms for locale \"" + get_locale() + "\"");
ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, vformat("Trying to add plural texts that don't match the required number of plural forms for locale \"%s\".", get_locale()));
HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
if (map_id_str.has(p_src_text)) {
WARN_PRINT("Double translations for \"" + p_src_text + "\" under the same context \"" + p_context + "\" for locale " + get_locale() + ".\nThere should only be one unique translation for a given string under the same context.");
WARN_PRINT(vformat("Double translations for \"%s\" under the same context \"%s\" for locale %s.\nThere should only be one unique translation for a given string under the same context.", p_src_text, p_context, get_locale()));
map_id_str[p_src_text].clear();
}
@ -280,7 +280,7 @@ StringName TranslationPO::get_message(const StringName &p_src_text, const String
if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
return StringName();
}
ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), vformat("Source text \"%s\" is registered but doesn't have a translation. Please report this bug.", String(p_src_text)));
return translation_map[p_context][p_src_text][0];
}
@ -296,7 +296,7 @@ StringName TranslationPO::get_plural_message(const StringName &p_src_text, const
if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
return StringName();
}
ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].is_empty(), StringName(), vformat("Source text \"%s\" is registered but doesn't have a translation. Please report this bug.", String(p_src_text)));
int plural_index = _get_plural_index(p_n);
ERR_FAIL_COND_V_MSG(plural_index < 0 || translation_map[p_context][p_src_text].size() < plural_index + 1, StringName(), "Plural index returned or number of plural translations is not valid. Please report this bug.");

View file

@ -1,5 +1,5 @@
/**************************************************************************/
/* translation.compat.inc */
/* translation_server.compat.inc */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@ -30,17 +30,12 @@
#ifndef DISABLE_DEPRECATED
void Translation::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("add_message", "src_message", "xlated_message", "context"), &Translation::add_message, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("add_plural_message", "src_message", "xlated_messages", "context"), &Translation::add_plural_message, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("get_message", "src_message", "context"), &Translation::get_message, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL(""));
String TranslationServer::_standardize_locale_bind_compat_98972(const String &p_locale) const {
return standardize_locale(p_locale, false);
}
void TranslationServer::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(""));
ClassDB::bind_compatibility_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::_standardize_locale_bind_compat_98972);
}
#endif
#endif // DISABLE_DEPRECATED

View file

@ -0,0 +1,659 @@
/**************************************************************************/
/* translation_server.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "translation_server.h"
#include "translation_server.compat.inc"
#include "core/config/project_settings.h"
#include "core/io/resource_loader.h"
#include "core/os/os.h"
#include "core/string/locales.h"
#ifdef TOOLS_ENABLED
#include "main/main.h"
#endif
Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
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.
language_map.clear();
int idx = 0;
while (language_list[idx][0] != nullptr) {
language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]);
idx++;
}
// Init locale-script map.
locale_script_info.clear();
idx = 0;
while (locale_scripts[idx][0] != nullptr) {
LocaleScriptInfo info;
info.name = locale_scripts[idx][0];
info.script = locale_scripts[idx][1];
info.default_country = locale_scripts[idx][2];
Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false);
for (int i = 0; i < supported_countries.size(); i++) {
info.supported_countries.insert(supported_countries[i]);
}
locale_script_info.push_back(info);
idx++;
}
// Init supported script list.
script_map.clear();
idx = 0;
while (script_list[idx][0] != nullptr) {
script_map[script_list[idx][1]] = String::utf8(script_list[idx][0]);
idx++;
}
// Init regional variant map.
variant_map.clear();
idx = 0;
while (locale_variants[idx][0] != nullptr) {
variant_map[locale_variants[idx][0]] = locale_variants[idx][1];
idx++;
}
// Init locale renames.
locale_rename_map.clear();
idx = 0;
while (locale_renames[idx][0] != nullptr) {
if (!String(locale_renames[idx][1]).is_empty()) {
locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1];
}
idx++;
}
// Init country names.
country_name_map.clear();
idx = 0;
while (country_names[idx][0] != nullptr) {
country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]);
idx++;
}
// Init country renames.
country_rename_map.clear();
idx = 0;
while (country_renames[idx][0] != nullptr) {
if (!String(country_renames[idx][1]).is_empty()) {
country_rename_map[country_renames[idx][0]] = country_renames[idx][1];
}
idx++;
}
}
TranslationServer::Locale::operator String() const {
String out = language;
if (!script.is_empty()) {
out = out + "_" + script;
}
if (!country.is_empty()) {
out = out + "_" + country;
}
if (!variant.is_empty()) {
out = out + "_" + variant;
}
return out;
}
TranslationServer::Locale::Locale(const TranslationServer &p_server, const String &p_locale, bool p_add_defaults) {
// Replaces '-' with '_' for macOS style locales.
String univ_locale = p_locale.replace("-", "_");
// Extract locale elements.
Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_");
language = locale_elements[0];
if (locale_elements.size() >= 2) {
if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
script = locale_elements[1];
}
if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
country = locale_elements[1];
}
}
if (locale_elements.size() >= 3) {
if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
country = locale_elements[2];
} else if (p_server.variant_map.has(locale_elements[2].to_lower()) && p_server.variant_map[locale_elements[2].to_lower()] == language) {
variant = locale_elements[2].to_lower();
}
}
if (locale_elements.size() >= 4) {
if (p_server.variant_map.has(locale_elements[3].to_lower()) && p_server.variant_map[locale_elements[3].to_lower()] == language) {
variant = locale_elements[3].to_lower();
}
}
// Try extract script and variant from the extra part.
Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";");
for (int i = 0; i < script_extra.size(); i++) {
if (script_extra[i].to_lower() == "cyrillic") {
script = "Cyrl";
break;
} else if (script_extra[i].to_lower() == "latin") {
script = "Latn";
break;
} else if (script_extra[i].to_lower() == "devanagari") {
script = "Deva";
break;
} else if (p_server.variant_map.has(script_extra[i].to_lower()) && p_server.variant_map[script_extra[i].to_lower()] == language) {
variant = script_extra[i].to_lower();
}
}
// Handles known non-ISO language names used e.g. on Windows.
if (p_server.locale_rename_map.has(language)) {
language = p_server.locale_rename_map[language];
}
// Handle country renames.
if (p_server.country_rename_map.has(country)) {
country = p_server.country_rename_map[country];
}
// Remove unsupported script codes.
if (!p_server.script_map.has(script)) {
script = "";
}
// Add script code base on language and country codes for some ambiguous cases.
if (p_add_defaults) {
if (script.is_empty()) {
for (int i = 0; i < p_server.locale_script_info.size(); i++) {
const LocaleScriptInfo &info = p_server.locale_script_info[i];
if (info.name == language) {
if (country.is_empty() || info.supported_countries.has(country)) {
script = info.script;
break;
}
}
}
}
if (!script.is_empty() && country.is_empty()) {
// Add conntry code based on script for some ambiguous cases.
for (int i = 0; i < p_server.locale_script_info.size(); i++) {
const LocaleScriptInfo &info = p_server.locale_script_info[i];
if (info.name == language && info.script == script) {
country = info.default_country;
break;
}
}
}
}
}
String TranslationServer::standardize_locale(const String &p_locale, bool p_add_defaults) const {
return Locale(*this, p_locale, p_add_defaults).operator String();
}
int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
if (p_locale_a == p_locale_b) {
// Exact match.
return 10;
}
const String cache_key = p_locale_a + "|" + p_locale_b;
const int *cached_result = locale_compare_cache.getptr(cache_key);
if (cached_result) {
return *cached_result;
}
Locale locale_a = Locale(*this, p_locale_a, true);
Locale locale_b = Locale(*this, p_locale_b, true);
if (locale_a == locale_b) {
// Exact match.
locale_compare_cache.insert(cache_key, 10);
return 10;
}
if (locale_a.language != locale_b.language) {
// No match.
locale_compare_cache.insert(cache_key, 0);
return 0;
}
// Matching language, both locales have extra parts. Compare the
// remaining elements. If both elements are non-empty, check the
// match to increase or decrease the score. If either element or
// both are empty, leave the score as is.
int score = 5;
if (!locale_a.script.is_empty() && !locale_b.script.is_empty()) {
if (locale_a.script == locale_b.script) {
score++;
} else {
score--;
}
}
if (!locale_a.country.is_empty() && !locale_b.country.is_empty()) {
if (locale_a.country == locale_b.country) {
score++;
} else {
score--;
}
}
if (!locale_a.variant.is_empty() && !locale_b.variant.is_empty()) {
if (locale_a.variant == locale_b.variant) {
score++;
} else {
score--;
}
}
locale_compare_cache.insert(cache_key, score);
return score;
}
String TranslationServer::get_locale_name(const String &p_locale) const {
String lang_name, script_name, country_name;
Vector<String> locale_elements = standardize_locale(p_locale).split("_");
lang_name = locale_elements[0];
if (locale_elements.size() >= 2) {
if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
script_name = locale_elements[1];
}
if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
country_name = locale_elements[1];
}
}
if (locale_elements.size() >= 3) {
if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
country_name = locale_elements[2];
}
}
String name = get_language_name(lang_name);
if (!script_name.is_empty()) {
name = name + " (" + get_script_name(script_name) + ")";
}
if (!country_name.is_empty()) {
name = name + ", " + get_country_name(country_name);
}
return name;
}
Vector<String> TranslationServer::get_all_languages() const {
Vector<String> languages;
for (const KeyValue<String, String> &E : language_map) {
languages.push_back(E.key);
}
return languages;
}
String TranslationServer::get_language_name(const String &p_language) const {
if (language_map.has(p_language)) {
return language_map[p_language];
} else {
return p_language;
}
}
Vector<String> TranslationServer::get_all_scripts() const {
Vector<String> scripts;
for (const KeyValue<String, String> &E : script_map) {
scripts.push_back(E.key);
}
return scripts;
}
String TranslationServer::get_script_name(const String &p_script) const {
if (script_map.has(p_script)) {
return script_map[p_script];
} else {
return p_script;
}
}
Vector<String> TranslationServer::get_all_countries() const {
Vector<String> countries;
for (const KeyValue<String, String> &E : country_name_map) {
countries.push_back(E.key);
}
return countries;
}
String TranslationServer::get_country_name(const String &p_country) const {
if (country_name_map.has(p_country)) {
return country_name_map[p_country];
} else {
return p_country;
}
}
void TranslationServer::set_locale(const String &p_locale) {
String new_locale = standardize_locale(p_locale);
if (locale == new_locale) {
return;
}
locale = new_locale;
ResourceLoader::reload_translation_remaps();
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
}
}
String TranslationServer::get_locale() const {
return locale;
}
String TranslationServer::get_fallback_locale() const {
return fallback;
}
PackedStringArray TranslationServer::get_loaded_locales() const {
return main_domain->get_loaded_locales();
}
void TranslationServer::add_translation(const Ref<Translation> &p_translation) {
main_domain->add_translation(p_translation);
}
void TranslationServer::remove_translation(const Ref<Translation> &p_translation) {
main_domain->remove_translation(p_translation);
}
Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
return main_domain->get_translation_object(p_locale);
}
void TranslationServer::clear() {
main_domain->clear();
}
StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const {
if (!enabled) {
return p_message;
}
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 {
if (!enabled) {
if (p_n == 1) {
return p_message;
}
return p_message_plural;
}
return main_domain->translate_plural(p_message, p_message_plural, p_n, p_context);
}
bool TranslationServer::_load_translations(const String &p_from) {
if (ProjectSettings::get_singleton()->has_setting(p_from)) {
const Vector<String> &translation_names = GLOBAL_GET(p_from);
int tcount = translation_names.size();
if (tcount) {
const String *r = translation_names.ptr();
for (int i = 0; i < tcount; i++) {
Ref<Translation> tr = ResourceLoader::load(r[i]);
if (tr.is_valid()) {
add_translation(tr);
}
}
}
return true;
}
return false;
}
bool TranslationServer::has_domain(const StringName &p_domain) const {
if (p_domain == StringName()) {
return true;
}
return custom_domains.has(p_domain);
}
Ref<TranslationDomain> TranslationServer::get_or_add_domain(const StringName &p_domain) {
if (p_domain == StringName()) {
return main_domain;
}
const Ref<TranslationDomain> *domain = custom_domains.getptr(p_domain);
if (domain) {
if (domain->is_valid()) {
return *domain;
}
ERR_PRINT("Bug (please report): Found invalid translation domain.");
}
Ref<TranslationDomain> new_domain = memnew(TranslationDomain);
custom_domains[p_domain] = new_domain;
return new_domain;
}
void TranslationServer::remove_domain(const StringName &p_domain) {
ERR_FAIL_COND_MSG(p_domain == StringName(), "Cannot remove main translation domain.");
custom_domains.erase(p_domain);
}
void TranslationServer::setup() {
String test = GLOBAL_DEF("internationalization/locale/test", "");
test = test.strip_edges();
if (!test.is_empty()) {
set_locale(test);
} else {
set_locale(OS::get_singleton()->get_locale());
}
fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
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/test", PROPERTY_HINT_LOCALE_ID, ""));
ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
#endif
}
String TranslationServer::get_tool_locale() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
const PackedStringArray &locales = editor_domain->get_loaded_locales();
if (locales.is_empty()) {
return "en";
}
return locales[0];
} else {
#else
{
#endif
// Look for best matching loaded translation.
Ref<Translation> t = main_domain->get_translation_object(locale);
if (t.is_null()) {
return fallback;
}
return t->get_locale();
}
}
StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
return editor_domain->translate(p_message, p_context);
}
StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
return editor_domain->translate_plural(p_message, p_message_plural, p_n, p_context);
}
StringName TranslationServer::property_translate(const StringName &p_message, const StringName &p_context) const {
return property_domain->translate(p_message, p_context);
}
StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const {
return doc_domain->translate(p_message, p_context);
}
StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
return doc_domain->translate_plural(p_message, p_message_plural, p_n, p_context);
}
bool TranslationServer::is_pseudolocalization_enabled() const {
return main_domain->is_pseudolocalization_enabled();
}
void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
main_domain->set_pseudolocalization_enabled(p_enabled);
ResourceLoader::reload_translation_remaps();
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
}
}
void TranslationServer::reload_pseudolocalization() {
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();
if (OS::get_singleton()->get_main_loop()) {
OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
}
}
StringName TranslationServer::pseudolocalize(const StringName &p_message) const {
return main_domain->pseudolocalize(p_message);
}
#ifdef TOOLS_ENABLED
void TranslationServer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
const String pf = p_function;
if (p_idx == 0) {
HashMap<String, String> *target_hash_map = nullptr;
if (pf == "get_language_name") {
target_hash_map = &language_map;
} else if (pf == "get_script_name") {
target_hash_map = &script_map;
} else if (pf == "get_country_name") {
target_hash_map = &country_name_map;
}
if (target_hash_map) {
for (const KeyValue<String, String> &E : *target_hash_map) {
r_options->push_back(E.key.quote());
}
}
}
Object::get_argument_options(p_function, p_idx, r_options);
}
#endif // TOOLS_ENABLED
void TranslationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale);
ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales);
ClassDB::bind_method(D_METHOD("standardize_locale", "locale", "add_defaults"), &TranslationServer::standardize_locale, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages);
ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name);
ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts);
ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name);
ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries);
ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name);
ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
ClassDB::bind_method(D_METHOD("has_domain", "domain"), &TranslationServer::has_domain);
ClassDB::bind_method(D_METHOD("get_or_add_domain", "domain"), &TranslationServer::get_or_add_domain);
ClassDB::bind_method(D_METHOD("remove_domain", "domain"), &TranslationServer::remove_domain);
ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
}
void TranslationServer::load_translations() {
_load_translations("internationalization/locale/translations"); //all
_load_translations("internationalization/locale/translations_" + locale.substr(0, 2));
if (locale.substr(0, 2) != locale) {
_load_translations("internationalization/locale/translations_" + locale);
}
}
TranslationServer::TranslationServer() {
singleton = this;
main_domain.instantiate();
editor_domain = get_or_add_domain("godot.editor");
property_domain = get_or_add_domain("godot.properties");
doc_domain = get_or_add_domain("godot.documentation");
init_locale_info();
}

View file

@ -0,0 +1,164 @@
/**************************************************************************/
/* translation_server.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef TRANSLATION_SERVER_H
#define TRANSLATION_SERVER_H
#include "core/string/translation.h"
#include "core/string/translation_domain.h"
class TranslationServer : public Object {
GDCLASS(TranslationServer, Object);
String locale = "en";
String fallback;
Ref<TranslationDomain> main_domain;
Ref<TranslationDomain> editor_domain;
Ref<TranslationDomain> property_domain;
Ref<TranslationDomain> doc_domain;
HashMap<StringName, Ref<TranslationDomain>> custom_domains;
mutable HashMap<String, int> locale_compare_cache;
bool enabled = true;
static inline TranslationServer *singleton = nullptr;
bool _load_translations(const String &p_from);
static void _bind_methods();
#ifndef DISABLE_DEPRECATED
String _standardize_locale_bind_compat_98972(const String &p_locale) const;
static void _bind_compatibility_methods();
#endif
struct LocaleScriptInfo {
String name;
String script;
String default_country;
HashSet<String> supported_countries;
};
static Vector<LocaleScriptInfo> locale_script_info;
struct Locale {
String language;
String script;
String country;
String variant;
bool operator==(const Locale &p_locale) const {
return (p_locale.language == language) &&
(p_locale.script == script) &&
(p_locale.country == country) &&
(p_locale.variant == variant);
}
operator String() const;
Locale(const TranslationServer &p_server, const String &p_locale, bool p_add_defaults);
};
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();
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; }
void set_locale(const String &p_locale);
String get_locale() const;
String get_fallback_locale() const;
Ref<Translation> get_translation_object(const String &p_locale);
Vector<String> get_all_languages() const;
String get_language_name(const String &p_language) const;
Vector<String> get_all_scripts() const;
String get_script_name(const String &p_script) const;
Vector<String> get_all_countries() const;
String get_country_name(const String &p_country) const;
String get_locale_name(const String &p_locale) const;
PackedStringArray get_loaded_locales() const;
void add_translation(const Ref<Translation> &p_translation);
void remove_translation(const Ref<Translation> &p_translation);
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;
StringName pseudolocalize(const StringName &p_message) const;
bool is_pseudolocalization_enabled() const;
void set_pseudolocalization_enabled(bool p_enabled);
void reload_pseudolocalization();
String standardize_locale(const String &p_locale, bool p_add_defaults = false) const;
int compare_locales(const String &p_locale_a, const String &p_locale_b) const;
String get_tool_locale();
StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const;
StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
StringName property_translate(const StringName &p_message, const StringName &p_context = "") const;
StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const;
StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
bool has_domain(const StringName &p_domain) const;
Ref<TranslationDomain> get_or_add_domain(const StringName &p_domain);
void remove_domain(const StringName &p_domain);
void setup();
void clear();
void load_translations();
#ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
#endif // TOOLS_ENABLED
TranslationServer();
};
#endif // TRANSLATION_SERVER_H

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -39,6 +39,89 @@
#include "core/typedefs.h"
#include "core/variant/array.h"
/*************************************************************************/
/* Utility Functions */
/*************************************************************************/
// Not defined by std.
// strlen equivalent function for char16_t * arguments.
constexpr size_t strlen(const char16_t *p_str) {
const char16_t *ptr = p_str;
while (*ptr != 0) {
++ptr;
}
return ptr - p_str;
}
// strlen equivalent function for char32_t * arguments.
constexpr size_t strlen(const char32_t *p_str) {
const char32_t *ptr = p_str;
while (*ptr != 0) {
++ptr;
}
return ptr - p_str;
}
// strlen equivalent function for wchar_t * arguments; depends on the platform.
constexpr size_t strlen(const wchar_t *str) {
// Use static_cast twice because reinterpret_cast is not allowed in constexpr
#ifdef WINDOWS_ENABLED
// wchar_t is 16-bit
return strlen(static_cast<const char16_t *>(static_cast<const void *>(str)));
#else
// wchar_t is 32-bit
return strlen(static_cast<const char32_t *>(static_cast<const void *>(str)));
#endif
}
constexpr size_t _strlen_clipped(const char *p_str, int p_clip_to_len) {
if (p_clip_to_len < 0) {
return strlen(p_str);
}
int len = 0;
while (len < p_clip_to_len && *(p_str++) != 0) {
len++;
}
return len;
}
constexpr size_t _strlen_clipped(const char32_t *p_str, int p_clip_to_len) {
if (p_clip_to_len < 0) {
return strlen(p_str);
}
int len = 0;
while (len < p_clip_to_len && *(p_str++) != 0) {
len++;
}
return len;
}
/*************************************************************************/
/* StrRange */
/*************************************************************************/
template <typename Element>
struct StrRange {
const Element *c_str;
size_t len;
explicit StrRange(const std::nullptr_t p_cstring) :
c_str(nullptr), len(0) {}
explicit StrRange(const Element *p_cstring, const size_t p_len) :
c_str(p_cstring), len(p_len) {}
template <size_t len>
explicit StrRange(const Element (&p_cstring)[len]) :
c_str(p_cstring), len(strlen(p_cstring)) {}
static StrRange from_c_str(const Element *p_cstring) {
return StrRange(p_cstring, p_cstring ? strlen(p_cstring) : 0);
}
};
/*************************************************************************/
/* CharProxy */
/*************************************************************************/
@ -110,7 +193,10 @@ public:
_FORCE_INLINE_ Char16String() {}
_FORCE_INLINE_ Char16String(const Char16String &p_str) { _cowdata._ref(p_str._cowdata); }
_FORCE_INLINE_ Char16String(Char16String &&p_str) :
_cowdata(std::move(p_str._cowdata)) {}
_FORCE_INLINE_ void operator=(const Char16String &p_str) { _cowdata._ref(p_str._cowdata); }
_FORCE_INLINE_ void operator=(Char16String &&p_str) { _cowdata = std::move(p_str._cowdata); }
_FORCE_INLINE_ Char16String(const char16_t *p_cstr) { copy_from(p_cstr); }
void operator=(const char16_t *p_cstr);
@ -118,7 +204,8 @@ public:
Char16String &operator+=(char16_t p_char);
int length() const { return size() ? size() - 1 : 0; }
const char16_t *get_data() const;
operator const char16_t *() const { return get_data(); };
operator const char16_t *() const { return get_data(); }
explicit operator StrRange<char16_t>() const { return StrRange(get_data(), length()); }
protected:
void copy_from(const char16_t *p_cstr);
@ -151,7 +238,10 @@ public:
_FORCE_INLINE_ CharString() {}
_FORCE_INLINE_ CharString(const CharString &p_str) { _cowdata._ref(p_str._cowdata); }
_FORCE_INLINE_ CharString(CharString &&p_str) :
_cowdata(std::move(p_str._cowdata)) {}
_FORCE_INLINE_ void operator=(const CharString &p_str) { _cowdata._ref(p_str._cowdata); }
_FORCE_INLINE_ void operator=(CharString &&p_str) { _cowdata = std::move(p_str._cowdata); }
_FORCE_INLINE_ CharString(const char *p_cstr) { copy_from(p_cstr); }
void operator=(const char *p_cstr);
@ -160,7 +250,8 @@ public:
CharString &operator+=(char p_char);
int length() const { return size() ? size() - 1 : 0; }
const char *get_data() const;
operator const char *() const { return get_data(); };
operator const char *() const { return get_data(); }
explicit operator StrRange<char>() const { return StrRange(get_data(), length()); }
protected:
void copy_from(const char *p_cstr);
@ -170,31 +261,59 @@ protected:
/* String */
/*************************************************************************/
struct StrRange {
const char32_t *c_str;
int len;
StrRange(const char32_t *p_c_str = nullptr, int p_len = 0) {
c_str = p_c_str;
len = p_len;
}
};
class String {
CowData<char32_t> _cowdata;
static const char32_t _null;
static const char32_t _replacement_char;
void copy_from(const char *p_cstr);
void copy_from(const char *p_cstr, const int p_clip_to);
void copy_from(const wchar_t *p_cstr);
void copy_from(const wchar_t *p_cstr, const int p_clip_to);
void copy_from(const char32_t *p_cstr);
void copy_from(const char32_t *p_cstr, const int p_clip_to);
// Known-length copy.
void parse_latin1(const StrRange<char> &p_cstr);
void parse_utf32(const StrRange<char32_t> &p_cstr);
void parse_utf32(const char32_t &p_char);
void copy_from_unchecked(const char32_t *p_char, int p_length);
void copy_from(const char32_t &p_char);
// NULL-terminated c string copy - automatically parse the string to find the length.
void parse_latin1(const char *p_cstr) {
parse_latin1(StrRange<char>::from_c_str(p_cstr));
}
void parse_latin1(const char *p_cstr, int p_clip_to) {
parse_latin1(StrRange(p_cstr, p_cstr ? _strlen_clipped(p_cstr, p_clip_to) : 0));
}
void parse_utf32(const char32_t *p_cstr) {
parse_utf32(StrRange<char32_t>::from_c_str(p_cstr));
}
void parse_utf32(const char32_t *p_cstr, int p_clip_to) {
parse_utf32(StrRange(p_cstr, p_cstr ? _strlen_clipped(p_cstr, p_clip_to) : 0));
}
void copy_from_unchecked(const char32_t *p_char, const int p_length);
// wchar_t copy_from depends on the platform.
void parse_wstring(const StrRange<wchar_t> &p_cstr) {
#ifdef WINDOWS_ENABLED
// wchar_t is 16-bit, parse as UTF-16
parse_utf16((const char16_t *)p_cstr.c_str, p_cstr.len);
#else
// wchar_t is 32-bit, copy directly
parse_utf32((StrRange<char32_t> &)p_cstr);
#endif
}
void parse_wstring(const wchar_t *p_cstr) {
#ifdef WINDOWS_ENABLED
// wchar_t is 16-bit, parse as UTF-16
parse_utf16((const char16_t *)p_cstr);
#else
// wchar_t is 32-bit, copy directly
parse_utf32((const char32_t *)p_cstr);
#endif
}
void parse_wstring(const wchar_t *p_cstr, int p_clip_to) {
#ifdef WINDOWS_ENABLED
// wchar_t is 16-bit, parse as UTF-16
parse_utf16((const char16_t *)p_cstr, p_clip_to);
#else
// wchar_t is 32-bit, copy directly
parse_utf32((const char32_t *)p_cstr, p_clip_to);
#endif
}
bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const;
int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const;
@ -227,6 +346,8 @@ public:
}
_FORCE_INLINE_ CharProxy<char32_t> operator[](int p_index) { return CharProxy<char32_t>(p_index, _cowdata); }
/* Compatibility Operators */
bool operator==(const String &p_str) const;
bool operator!=(const String &p_str) const;
String operator+(const String &p_str) const;
@ -238,16 +359,10 @@ public:
String &operator+=(const wchar_t *p_str);
String &operator+=(const char32_t *p_str);
/* Compatibility Operators */
void operator=(const char *p_str);
void operator=(const wchar_t *p_str);
void operator=(const char32_t *p_str);
bool operator==(const char *p_str) const;
bool operator==(const wchar_t *p_str) const;
bool operator==(const char32_t *p_str) const;
bool operator==(const StrRange &p_str_range) const;
bool operator==(const StrRange<char32_t> &p_str_range) const;
bool operator!=(const char *p_str) const;
bool operator!=(const wchar_t *p_str) const;
@ -287,11 +402,12 @@ public:
String substr(int p_from, int p_chars = -1) const;
int find(const String &p_str, int p_from = 0) const; ///< return <0 if failed
int find(const char *p_str, int p_from = 0) const; ///< return <0 if failed
int find_char(const char32_t &p_char, int p_from = 0) const; ///< return <0 if failed
int find_char(char32_t p_char, int p_from = 0) const; ///< return <0 if failed
int findn(const String &p_str, int p_from = 0) const; ///< return <0 if failed, case insensitive
int findn(const char *p_str, int p_from = 0) const; ///< return <0 if failed
int rfind(const String &p_str, int p_from = -1) const; ///< return <0 if failed
int rfind(const char *p_str, int p_from = -1) const; ///< return <0 if failed
int rfind_char(char32_t p_char, int p_from = -1) const; ///< return <0 if failed
int rfindn(const String &p_str, int p_from = -1) const; ///< return <0 if failed, case insensitive
int rfindn(const char *p_str, int p_from = -1) const; ///< return <0 if failed
int findmk(const Vector<String> &p_keys, int p_from = 0, int *r_key = nullptr) const; ///< return <0 if failed
@ -305,6 +421,7 @@ public:
bool is_subsequence_of(const String &p_string) const;
bool is_subsequence_ofn(const String &p_string) const;
bool is_quoted() const;
bool is_lowercase() const;
Vector<String> bigrams() const;
float similarity(const String &p_string) const;
String format(const Variant &values, const String &placeholder = "{_}") const;
@ -332,6 +449,7 @@ public:
static String num(double p_num, int p_decimals = -1);
static String num_scientific(double p_num);
static String num_real(double p_num, bool p_trailing = true);
static String num_real(float p_num, bool p_trailing = true);
static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false);
static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false);
static String chr(char32_t p_char);
@ -429,6 +547,7 @@ public:
_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
_FORCE_INLINE_ bool contains(const char *p_str) const { return find(p_str) != -1; }
_FORCE_INLINE_ bool contains(const String &p_str) const { return find(p_str) != -1; }
_FORCE_INLINE_ bool contains_char(char32_t p_chr) const { return find_char(p_chr) != -1; }
_FORCE_INLINE_ bool containsn(const char *p_str) const { return findn(p_str) != -1; }
_FORCE_INLINE_ bool containsn(const String &p_str) const { return findn(p_str) != -1; }
@ -452,17 +571,19 @@ public:
String c_escape_multiline() const;
String c_unescape() const;
String json_escape() const;
Error parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const;
Error parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path, String &r_fragment) const;
String property_name_encode() const;
// node functions
static String get_invalid_node_name_characters(bool p_allow_internal = false);
String validate_node_name() const;
String validate_identifier() const;
String validate_ascii_identifier() const;
String validate_unicode_identifier() const;
String validate_filename() const;
bool is_valid_identifier() const;
bool is_valid_ascii_identifier() const;
bool is_valid_unicode_identifier() const;
bool is_valid_int() const;
bool is_valid_float() const;
bool is_valid_hex_number(bool p_with_prefix) const;
@ -470,13 +591,19 @@ public:
bool is_valid_ip_address() const;
bool is_valid_filename() const;
// Use `is_valid_ascii_identifier()` instead. Kept for compatibility.
bool is_valid_identifier() const { return is_valid_ascii_identifier(); }
/**
* The constructors must not depend on other overloads
*/
_FORCE_INLINE_ String() {}
_FORCE_INLINE_ String(const String &p_str) { _cowdata._ref(p_str._cowdata); }
_FORCE_INLINE_ String(String &&p_str) :
_cowdata(std::move(p_str._cowdata)) {}
_FORCE_INLINE_ void operator=(const String &p_str) { _cowdata._ref(p_str._cowdata); }
_FORCE_INLINE_ void operator=(String &&p_str) { _cowdata = std::move(p_str._cowdata); }
Vector<uint8_t> to_ascii_buffer() const;
Vector<uint8_t> to_utf8_buffer() const;
@ -484,13 +611,38 @@ public:
Vector<uint8_t> to_utf32_buffer() const;
Vector<uint8_t> to_wchar_buffer() const;
String(const char *p_str);
String(const wchar_t *p_str);
String(const char32_t *p_str);
String(const char *p_str, int p_clip_to_len);
String(const wchar_t *p_str, int p_clip_to_len);
String(const char32_t *p_str, int p_clip_to_len);
String(const StrRange &p_range);
// Constructors for NULL terminated C strings.
String(const char *p_cstr) {
parse_latin1(p_cstr);
}
String(const wchar_t *p_cstr) {
parse_wstring(p_cstr);
}
String(const char32_t *p_cstr) {
parse_utf32(p_cstr);
}
String(const char *p_cstr, int p_clip_to_len) {
parse_latin1(p_cstr, p_clip_to_len);
}
String(const wchar_t *p_cstr, int p_clip_to_len) {
parse_wstring(p_cstr, p_clip_to_len);
}
String(const char32_t *p_cstr, int p_clip_to_len) {
parse_utf32(p_cstr, p_clip_to_len);
}
// Copy assignment for NULL terminated C strings.
void operator=(const char *p_cstr) {
parse_latin1(p_cstr);
}
void operator=(const wchar_t *p_cstr) {
parse_wstring(p_cstr);
}
void operator=(const char32_t *p_cstr) {
parse_utf32(p_cstr);
}
explicit operator StrRange<char32_t>() const { return StrRange(get_data(), length()); }
};
bool operator==(const char *p_chr, const String &p_str);