feat: updated engine version to 4.4-rc1
This commit is contained in:
parent
ee00efde1f
commit
21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions
|
|
@ -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
|
|
@ -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 == '_');
|
||||
}
|
||||
|
||||
|
|
|
|||
349
engine/core/string/fuzzy_search.cpp
Normal file
349
engine/core/string/fuzzy_search.cpp
Normal 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);
|
||||
}
|
||||
101
engine/core/string/fuzzy_search.h
Normal file
101
engine/core/string/fuzzy_search.h
Normal 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
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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"F́" },
|
||||
{ 'G', U"Ĝ" },
|
||||
{ 'H', U"Ĥ" },
|
||||
{ 'I', U"Ĩ" },
|
||||
{ 'J', U"Ĵ" },
|
||||
{ 'K', U"ĸ" },
|
||||
{ 'L', U"Ł" },
|
||||
{ 'M', U"Ḿ" },
|
||||
{ 'N', U"й" },
|
||||
{ 'O', U"Ö" },
|
||||
{ 'P', U"Ṕ" },
|
||||
{ 'Q', U"Q́" },
|
||||
{ 'R', U"Ř" },
|
||||
{ 'S', U"Ŝ" },
|
||||
{ 'T', U"Ŧ" },
|
||||
{ 'U', U"Ũ" },
|
||||
{ 'V', U"Ṽ" },
|
||||
{ 'W', U"Ŵ" },
|
||||
{ 'X', U"X́" },
|
||||
{ 'Y', U"Ÿ" },
|
||||
{ 'Z', U"Ž" },
|
||||
{ 'a', U"á" },
|
||||
{ 'b', U"ḅ" },
|
||||
{ 'c', U"ć" },
|
||||
{ 'd', U"d́" },
|
||||
{ 'e', U"é" },
|
||||
{ 'f', U"f́" },
|
||||
{ 'g', U"ǵ" },
|
||||
{ 'h', U"h̀" },
|
||||
{ 'i', U"í" },
|
||||
{ 'j', U"ǰ" },
|
||||
{ 'k', U"ḱ" },
|
||||
{ 'l', U"ł" },
|
||||
{ 'm', U"m̀" },
|
||||
{ 'n', U"ή" },
|
||||
{ 'o', U"ô" },
|
||||
{ 'p', U"ṕ" },
|
||||
{ 'q', U"q́" },
|
||||
{ 'r', U"ŕ" },
|
||||
{ 's', U"š" },
|
||||
{ 't', U"ŧ" },
|
||||
{ 'u', U"ü" },
|
||||
{ 'v', U"ṽ" },
|
||||
{ 'w', U"ŵ" },
|
||||
{ 'x', U"x́" },
|
||||
{ '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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
460
engine/core/string/translation_domain.cpp
Normal file
460
engine/core/string/translation_domain.cpp
Normal 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"F́" },
|
||||
{ 'G', U"Ĝ" },
|
||||
{ 'H', U"Ĥ" },
|
||||
{ 'I', U"Ĩ" },
|
||||
{ 'J', U"Ĵ" },
|
||||
{ 'K', U"ĸ" },
|
||||
{ 'L', U"Ł" },
|
||||
{ 'M', U"Ḿ" },
|
||||
{ 'N', U"й" },
|
||||
{ 'O', U"Ö" },
|
||||
{ 'P', U"Ṕ" },
|
||||
{ 'Q', U"Q́" },
|
||||
{ 'R', U"Ř" },
|
||||
{ 'S', U"Ŝ" },
|
||||
{ 'T', U"Ŧ" },
|
||||
{ 'U', U"Ũ" },
|
||||
{ 'V', U"Ṽ" },
|
||||
{ 'W', U"Ŵ" },
|
||||
{ 'X', U"X́" },
|
||||
{ 'Y', U"Ÿ" },
|
||||
{ 'Z', U"Ž" },
|
||||
{ 'a', U"á" },
|
||||
{ 'b', U"ḅ" },
|
||||
{ 'c', U"ć" },
|
||||
{ 'd', U"d́" },
|
||||
{ 'e', U"é" },
|
||||
{ 'f', U"f́" },
|
||||
{ 'g', U"ǵ" },
|
||||
{ 'h', U"h̀" },
|
||||
{ 'i', U"í" },
|
||||
{ 'j', U"ǰ" },
|
||||
{ 'k', U"ḱ" },
|
||||
{ 'l', U"ł" },
|
||||
{ 'm', U"m̀" },
|
||||
{ 'n', U"ή" },
|
||||
{ 'o', U"ô" },
|
||||
{ 'p', U"ṕ" },
|
||||
{ 'q', U"q́" },
|
||||
{ 'r', U"ŕ" },
|
||||
{ 's', U"š" },
|
||||
{ 't', U"ŧ" },
|
||||
{ 'u', U"ü" },
|
||||
{ 'v', U"ṽ" },
|
||||
{ 'w', U"ŵ" },
|
||||
{ 'x', U"x́" },
|
||||
{ '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() {
|
||||
}
|
||||
107
engine/core/string/translation_domain.h
Normal file
107
engine/core/string/translation_domain.h
Normal 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
|
||||
|
|
@ -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.");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
659
engine/core/string/translation_server.cpp
Normal file
659
engine/core/string/translation_server.cpp
Normal 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();
|
||||
}
|
||||
164
engine/core/string/translation_server.h
Normal file
164
engine/core/string/translation_server.h
Normal 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
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue