initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
8
core/string/SCsub
Normal file
8
core/string/SCsub
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env_string = env.Clone()
|
||||
|
||||
env_string.add_source_files(env.core_sources, "*.cpp")
|
71
core/string/alt_codes.h
Normal file
71
core/string/alt_codes.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/**************************************************************************/
|
||||
/* alt_codes.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
// OEM encodings for Alt codes input.
|
||||
|
||||
inline constexpr char32_t alt_code_oem437[256] = {
|
||||
0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022, 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
|
||||
0x25ba, 0x25c4, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8, 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
|
||||
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
|
||||
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
|
||||
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
|
||||
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
|
||||
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
|
||||
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
|
||||
0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7, 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
|
||||
0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9, 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
|
||||
0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba, 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
|
||||
0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556, 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
|
||||
0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f, 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
|
||||
0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b, 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
|
||||
0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03a4, 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
|
||||
0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248, 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
|
||||
};
|
||||
|
||||
inline constexpr char32_t alt_code_cp1252[256] = {
|
||||
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
|
||||
0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
|
||||
0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
|
||||
0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
|
||||
0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
|
||||
0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
|
||||
0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
|
||||
0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
|
||||
0x20ac, 0x0081, 0x201a, 0x0192, 0x201e, 0x2026, 0x2020, 0x2021, 0x02c6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008d, 0x017d, 0x008f,
|
||||
0x0090, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0x02dc, 0x2122, 0x0161, 0x203a, 0x0153, 0x009d, 0x017e, 0x0178,
|
||||
0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
|
||||
0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
|
||||
0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
|
||||
0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
|
||||
0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
|
||||
0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
|
||||
};
|
3631
core/string/char_range.inc
Normal file
3631
core/string/char_range.inc
Normal file
File diff suppressed because it is too large
Load Diff
141
core/string/char_utils.h
Normal file
141
core/string/char_utils.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/**************************************************************************/
|
||||
/* char_utils.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/typedefs.h"
|
||||
|
||||
#include "char_range.inc"
|
||||
|
||||
#include <iterator>
|
||||
|
||||
static constexpr char hex_char_table_upper[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
static constexpr char hex_char_table_lower[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
#define BSEARCH_CHAR_RANGE(m_array) \
|
||||
int low = 0; \
|
||||
int high = std::size(m_array) - 1; \
|
||||
int middle = (low + high) / 2; \
|
||||
\
|
||||
while (low <= high) { \
|
||||
if (p_char < m_array[middle].start) { \
|
||||
high = middle - 1; \
|
||||
} else if (p_char > m_array[middle].end) { \
|
||||
low = middle + 1; \
|
||||
} else { \
|
||||
return true; \
|
||||
} \
|
||||
\
|
||||
middle = (low + high) / 2; \
|
||||
} \
|
||||
\
|
||||
return false
|
||||
|
||||
constexpr bool is_unicode_identifier_start(char32_t p_char) {
|
||||
BSEARCH_CHAR_RANGE(xid_start);
|
||||
}
|
||||
|
||||
constexpr bool is_unicode_identifier_continue(char32_t p_char) {
|
||||
BSEARCH_CHAR_RANGE(xid_continue);
|
||||
}
|
||||
|
||||
constexpr bool is_unicode_upper_case(char32_t p_char) {
|
||||
BSEARCH_CHAR_RANGE(uppercase_letter);
|
||||
}
|
||||
|
||||
constexpr bool is_unicode_lower_case(char32_t p_char) {
|
||||
BSEARCH_CHAR_RANGE(lowercase_letter);
|
||||
}
|
||||
|
||||
constexpr bool is_unicode_letter(char32_t p_char) {
|
||||
BSEARCH_CHAR_RANGE(unicode_letter);
|
||||
}
|
||||
|
||||
#undef BSEARCH_CHAR_RANGE
|
||||
|
||||
constexpr bool is_ascii_upper_case(char32_t p_char) {
|
||||
return (p_char >= 'A' && p_char <= 'Z');
|
||||
}
|
||||
|
||||
constexpr bool is_ascii_lower_case(char32_t p_char) {
|
||||
return (p_char >= 'a' && p_char <= 'z');
|
||||
}
|
||||
|
||||
constexpr bool is_digit(char32_t p_char) {
|
||||
return (p_char >= '0' && p_char <= '9');
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
constexpr bool is_binary_digit(char32_t p_char) {
|
||||
return (p_char == '0' || p_char == '1');
|
||||
}
|
||||
|
||||
constexpr bool is_ascii_alphabet_char(char32_t p_char) {
|
||||
return (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z');
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
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 == '_';
|
||||
}
|
||||
|
||||
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 == ' ');
|
||||
}
|
||||
|
||||
constexpr bool is_control(char32_t p_char) {
|
||||
return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009f);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
constexpr bool is_linebreak(char32_t p_char) {
|
||||
return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
constexpr bool is_underscore(char32_t p_char) {
|
||||
return (p_char == '_');
|
||||
}
|
||||
|
||||
constexpr bool is_hyphen(char32_t p_char) {
|
||||
return (p_char == '-') || (p_char == 0x2010) || (p_char == 0x2011);
|
||||
}
|
357
core/string/fuzzy_search.cpp
Normal file
357
core/string/fuzzy_search.cpp
Normal file
@@ -0,0 +1,357 @@
|
||||
/**************************************************************************/
|
||||
/* 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) {
|
||||
set_query(p_query, !p_query.is_lowercase());
|
||||
}
|
||||
|
||||
void FuzzySearch::set_query(const String &p_query, bool p_case_sensitive) {
|
||||
tokens.clear();
|
||||
case_sensitive = p_case_sensitive;
|
||||
|
||||
for (const String &string : p_query.split(" ", false)) {
|
||||
tokens.append({
|
||||
static_cast<int>(tokens.size()),
|
||||
p_case_sensitive ? string : string.to_lower(),
|
||||
});
|
||||
}
|
||||
|
||||
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 (int i = 0; i < p_targets.size(); i++) {
|
||||
FuzzySearchResult result;
|
||||
result.original_index = i;
|
||||
if (search(p_targets[i], result)) {
|
||||
p_results.append(result);
|
||||
}
|
||||
}
|
||||
|
||||
sort_and_filter(p_results);
|
||||
}
|
100
core/string/fuzzy_search.h
Normal file
100
core/string/fuzzy_search.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#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 original_index = -1;
|
||||
int dir_index = -1;
|
||||
Vector<FuzzyTokenMatch> token_matches;
|
||||
};
|
||||
|
||||
class FuzzySearch {
|
||||
Vector<FuzzySearchToken> tokens;
|
||||
|
||||
bool case_sensitive = false;
|
||||
void sort_and_filter(Vector<FuzzySearchResult> &p_results) const;
|
||||
|
||||
public:
|
||||
int start_offset = 0;
|
||||
int max_results = 100;
|
||||
int max_misses = 2;
|
||||
bool allow_subsequences = true;
|
||||
|
||||
void set_query(const String &p_query);
|
||||
void set_query(const String &p_query, bool p_case_sensitive);
|
||||
bool search(const String &p_target, FuzzySearchResult &p_result) const;
|
||||
void search_all(const PackedStringArray &p_targets, Vector<FuzzySearchResult> &p_results) const;
|
||||
};
|
1196
core/string/locales.h
Normal file
1196
core/string/locales.h
Normal file
File diff suppressed because it is too large
Load Diff
481
core/string/node_path.cpp
Normal file
481
core/string/node_path.cpp
Normal file
@@ -0,0 +1,481 @@
|
||||
/**************************************************************************/
|
||||
/* node_path.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 "node_path.h"
|
||||
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
void NodePath::_update_hash_cache() const {
|
||||
uint32_t h = data->absolute ? 1 : 0;
|
||||
int pc = data->path.size();
|
||||
const StringName *sn = data->path.ptr();
|
||||
for (int i = 0; i < pc; i++) {
|
||||
h = h ^ sn[i].hash();
|
||||
}
|
||||
int spc = data->subpath.size();
|
||||
const StringName *ssn = data->subpath.ptr();
|
||||
for (int i = 0; i < spc; i++) {
|
||||
h = h ^ ssn[i].hash();
|
||||
}
|
||||
|
||||
data->hash_cache_valid = true;
|
||||
data->hash_cache = h;
|
||||
}
|
||||
|
||||
void NodePath::prepend_period() {
|
||||
if (data->path.size() && data->path[0].operator String() != ".") {
|
||||
data->path.insert(0, ".");
|
||||
data->hash_cache_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool NodePath::is_absolute() const {
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return data->absolute;
|
||||
}
|
||||
|
||||
int NodePath::get_name_count() const {
|
||||
if (!data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return data->path.size();
|
||||
}
|
||||
|
||||
StringName NodePath::get_name(int p_idx) const {
|
||||
ERR_FAIL_NULL_V(data, StringName());
|
||||
ERR_FAIL_INDEX_V(p_idx, data->path.size(), StringName());
|
||||
return data->path[p_idx];
|
||||
}
|
||||
|
||||
int NodePath::get_subname_count() const {
|
||||
if (!data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return data->subpath.size();
|
||||
}
|
||||
|
||||
StringName NodePath::get_subname(int p_idx) const {
|
||||
ERR_FAIL_NULL_V(data, StringName());
|
||||
ERR_FAIL_INDEX_V(p_idx, data->subpath.size(), StringName());
|
||||
return data->subpath[p_idx];
|
||||
}
|
||||
|
||||
int NodePath::get_total_name_count() const {
|
||||
if (!data) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return data->path.size() + data->subpath.size();
|
||||
}
|
||||
|
||||
void NodePath::unref() {
|
||||
if (data && data->refcount.unref()) {
|
||||
memdelete(data);
|
||||
}
|
||||
data = nullptr;
|
||||
}
|
||||
|
||||
bool NodePath::operator==(const NodePath &p_path) const {
|
||||
if (data == p_path.data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!data || !p_path.data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->absolute != p_path.data->absolute) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int path_size = data->path.size();
|
||||
|
||||
if (path_size != p_path.data->path.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int subpath_size = data->subpath.size();
|
||||
|
||||
if (subpath_size != p_path.data->subpath.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const StringName *l_path_ptr = data->path.ptr();
|
||||
const StringName *r_path_ptr = p_path.data->path.ptr();
|
||||
|
||||
for (int i = 0; i < path_size; i++) {
|
||||
if (l_path_ptr[i] != r_path_ptr[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const StringName *l_subpath_ptr = data->subpath.ptr();
|
||||
const StringName *r_subpath_ptr = p_path.data->subpath.ptr();
|
||||
|
||||
for (int i = 0; i < subpath_size; i++) {
|
||||
if (l_subpath_ptr[i] != r_subpath_ptr[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NodePath::operator!=(const NodePath &p_path) const {
|
||||
return (!(*this == p_path));
|
||||
}
|
||||
|
||||
void NodePath::operator=(const NodePath &p_path) {
|
||||
if (this == &p_path) {
|
||||
return;
|
||||
}
|
||||
|
||||
unref();
|
||||
|
||||
if (p_path.data && p_path.data->refcount.ref()) {
|
||||
data = p_path.data;
|
||||
}
|
||||
}
|
||||
|
||||
NodePath::operator String() const {
|
||||
if (!data) {
|
||||
return String();
|
||||
}
|
||||
|
||||
String ret;
|
||||
if (data->absolute) {
|
||||
ret = "/";
|
||||
}
|
||||
|
||||
for (int i = 0; i < data->path.size(); i++) {
|
||||
if (i > 0) {
|
||||
ret += "/";
|
||||
}
|
||||
ret += data->path[i].operator String();
|
||||
}
|
||||
|
||||
for (int i = 0; i < data->subpath.size(); i++) {
|
||||
ret += ":" + data->subpath[i].operator String();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Vector<StringName> NodePath::get_names() const {
|
||||
if (data) {
|
||||
return data->path;
|
||||
}
|
||||
return Vector<StringName>();
|
||||
}
|
||||
|
||||
Vector<StringName> NodePath::get_subnames() const {
|
||||
if (data) {
|
||||
return data->subpath;
|
||||
}
|
||||
return Vector<StringName>();
|
||||
}
|
||||
|
||||
StringName NodePath::get_concatenated_names() const {
|
||||
ERR_FAIL_NULL_V(data, StringName());
|
||||
|
||||
if (!data->concatenated_path) {
|
||||
int pc = data->path.size();
|
||||
String concatenated;
|
||||
const StringName *sn = data->path.ptr();
|
||||
for (int i = 0; i < pc; i++) {
|
||||
if (i > 0) {
|
||||
concatenated += "/";
|
||||
}
|
||||
concatenated += sn[i].operator String();
|
||||
}
|
||||
data->concatenated_path = concatenated;
|
||||
}
|
||||
return data->concatenated_path;
|
||||
}
|
||||
|
||||
StringName NodePath::get_concatenated_subnames() const {
|
||||
ERR_FAIL_NULL_V(data, StringName());
|
||||
|
||||
if (!data->concatenated_subpath) {
|
||||
int spc = data->subpath.size();
|
||||
String concatenated;
|
||||
const StringName *ssn = data->subpath.ptr();
|
||||
for (int i = 0; i < spc; i++) {
|
||||
if (i > 0) {
|
||||
concatenated += ":";
|
||||
}
|
||||
concatenated += ssn[i].operator String();
|
||||
}
|
||||
data->concatenated_subpath = concatenated;
|
||||
}
|
||||
return data->concatenated_subpath;
|
||||
}
|
||||
|
||||
NodePath NodePath::slice(int p_begin, int p_end) const {
|
||||
const int name_count = get_name_count();
|
||||
const int total_count = get_total_name_count();
|
||||
|
||||
int begin = CLAMP(p_begin, -total_count, total_count);
|
||||
if (begin < 0) {
|
||||
begin += total_count;
|
||||
}
|
||||
int end = CLAMP(p_end, -total_count, total_count);
|
||||
if (end < 0) {
|
||||
end += total_count;
|
||||
}
|
||||
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);
|
||||
const Vector<StringName> sub_names = get_subnames().slice(sub_begin, sub_end);
|
||||
const bool absolute = is_absolute() && (begin == 0);
|
||||
return NodePath(names, sub_names, absolute);
|
||||
}
|
||||
|
||||
NodePath NodePath::rel_path_to(const NodePath &p_np) const {
|
||||
ERR_FAIL_COND_V(!is_absolute(), NodePath());
|
||||
ERR_FAIL_COND_V(!p_np.is_absolute(), NodePath());
|
||||
|
||||
Vector<StringName> src_dirs = get_names();
|
||||
Vector<StringName> dst_dirs = p_np.get_names();
|
||||
|
||||
//find common parent
|
||||
int common_parent = 0;
|
||||
|
||||
while (true) {
|
||||
if (src_dirs.size() == common_parent) {
|
||||
break;
|
||||
}
|
||||
if (dst_dirs.size() == common_parent) {
|
||||
break;
|
||||
}
|
||||
if (src_dirs[common_parent] != dst_dirs[common_parent]) {
|
||||
break;
|
||||
}
|
||||
common_parent++;
|
||||
}
|
||||
|
||||
common_parent--;
|
||||
|
||||
Vector<StringName> relpath;
|
||||
relpath.resize(src_dirs.size() + dst_dirs.size() + 1);
|
||||
|
||||
StringName *relpath_ptr = relpath.ptrw();
|
||||
|
||||
int path_size = 0;
|
||||
StringName back_str("..");
|
||||
for (int i = common_parent + 1; i < src_dirs.size(); i++) {
|
||||
relpath_ptr[path_size++] = back_str;
|
||||
}
|
||||
|
||||
for (int i = common_parent + 1; i < dst_dirs.size(); i++) {
|
||||
relpath_ptr[path_size++] = dst_dirs[i];
|
||||
}
|
||||
|
||||
if (path_size == 0) {
|
||||
relpath_ptr[path_size++] = ".";
|
||||
}
|
||||
|
||||
relpath.resize(path_size);
|
||||
|
||||
return NodePath(relpath, p_np.get_subnames(), false);
|
||||
}
|
||||
|
||||
NodePath NodePath::get_as_property_path() const {
|
||||
if (!data || !data->path.size()) {
|
||||
return *this;
|
||||
} else {
|
||||
Vector<StringName> new_path = data->subpath;
|
||||
|
||||
String initial_subname = data->path[0];
|
||||
|
||||
for (int i = 1; i < data->path.size(); i++) {
|
||||
initial_subname += "/" + data->path[i];
|
||||
}
|
||||
new_path.insert(0, initial_subname);
|
||||
|
||||
return NodePath(Vector<StringName>(), new_path, false);
|
||||
}
|
||||
}
|
||||
|
||||
bool NodePath::is_empty() const {
|
||||
return !data;
|
||||
}
|
||||
|
||||
void NodePath::simplify() {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < data->path.size(); i++) {
|
||||
if (data->path.size() == 1) {
|
||||
break;
|
||||
}
|
||||
if (data->path[i].operator String() == ".") {
|
||||
data->path.remove_at(i);
|
||||
i--;
|
||||
} else if (i > 0 && data->path[i].operator String() == ".." && data->path[i - 1].operator String() != "." && data->path[i - 1].operator String() != "..") {
|
||||
//remove both
|
||||
data->path.remove_at(i - 1);
|
||||
data->path.remove_at(i - 1);
|
||||
i -= 2;
|
||||
if (data->path.is_empty()) {
|
||||
data->path.push_back(".");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
data->hash_cache_valid = false;
|
||||
}
|
||||
|
||||
NodePath NodePath::simplified() const {
|
||||
NodePath np = *this;
|
||||
np.simplify();
|
||||
return np;
|
||||
}
|
||||
|
||||
NodePath::NodePath(const Vector<StringName> &p_path, bool p_absolute) {
|
||||
if (p_path.is_empty() && !p_absolute) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = memnew(Data);
|
||||
data->refcount.init();
|
||||
data->absolute = p_absolute;
|
||||
data->path = p_path;
|
||||
data->hash_cache_valid = false;
|
||||
}
|
||||
|
||||
NodePath::NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute) {
|
||||
if (p_path.is_empty() && p_subpath.is_empty() && !p_absolute) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = memnew(Data);
|
||||
data->refcount.init();
|
||||
data->absolute = p_absolute;
|
||||
data->path = p_path;
|
||||
data->subpath = p_subpath;
|
||||
data->hash_cache_valid = false;
|
||||
}
|
||||
|
||||
NodePath::NodePath(const NodePath &p_path) {
|
||||
if (p_path.data && p_path.data->refcount.ref()) {
|
||||
data = p_path.data;
|
||||
}
|
||||
}
|
||||
|
||||
NodePath::NodePath(const String &p_path) {
|
||||
if (p_path.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
String path = p_path;
|
||||
Vector<StringName> subpath;
|
||||
|
||||
bool absolute = (path[0] == '/');
|
||||
bool last_is_slash = true;
|
||||
int slices = 0;
|
||||
int subpath_pos = path.find_char(':');
|
||||
|
||||
if (subpath_pos != -1) {
|
||||
int from = subpath_pos + 1;
|
||||
|
||||
for (int i = from; i <= path.length(); i++) {
|
||||
if (path[i] == ':' || path[i] == 0) {
|
||||
String str = path.substr(from, i - from);
|
||||
if (str.is_empty()) {
|
||||
if (path[i] == 0) {
|
||||
continue; // Allow end-of-path :
|
||||
}
|
||||
|
||||
ERR_FAIL_MSG(vformat("Invalid NodePath '%s'.", p_path));
|
||||
}
|
||||
subpath.push_back(str);
|
||||
|
||||
from = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
path = path.substr(0, subpath_pos);
|
||||
}
|
||||
|
||||
for (int i = (int)absolute; i < path.length(); i++) {
|
||||
if (path[i] == '/') {
|
||||
last_is_slash = true;
|
||||
} else {
|
||||
if (last_is_slash) {
|
||||
slices++;
|
||||
}
|
||||
|
||||
last_is_slash = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (slices == 0 && !absolute && !subpath.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
data = memnew(Data);
|
||||
data->refcount.init();
|
||||
data->absolute = absolute;
|
||||
data->subpath = subpath;
|
||||
data->hash_cache_valid = false;
|
||||
|
||||
if (slices == 0) {
|
||||
return;
|
||||
}
|
||||
data->path.resize(slices);
|
||||
last_is_slash = true;
|
||||
int from = (int)absolute;
|
||||
int slice = 0;
|
||||
|
||||
for (int i = (int)absolute; i < path.length() + 1; i++) {
|
||||
if (path[i] == '/' || path[i] == 0) {
|
||||
if (!last_is_slash) {
|
||||
String name = path.substr(from, i - from);
|
||||
ERR_FAIL_INDEX(slice, data->path.size());
|
||||
data->path.write[slice++] = name;
|
||||
}
|
||||
from = i + 1;
|
||||
last_is_slash = true;
|
||||
} else {
|
||||
last_is_slash = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodePath::~NodePath() {
|
||||
unref();
|
||||
}
|
101
core/string/node_path.h
Normal file
101
core/string/node_path.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/**************************************************************************/
|
||||
/* node_path.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
class [[nodiscard]] NodePath {
|
||||
struct Data {
|
||||
SafeRefCount refcount;
|
||||
Vector<StringName> path;
|
||||
Vector<StringName> subpath;
|
||||
StringName concatenated_path;
|
||||
StringName concatenated_subpath;
|
||||
bool absolute;
|
||||
mutable bool hash_cache_valid;
|
||||
mutable uint32_t hash_cache;
|
||||
};
|
||||
|
||||
mutable Data *data = nullptr;
|
||||
void unref();
|
||||
|
||||
void _update_hash_cache() const;
|
||||
|
||||
public:
|
||||
bool is_absolute() const;
|
||||
int get_name_count() const;
|
||||
StringName get_name(int p_idx) const;
|
||||
int get_subname_count() const;
|
||||
StringName get_subname(int p_idx) const;
|
||||
int get_total_name_count() const;
|
||||
Vector<StringName> get_names() const;
|
||||
Vector<StringName> get_subnames() const;
|
||||
StringName get_concatenated_names() const;
|
||||
StringName get_concatenated_subnames() const;
|
||||
NodePath slice(int p_begin, int p_end = INT_MAX) const;
|
||||
|
||||
NodePath rel_path_to(const NodePath &p_np) const;
|
||||
NodePath get_as_property_path() const;
|
||||
|
||||
void prepend_period();
|
||||
|
||||
_FORCE_INLINE_ uint32_t hash() const {
|
||||
if (!data) {
|
||||
return 0;
|
||||
}
|
||||
if (!data->hash_cache_valid) {
|
||||
_update_hash_cache();
|
||||
}
|
||||
return data->hash_cache;
|
||||
}
|
||||
|
||||
explicit operator String() const;
|
||||
bool is_empty() const;
|
||||
|
||||
bool operator==(const NodePath &p_path) const;
|
||||
bool operator!=(const NodePath &p_path) const;
|
||||
void operator=(const NodePath &p_path);
|
||||
|
||||
void simplify();
|
||||
NodePath simplified() const;
|
||||
|
||||
NodePath(const Vector<StringName> &p_path, bool p_absolute);
|
||||
NodePath(const Vector<StringName> &p_path, const Vector<StringName> &p_subpath, bool p_absolute);
|
||||
NodePath(const NodePath &p_path);
|
||||
NodePath(const String &p_path);
|
||||
NodePath() {}
|
||||
~NodePath();
|
||||
};
|
||||
|
||||
// Zero-constructing NodePath initializes data to nullptr (and thus empty).
|
||||
template <>
|
||||
struct is_zero_constructible<NodePath> : std::true_type {};
|
310
core/string/optimized_translation.cpp
Normal file
310
core/string/optimized_translation.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
/**************************************************************************/
|
||||
/* optimized_translation.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 "optimized_translation.h"
|
||||
|
||||
#include "core/templates/pair.h"
|
||||
|
||||
extern "C" {
|
||||
#include "thirdparty/misc/smaz.h"
|
||||
}
|
||||
|
||||
struct CompressedString {
|
||||
int orig_len = 0;
|
||||
CharString compressed;
|
||||
int offset = 0;
|
||||
};
|
||||
|
||||
void OptimizedTranslation::generate(const Ref<Translation> &p_from) {
|
||||
// This method compresses a Translation instance.
|
||||
// Right now, it doesn't handle context or plurals, so Translation subclasses using plurals or context (i.e TranslationPO) shouldn't be compressed.
|
||||
#ifdef TOOLS_ENABLED
|
||||
ERR_FAIL_COND(p_from.is_null());
|
||||
List<StringName> keys;
|
||||
p_from->get_message_list(&keys);
|
||||
|
||||
int size = Math::larger_prime(keys.size());
|
||||
|
||||
Vector<Vector<Pair<int, CharString>>> buckets;
|
||||
Vector<HashMap<uint32_t, int>> table;
|
||||
Vector<uint32_t> hfunc_table;
|
||||
Vector<CompressedString> compressed;
|
||||
|
||||
table.resize(size);
|
||||
hfunc_table.resize(size);
|
||||
buckets.resize(size);
|
||||
compressed.resize(keys.size());
|
||||
|
||||
int idx = 0;
|
||||
int total_compression_size = 0;
|
||||
|
||||
for (const StringName &E : keys) {
|
||||
//hash string
|
||||
CharString cs = E.operator String().utf8();
|
||||
uint32_t h = hash(0, cs.get_data());
|
||||
Pair<int, CharString> p;
|
||||
p.first = idx;
|
||||
p.second = cs;
|
||||
buckets.write[h % size].push_back(p);
|
||||
|
||||
//compress string
|
||||
CharString src_s = p_from->get_message(E).operator String().utf8();
|
||||
CompressedString ps;
|
||||
ps.orig_len = src_s.size();
|
||||
ps.offset = total_compression_size;
|
||||
|
||||
if (ps.orig_len != 0) {
|
||||
CharString dst_s;
|
||||
dst_s.resize_uninitialized(src_s.size());
|
||||
int ret = smaz_compress(src_s.get_data(), src_s.size(), dst_s.ptrw(), src_s.size());
|
||||
if (ret >= src_s.size()) {
|
||||
//if compressed is larger than original, just use original
|
||||
ps.orig_len = src_s.size();
|
||||
ps.compressed = src_s;
|
||||
} else {
|
||||
dst_s.resize_uninitialized(ret);
|
||||
//ps.orig_len=;
|
||||
ps.compressed = dst_s;
|
||||
}
|
||||
} else {
|
||||
ps.orig_len = 1;
|
||||
ps.compressed.resize_uninitialized(1);
|
||||
ps.compressed[0] = 0;
|
||||
}
|
||||
|
||||
compressed.write[idx] = ps;
|
||||
total_compression_size += ps.compressed.size();
|
||||
idx++;
|
||||
}
|
||||
|
||||
int bucket_table_size = 0;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
const Vector<Pair<int, CharString>> &b = buckets[i];
|
||||
HashMap<uint32_t, int> &t = table.write[i];
|
||||
|
||||
if (b.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int d = 1;
|
||||
int item = 0;
|
||||
|
||||
while (item < b.size()) {
|
||||
uint32_t slot = hash(d, b[item].second.get_data());
|
||||
if (t.has(slot)) {
|
||||
item = 0;
|
||||
d++;
|
||||
t.clear();
|
||||
} else {
|
||||
t[slot] = b[item].first;
|
||||
item++;
|
||||
}
|
||||
}
|
||||
|
||||
hfunc_table.write[i] = d;
|
||||
bucket_table_size += 2 + b.size() * 4;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(bucket_table_size == 0);
|
||||
|
||||
hash_table.resize(size);
|
||||
bucket_table.resize(bucket_table_size);
|
||||
|
||||
int *htwb = hash_table.ptrw();
|
||||
int *btwb = bucket_table.ptrw();
|
||||
|
||||
uint32_t *htw = (uint32_t *)&htwb[0];
|
||||
uint32_t *btw = (uint32_t *)&btwb[0];
|
||||
|
||||
int btindex = 0;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
const HashMap<uint32_t, int> &t = table[i];
|
||||
if (t.is_empty()) {
|
||||
htw[i] = 0xFFFFFFFF; //nothing
|
||||
continue;
|
||||
}
|
||||
|
||||
htw[i] = btindex;
|
||||
btw[btindex++] = t.size();
|
||||
btw[btindex++] = hfunc_table[i];
|
||||
|
||||
for (const KeyValue<uint32_t, int> &E : t) {
|
||||
btw[btindex++] = E.key;
|
||||
btw[btindex++] = compressed[E.value].offset;
|
||||
btw[btindex++] = compressed[E.value].compressed.size();
|
||||
btw[btindex++] = compressed[E.value].orig_len;
|
||||
}
|
||||
}
|
||||
|
||||
strings.resize(total_compression_size);
|
||||
uint8_t *cw = strings.ptrw();
|
||||
|
||||
for (int i = 0; i < compressed.size(); i++) {
|
||||
memcpy(&cw[compressed[i].offset], compressed[i].compressed.get_data(), compressed[i].compressed.size());
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(btindex != bucket_table_size);
|
||||
set_locale(p_from->get_locale());
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
bool OptimizedTranslation::_set(const StringName &p_name, const Variant &p_value) {
|
||||
String prop_name = p_name.operator String();
|
||||
if (prop_name == "hash_table") {
|
||||
hash_table = p_value;
|
||||
} else if (prop_name == "bucket_table") {
|
||||
bucket_table = p_value;
|
||||
} else if (prop_name == "strings") {
|
||||
strings = p_value;
|
||||
} else if (prop_name == "load_from") {
|
||||
generate(p_value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool OptimizedTranslation::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
String prop_name = p_name.operator String();
|
||||
if (prop_name == "hash_table") {
|
||||
r_ret = hash_table;
|
||||
} else if (prop_name == "bucket_table") {
|
||||
r_ret = bucket_table;
|
||||
} else if (prop_name == "strings") {
|
||||
r_ret = strings;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
StringName OptimizedTranslation::get_message(const StringName &p_src_text, const StringName &p_context) const {
|
||||
// p_context passed in is ignore. The use of context is not yet supported in OptimizedTranslation.
|
||||
|
||||
int htsize = hash_table.size();
|
||||
|
||||
if (htsize == 0) {
|
||||
return StringName();
|
||||
}
|
||||
|
||||
CharString str = p_src_text.operator String().utf8();
|
||||
uint32_t h = hash(0, str.get_data());
|
||||
|
||||
const int *htr = hash_table.ptr();
|
||||
const uint32_t *htptr = (const uint32_t *)&htr[0];
|
||||
const int *btr = bucket_table.ptr();
|
||||
const uint32_t *btptr = (const uint32_t *)&btr[0];
|
||||
const uint8_t *sr = strings.ptr();
|
||||
const char *sptr = (const char *)&sr[0];
|
||||
|
||||
uint32_t p = htptr[h % htsize];
|
||||
|
||||
if (p == 0xFFFFFFFF) {
|
||||
return StringName(); //nothing
|
||||
}
|
||||
|
||||
const Bucket &bucket = *(const Bucket *)&btptr[p];
|
||||
|
||||
h = hash(bucket.func, str.get_data());
|
||||
|
||||
int idx = -1;
|
||||
|
||||
for (int i = 0; i < bucket.size; i++) {
|
||||
if (bucket.elem[i].key == h) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (idx == -1) {
|
||||
return StringName();
|
||||
}
|
||||
|
||||
if (bucket.elem[idx].comp_size == bucket.elem[idx].uncomp_size) {
|
||||
return String::utf8(&sptr[bucket.elem[idx].str_offset], bucket.elem[idx].uncomp_size);
|
||||
} else {
|
||||
CharString uncomp;
|
||||
uncomp.resize_uninitialized(bucket.elem[idx].uncomp_size + 1);
|
||||
smaz_decompress(&sptr[bucket.elem[idx].str_offset], bucket.elem[idx].comp_size, uncomp.ptrw(), bucket.elem[idx].uncomp_size);
|
||||
return String::utf8(uncomp.get_data());
|
||||
}
|
||||
}
|
||||
|
||||
Vector<String> OptimizedTranslation::get_translated_message_list() const {
|
||||
Vector<String> msgs;
|
||||
|
||||
const int *htr = hash_table.ptr();
|
||||
const uint32_t *htptr = (const uint32_t *)&htr[0];
|
||||
const int *btr = bucket_table.ptr();
|
||||
const uint32_t *btptr = (const uint32_t *)&btr[0];
|
||||
const uint8_t *sr = strings.ptr();
|
||||
const char *sptr = (const char *)&sr[0];
|
||||
|
||||
for (int i = 0; i < hash_table.size(); i++) {
|
||||
uint32_t p = htptr[i];
|
||||
if (p != 0xFFFFFFFF) {
|
||||
const Bucket &bucket = *(const Bucket *)&btptr[p];
|
||||
for (int j = 0; j < bucket.size; j++) {
|
||||
if (bucket.elem[j].comp_size == bucket.elem[j].uncomp_size) {
|
||||
String rstr = String::utf8(&sptr[bucket.elem[j].str_offset], bucket.elem[j].uncomp_size);
|
||||
msgs.push_back(rstr);
|
||||
} else {
|
||||
CharString uncomp;
|
||||
uncomp.resize_uninitialized(bucket.elem[j].uncomp_size + 1);
|
||||
smaz_decompress(&sptr[bucket.elem[j].str_offset], bucket.elem[j].comp_size, uncomp.ptrw(), bucket.elem[j].uncomp_size);
|
||||
String rstr = String::utf8(uncomp.get_data());
|
||||
msgs.push_back(rstr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return msgs;
|
||||
}
|
||||
|
||||
StringName OptimizedTranslation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
|
||||
// The use of plurals translation is not yet supported in OptimizedTranslation.
|
||||
return get_message(p_src_text, p_context);
|
||||
}
|
||||
|
||||
void OptimizedTranslation::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "hash_table"));
|
||||
p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "bucket_table"));
|
||||
p_list->push_back(PropertyInfo(Variant::PACKED_BYTE_ARRAY, "strings"));
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, "load_from", PROPERTY_HINT_RESOURCE_TYPE, "Translation", PROPERTY_USAGE_EDITOR));
|
||||
}
|
||||
|
||||
void OptimizedTranslation::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("generate", "from"), &OptimizedTranslation::generate);
|
||||
}
|
87
core/string/optimized_translation.h
Normal file
87
core/string/optimized_translation.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/**************************************************************************/
|
||||
/* optimized_translation.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/translation.h"
|
||||
|
||||
class OptimizedTranslation : public Translation {
|
||||
GDCLASS(OptimizedTranslation, Translation);
|
||||
|
||||
//this translation uses a sort of modified perfect hash algorithm
|
||||
//it requires hashing strings twice and then does a binary search,
|
||||
//so it's slower, but at the same time it has an extremely high chance
|
||||
//of catching untranslated strings
|
||||
|
||||
//load/store friendly types
|
||||
Vector<int> hash_table;
|
||||
Vector<int> bucket_table;
|
||||
Vector<uint8_t> strings;
|
||||
|
||||
struct Bucket {
|
||||
int size;
|
||||
uint32_t func;
|
||||
|
||||
struct Elem {
|
||||
uint32_t key;
|
||||
uint32_t str_offset;
|
||||
uint32_t comp_size;
|
||||
uint32_t uncomp_size;
|
||||
};
|
||||
|
||||
Elem elem[1];
|
||||
};
|
||||
|
||||
_FORCE_INLINE_ uint32_t hash(uint32_t d, const char *p_str) const {
|
||||
if (d == 0) {
|
||||
d = 0x1000193;
|
||||
}
|
||||
while (*p_str) {
|
||||
d = (d * 0x1000193) ^ static_cast<uint8_t>(*p_str);
|
||||
p_str++;
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
virtual StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const override; //overridable for other implementations
|
||||
virtual StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const override;
|
||||
virtual Vector<String> get_translated_message_list() const override;
|
||||
void generate(const Ref<Translation> &p_from);
|
||||
|
||||
OptimizedTranslation() {}
|
||||
};
|
346
core/string/print_string.cpp
Normal file
346
core/string/print_string.cpp
Normal file
@@ -0,0 +1,346 @@
|
||||
/**************************************************************************/
|
||||
/* print_string.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 "print_string.h"
|
||||
|
||||
#include "core/core_globals.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
static PrintHandlerList *print_handler_list = nullptr;
|
||||
static thread_local bool is_printing = false;
|
||||
|
||||
static void __print_fallback(const String &p_string, bool p_err) {
|
||||
fprintf(p_err ? stderr : stdout, "While attempting to print a message, another message was printed:\n%s\n", p_string.utf8().get_data());
|
||||
}
|
||||
|
||||
void add_print_handler(PrintHandlerList *p_handler) {
|
||||
_global_lock();
|
||||
p_handler->next = print_handler_list;
|
||||
print_handler_list = p_handler;
|
||||
_global_unlock();
|
||||
}
|
||||
|
||||
void remove_print_handler(const PrintHandlerList *p_handler) {
|
||||
_global_lock();
|
||||
|
||||
PrintHandlerList *prev = nullptr;
|
||||
PrintHandlerList *l = print_handler_list;
|
||||
|
||||
while (l) {
|
||||
if (l == p_handler) {
|
||||
if (prev) {
|
||||
prev->next = l->next;
|
||||
} else {
|
||||
print_handler_list = l->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
prev = l;
|
||||
l = l->next;
|
||||
}
|
||||
//OS::get_singleton()->print("print handler list is %p\n",print_handler_list);
|
||||
|
||||
_global_unlock();
|
||||
ERR_FAIL_NULL(l);
|
||||
}
|
||||
|
||||
void __print_line(const String &p_string) {
|
||||
if (!CoreGlobals::print_line_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_printing) {
|
||||
__print_fallback(p_string, false);
|
||||
return;
|
||||
}
|
||||
|
||||
is_printing = true;
|
||||
|
||||
OS::get_singleton()->print("%s\n", p_string.utf8().get_data());
|
||||
|
||||
_global_lock();
|
||||
PrintHandlerList *l = print_handler_list;
|
||||
while (l) {
|
||||
l->printfunc(l->userdata, p_string, false, false);
|
||||
l = l->next;
|
||||
}
|
||||
|
||||
_global_unlock();
|
||||
|
||||
is_printing = false;
|
||||
}
|
||||
|
||||
void __print_line_rich(const String &p_string) {
|
||||
if (!CoreGlobals::print_line_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 output;
|
||||
int pos = 0;
|
||||
while (pos <= p_string.length()) {
|
||||
int brk_pos = p_string.find_char('[', pos);
|
||||
|
||||
if (brk_pos < 0) {
|
||||
brk_pos = p_string.length();
|
||||
}
|
||||
|
||||
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);
|
||||
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 += "[";
|
||||
pos = brk_pos + 1;
|
||||
}
|
||||
}
|
||||
output += "\u001b[0m"; // Reset.
|
||||
|
||||
if (is_printing) {
|
||||
__print_fallback(output, false);
|
||||
return;
|
||||
}
|
||||
|
||||
is_printing = true;
|
||||
|
||||
OS::get_singleton()->print_rich("%s\n", output.utf8().get_data());
|
||||
|
||||
_global_lock();
|
||||
PrintHandlerList *l = print_handler_list;
|
||||
while (l) {
|
||||
l->printfunc(l->userdata, p_string, false, true);
|
||||
l = l->next;
|
||||
}
|
||||
|
||||
_global_unlock();
|
||||
|
||||
is_printing = false;
|
||||
}
|
||||
|
||||
void print_raw(const String &p_string) {
|
||||
if (is_printing) {
|
||||
__print_fallback(p_string, true);
|
||||
return;
|
||||
}
|
||||
|
||||
is_printing = true;
|
||||
|
||||
OS::get_singleton()->print("%s", p_string.utf8().get_data());
|
||||
|
||||
is_printing = false;
|
||||
}
|
||||
|
||||
void print_error(const String &p_string) {
|
||||
if (!CoreGlobals::print_error_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_printing) {
|
||||
__print_fallback(p_string, true);
|
||||
return;
|
||||
}
|
||||
|
||||
is_printing = true;
|
||||
|
||||
OS::get_singleton()->printerr("%s\n", p_string.utf8().get_data());
|
||||
|
||||
_global_lock();
|
||||
PrintHandlerList *l = print_handler_list;
|
||||
while (l) {
|
||||
l->printfunc(l->userdata, p_string, true, false);
|
||||
l = l->next;
|
||||
}
|
||||
|
||||
_global_unlock();
|
||||
|
||||
is_printing = false;
|
||||
}
|
||||
|
||||
bool is_print_verbose_enabled() {
|
||||
return OS::get_singleton()->is_stdout_verbose();
|
||||
}
|
||||
|
||||
String stringify_variants(const Variant &p_var) {
|
||||
return p_var.operator String();
|
||||
}
|
88
core/string/print_string.h
Normal file
88
core/string/print_string.h
Normal file
@@ -0,0 +1,88 @@
|
||||
/**************************************************************************/
|
||||
/* print_string.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/variant/variant.h"
|
||||
|
||||
extern void (*_print_func)(String);
|
||||
|
||||
typedef void (*PrintHandlerFunc)(void *, const String &p_string, bool p_error, bool p_rich);
|
||||
|
||||
struct PrintHandlerList {
|
||||
PrintHandlerFunc printfunc = nullptr;
|
||||
void *userdata = nullptr;
|
||||
|
||||
PrintHandlerList *next = nullptr;
|
||||
|
||||
PrintHandlerList() {}
|
||||
};
|
||||
|
||||
String stringify_variants(const Variant &p_var);
|
||||
|
||||
template <typename... Args>
|
||||
String stringify_variants(const Variant &p_var, Args... p_args) {
|
||||
return p_var.operator String() + " " + stringify_variants(p_args...);
|
||||
}
|
||||
|
||||
void add_print_handler(PrintHandlerList *p_handler);
|
||||
void remove_print_handler(const PrintHandlerList *p_handler);
|
||||
|
||||
extern void __print_line(const String &p_string);
|
||||
extern void __print_line_rich(const String &p_string);
|
||||
extern void print_raw(const String &p_string);
|
||||
extern void print_error(const String &p_string);
|
||||
extern bool is_print_verbose_enabled();
|
||||
|
||||
// This version avoids processing the text to be printed until it actually has to be printed, saving some CPU usage.
|
||||
#define print_verbose(m_text) \
|
||||
{ \
|
||||
if (is_print_verbose_enabled()) { \
|
||||
print_line(m_text); \
|
||||
} \
|
||||
}
|
||||
|
||||
inline void print_line(const Variant &v) {
|
||||
__print_line(stringify_variants(v));
|
||||
}
|
||||
|
||||
inline void print_line_rich(const Variant &v) {
|
||||
__print_line_rich(stringify_variants(v));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void print_line(const Variant &p_var, Args... p_args) {
|
||||
__print_line(stringify_variants(p_var, p_args...));
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void print_line_rich(const Variant &p_var, Args... p_args) {
|
||||
__print_line_rich(stringify_variants(p_var, p_args...));
|
||||
}
|
160
core/string/string_buffer.h
Normal file
160
core/string/string_buffer.h
Normal file
@@ -0,0 +1,160 @@
|
||||
/**************************************************************************/
|
||||
/* string_buffer.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
template <int SHORT_BUFFER_SIZE = 64>
|
||||
class StringBuffer {
|
||||
char32_t short_buffer[SHORT_BUFFER_SIZE];
|
||||
String buffer;
|
||||
int string_length = 0;
|
||||
|
||||
_FORCE_INLINE_ char32_t *current_buffer_ptr() {
|
||||
return static_cast<String &>(buffer).is_empty() ? short_buffer : buffer.ptrw();
|
||||
}
|
||||
|
||||
public:
|
||||
StringBuffer &append(char32_t p_char);
|
||||
StringBuffer &append(const String &p_string);
|
||||
StringBuffer &append(const char *p_str);
|
||||
StringBuffer &append(const char32_t *p_str, int p_clip_to_len = -1);
|
||||
|
||||
_FORCE_INLINE_ void operator+=(char32_t p_char) {
|
||||
append(p_char);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void operator+=(const String &p_string) {
|
||||
append(p_string);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void operator+=(const char *p_str) {
|
||||
append(p_str);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void operator+=(const char32_t *p_str) {
|
||||
append(p_str);
|
||||
}
|
||||
|
||||
StringBuffer &reserve(int p_size);
|
||||
|
||||
int length() const;
|
||||
|
||||
String as_string();
|
||||
|
||||
double as_double();
|
||||
int64_t as_int();
|
||||
|
||||
_FORCE_INLINE_ operator String() {
|
||||
return as_string();
|
||||
}
|
||||
};
|
||||
|
||||
template <int SHORT_BUFFER_SIZE>
|
||||
StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(char32_t p_char) {
|
||||
reserve(string_length + 2);
|
||||
current_buffer_ptr()[string_length++] = p_char;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <int SHORT_BUFFER_SIZE>
|
||||
StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const String &p_string) {
|
||||
return append(p_string.get_data());
|
||||
}
|
||||
|
||||
template <int SHORT_BUFFER_SIZE>
|
||||
StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const char *p_str) {
|
||||
int len = strlen(p_str);
|
||||
reserve(string_length + len + 1);
|
||||
|
||||
char32_t *buf = current_buffer_ptr();
|
||||
for (const char *c_ptr = p_str; *c_ptr; ++c_ptr) {
|
||||
buf[string_length++] = *c_ptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <int SHORT_BUFFER_SIZE>
|
||||
StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const char32_t *p_str, int p_clip_to_len) {
|
||||
int len = 0;
|
||||
while ((p_clip_to_len < 0 || len < p_clip_to_len) && p_str[len]) {
|
||||
++len;
|
||||
}
|
||||
reserve(string_length + len + 1);
|
||||
memcpy(&(current_buffer_ptr()[string_length]), p_str, len * sizeof(char32_t));
|
||||
string_length += len;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <int SHORT_BUFFER_SIZE>
|
||||
StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::reserve(int p_size) {
|
||||
ERR_FAIL_COND_V_MSG(p_size < length(), *this, "reserve() called with a capacity smaller than the current size. This is likely a mistake.");
|
||||
if (p_size <= SHORT_BUFFER_SIZE || p_size <= buffer.size()) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool need_copy = string_length > 0 && buffer.is_empty();
|
||||
buffer.resize_uninitialized(next_power_of_2((uint32_t)p_size));
|
||||
if (need_copy) {
|
||||
memcpy(buffer.ptrw(), short_buffer, string_length * sizeof(char32_t));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <int SHORT_BUFFER_SIZE>
|
||||
int StringBuffer<SHORT_BUFFER_SIZE>::length() const {
|
||||
return string_length;
|
||||
}
|
||||
|
||||
template <int SHORT_BUFFER_SIZE>
|
||||
String StringBuffer<SHORT_BUFFER_SIZE>::as_string() {
|
||||
current_buffer_ptr()[string_length] = '\0';
|
||||
if (buffer.is_empty()) {
|
||||
return String(short_buffer);
|
||||
} else {
|
||||
buffer.resize_uninitialized(string_length + 1);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
template <int SHORT_BUFFER_SIZE>
|
||||
double StringBuffer<SHORT_BUFFER_SIZE>::as_double() {
|
||||
current_buffer_ptr()[string_length] = '\0';
|
||||
return String::to_float(current_buffer_ptr());
|
||||
}
|
||||
|
||||
template <int SHORT_BUFFER_SIZE>
|
||||
int64_t StringBuffer<SHORT_BUFFER_SIZE>::as_int() {
|
||||
current_buffer_ptr()[string_length] = '\0';
|
||||
return String::to_int(current_buffer_ptr());
|
||||
}
|
98
core/string/string_builder.cpp
Normal file
98
core/string/string_builder.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/**************************************************************************/
|
||||
/* string_builder.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 "string_builder.h"
|
||||
|
||||
StringBuilder &StringBuilder::append(const String &p_string) {
|
||||
if (p_string.is_empty()) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
strings.push_back(p_string);
|
||||
appended_strings.push_back(-1);
|
||||
|
||||
string_length += p_string.length();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
StringBuilder &StringBuilder::append(const char *p_cstring) {
|
||||
int32_t len = strlen(p_cstring);
|
||||
|
||||
c_strings.push_back(p_cstring);
|
||||
appended_strings.push_back(len);
|
||||
|
||||
string_length += len;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
String StringBuilder::as_string() const {
|
||||
if (string_length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String string;
|
||||
string.resize_uninitialized(string_length + 1);
|
||||
char32_t *buffer = string.ptrw();
|
||||
|
||||
int current_position = 0;
|
||||
|
||||
int godot_string_elem = 0;
|
||||
int c_string_elem = 0;
|
||||
|
||||
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];
|
||||
|
||||
memcpy(buffer + current_position, s.ptr(), s.length() * sizeof(char32_t));
|
||||
|
||||
current_position += s.length();
|
||||
|
||||
godot_string_elem++;
|
||||
} else {
|
||||
const char *s = c_strings[c_string_elem];
|
||||
|
||||
for (int32_t j = 0; j < str_len; j++) {
|
||||
buffer[current_position + j] = s[j];
|
||||
}
|
||||
|
||||
current_position += str_len;
|
||||
|
||||
c_string_elem++;
|
||||
}
|
||||
}
|
||||
buffer[current_position] = 0;
|
||||
|
||||
return string;
|
||||
}
|
81
core/string/string_builder.h
Normal file
81
core/string/string_builder.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/**************************************************************************/
|
||||
/* string_builder.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
class StringBuilder {
|
||||
uint32_t string_length = 0;
|
||||
|
||||
LocalVector<String> strings;
|
||||
LocalVector<const char *> c_strings;
|
||||
|
||||
// -1 means it's a Godot String
|
||||
// a natural number means C string.
|
||||
LocalVector<int32_t> appended_strings;
|
||||
|
||||
public:
|
||||
StringBuilder &append(const String &p_string);
|
||||
StringBuilder &append(const char *p_cstring);
|
||||
|
||||
_FORCE_INLINE_ StringBuilder &operator+(const String &p_string) {
|
||||
return append(p_string);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ StringBuilder &operator+(const char *p_cstring) {
|
||||
return append(p_cstring);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void operator+=(const String &p_string) {
|
||||
append(p_string);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void operator+=(const char *p_cstring) {
|
||||
append(p_cstring);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ int num_strings_appended() const {
|
||||
return appended_strings.size();
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ uint32_t get_string_length() const {
|
||||
return string_length;
|
||||
}
|
||||
|
||||
String as_string() const;
|
||||
|
||||
_FORCE_INLINE_ operator String() const {
|
||||
return as_string();
|
||||
}
|
||||
|
||||
StringBuilder() {}
|
||||
};
|
329
core/string/string_name.cpp
Normal file
329
core/string/string_name.cpp
Normal file
@@ -0,0 +1,329 @@
|
||||
/**************************************************************************/
|
||||
/* string_name.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 "string_name.h"
|
||||
|
||||
#include "core/os/mutex.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/string/print_string.h"
|
||||
|
||||
struct StringName::Table {
|
||||
constexpr static uint32_t TABLE_BITS = 16;
|
||||
constexpr static uint32_t TABLE_LEN = 1 << TABLE_BITS;
|
||||
constexpr static uint32_t TABLE_MASK = TABLE_LEN - 1;
|
||||
|
||||
static inline _Data *table[TABLE_LEN];
|
||||
static inline BinaryMutex mutex;
|
||||
static inline PagedAllocator<_Data> allocator;
|
||||
};
|
||||
|
||||
void StringName::setup() {
|
||||
ERR_FAIL_COND(configured);
|
||||
for (uint32_t i = 0; i < Table::TABLE_LEN; i++) {
|
||||
Table::table[i] = nullptr;
|
||||
}
|
||||
configured = true;
|
||||
}
|
||||
|
||||
void StringName::cleanup() {
|
||||
MutexLock lock(Table::mutex);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (unlikely(debug_stringname)) {
|
||||
Vector<_Data *> data;
|
||||
for (uint32_t i = 0; i < Table::TABLE_LEN; i++) {
|
||||
_Data *d = Table::table[i];
|
||||
while (d) {
|
||||
data.push_back(d);
|
||||
d = d->next;
|
||||
}
|
||||
}
|
||||
|
||||
print_line("\nStringName reference ranking (from most to least referenced):\n");
|
||||
|
||||
data.sort_custom<DebugSortReferences>();
|
||||
int unreferenced_stringnames = 0;
|
||||
int rarely_referenced_stringnames = 0;
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
print_line(itos(i + 1) + ": " + data[i]->name + " - " + itos(data[i]->debug_references));
|
||||
if (data[i]->debug_references == 0) {
|
||||
unreferenced_stringnames += 1;
|
||||
} else if (data[i]->debug_references < 5) {
|
||||
rarely_referenced_stringnames += 1;
|
||||
}
|
||||
}
|
||||
|
||||
print_line(vformat("\nOut of %d StringNames, %d StringNames were never referenced during this run (0 times) (%.2f%%).", data.size(), unreferenced_stringnames, unreferenced_stringnames / float(data.size()) * 100));
|
||||
print_line(vformat("Out of %d StringNames, %d StringNames were rarely referenced during this run (1-4 times) (%.2f%%).", data.size(), rarely_referenced_stringnames, rarely_referenced_stringnames / float(data.size()) * 100));
|
||||
}
|
||||
#endif
|
||||
int lost_strings = 0;
|
||||
for (uint32_t i = 0; i < Table::TABLE_LEN; i++) {
|
||||
while (Table::table[i]) {
|
||||
_Data *d = Table::table[i];
|
||||
if (d->static_count.get() != d->refcount.get()) {
|
||||
lost_strings++;
|
||||
|
||||
if (OS::get_singleton()->is_stdout_verbose()) {
|
||||
print_line(vformat("Orphan StringName: %s (static: %d, total: %d)", d->name, d->static_count.get(), d->refcount.get()));
|
||||
}
|
||||
}
|
||||
|
||||
Table::table[i] = Table::table[i]->next;
|
||||
Table::allocator.free(d);
|
||||
}
|
||||
}
|
||||
if (lost_strings) {
|
||||
print_verbose(vformat("StringName: %d unclaimed string names at exit.", lost_strings));
|
||||
}
|
||||
configured = false;
|
||||
}
|
||||
|
||||
void StringName::unref() {
|
||||
ERR_FAIL_COND(!configured);
|
||||
|
||||
if (_data && _data->refcount.unref()) {
|
||||
MutexLock lock(Table::mutex);
|
||||
|
||||
if (CoreGlobals::leak_reporting_enabled && _data->static_count.get() > 0) {
|
||||
ERR_PRINT("BUG: Unreferenced static string to 0: " + _data->name);
|
||||
}
|
||||
if (_data->prev) {
|
||||
_data->prev->next = _data->next;
|
||||
} else {
|
||||
const uint32_t idx = _data->hash & Table::TABLE_MASK;
|
||||
Table::table[idx] = _data->next;
|
||||
}
|
||||
|
||||
if (_data->next) {
|
||||
_data->next->prev = _data->prev;
|
||||
}
|
||||
Table::allocator.free(_data);
|
||||
}
|
||||
|
||||
_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 _data->name == p_name;
|
||||
}
|
||||
|
||||
return p_name.is_empty();
|
||||
}
|
||||
|
||||
bool StringName::operator==(const char *p_name) const {
|
||||
if (_data) {
|
||||
return _data->name == p_name;
|
||||
}
|
||||
|
||||
return p_name[0] == 0;
|
||||
}
|
||||
|
||||
bool StringName::operator!=(const String &p_name) const {
|
||||
return !(operator==(p_name));
|
||||
}
|
||||
|
||||
bool StringName::operator!=(const char *p_name) const {
|
||||
return !(operator==(p_name));
|
||||
}
|
||||
|
||||
char32_t StringName::operator[](int p_index) const {
|
||||
if (_data) {
|
||||
return _data->name[p_index];
|
||||
}
|
||||
|
||||
CRASH_BAD_INDEX(p_index, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int StringName::length() const {
|
||||
if (_data) {
|
||||
return _data->name.length();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
StringName &StringName::operator=(const StringName &p_name) {
|
||||
if (this == &p_name) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
unref();
|
||||
|
||||
if (p_name._data && p_name._data->refcount.ref()) {
|
||||
_data = p_name._data;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
StringName::StringName(const StringName &p_name) {
|
||||
_data = nullptr;
|
||||
|
||||
ERR_FAIL_COND(!configured);
|
||||
|
||||
if (p_name._data && p_name._data->refcount.ref()) {
|
||||
_data = p_name._data;
|
||||
}
|
||||
}
|
||||
|
||||
StringName::StringName(const char *p_name, bool p_static) {
|
||||
_data = nullptr;
|
||||
|
||||
ERR_FAIL_COND(!configured);
|
||||
|
||||
if (!p_name || p_name[0] == 0) {
|
||||
return; //empty, ignore
|
||||
}
|
||||
|
||||
const uint32_t hash = String::hash(p_name);
|
||||
const uint32_t idx = hash & Table::TABLE_MASK;
|
||||
|
||||
MutexLock lock(Table::mutex);
|
||||
_data = Table::table[idx];
|
||||
|
||||
while (_data) {
|
||||
// compare hash first
|
||||
if (_data->hash == hash && _data->name == p_name) {
|
||||
break;
|
||||
}
|
||||
_data = _data->next;
|
||||
}
|
||||
|
||||
if (_data && _data->refcount.ref()) {
|
||||
// exists
|
||||
if (p_static) {
|
||||
_data->static_count.increment();
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (unlikely(debug_stringname)) {
|
||||
_data->debug_references++;
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
_data = Table::allocator.alloc();
|
||||
_data->name = p_name;
|
||||
_data->refcount.init();
|
||||
_data->static_count.set(p_static ? 1 : 0);
|
||||
_data->hash = hash;
|
||||
_data->next = Table::table[idx];
|
||||
_data->prev = nullptr;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (unlikely(debug_stringname)) {
|
||||
// Keep in memory, force static.
|
||||
_data->refcount.ref();
|
||||
_data->static_count.increment();
|
||||
}
|
||||
#endif
|
||||
if (Table::table[idx]) {
|
||||
Table::table[idx]->prev = _data;
|
||||
}
|
||||
Table::table[idx] = _data;
|
||||
}
|
||||
|
||||
StringName::StringName(const String &p_name, bool p_static) {
|
||||
_data = nullptr;
|
||||
|
||||
ERR_FAIL_COND(!configured);
|
||||
|
||||
if (p_name.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t hash = p_name.hash();
|
||||
const uint32_t idx = hash & Table::TABLE_MASK;
|
||||
|
||||
MutexLock lock(Table::mutex);
|
||||
_data = Table::table[idx];
|
||||
|
||||
while (_data) {
|
||||
if (_data->hash == hash && _data->name == p_name) {
|
||||
break;
|
||||
}
|
||||
_data = _data->next;
|
||||
}
|
||||
|
||||
if (_data && _data->refcount.ref()) {
|
||||
// exists
|
||||
if (p_static) {
|
||||
_data->static_count.increment();
|
||||
}
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (unlikely(debug_stringname)) {
|
||||
_data->debug_references++;
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
_data = Table::allocator.alloc();
|
||||
_data->name = p_name;
|
||||
_data->refcount.init();
|
||||
_data->static_count.set(p_static ? 1 : 0);
|
||||
_data->hash = hash;
|
||||
_data->next = Table::table[idx];
|
||||
_data->prev = nullptr;
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (unlikely(debug_stringname)) {
|
||||
// Keep in memory, force static.
|
||||
_data->refcount.ref();
|
||||
_data->static_count.increment();
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Table::table[idx]) {
|
||||
Table::table[idx]->prev = _data;
|
||||
}
|
||||
Table::table[idx] = _data;
|
||||
}
|
||||
|
||||
bool operator==(const String &p_name, const StringName &p_string_name) {
|
||||
return p_string_name.operator==(p_name);
|
||||
}
|
||||
bool operator!=(const String &p_name, const StringName &p_string_name) {
|
||||
return p_string_name.operator!=(p_name);
|
||||
}
|
||||
|
||||
bool operator==(const char *p_name, const StringName &p_string_name) {
|
||||
return p_string_name.operator==(p_name);
|
||||
}
|
||||
bool operator!=(const char *p_name, const StringName &p_string_name) {
|
||||
return p_string_name.operator!=(p_name);
|
||||
}
|
213
core/string/string_name.h
Normal file
213
core/string/string_name.h
Normal file
@@ -0,0 +1,213 @@
|
||||
/**************************************************************************/
|
||||
/* string_name.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/templates/safe_refcount.h"
|
||||
|
||||
#define UNIQUE_NODE_PREFIX "%"
|
||||
|
||||
class Main;
|
||||
|
||||
class [[nodiscard]] StringName {
|
||||
struct Table;
|
||||
|
||||
struct _Data {
|
||||
SafeRefCount refcount;
|
||||
SafeNumeric<uint32_t> static_count;
|
||||
String name;
|
||||
#ifdef DEBUG_ENABLED
|
||||
uint32_t debug_references = 0;
|
||||
#endif
|
||||
|
||||
uint32_t hash = 0;
|
||||
_Data *prev = nullptr;
|
||||
_Data *next = nullptr;
|
||||
_Data() {}
|
||||
};
|
||||
|
||||
_Data *_data = nullptr;
|
||||
|
||||
void unref();
|
||||
friend void register_core_types();
|
||||
friend void unregister_core_types();
|
||||
friend class Main;
|
||||
static void setup();
|
||||
static void cleanup();
|
||||
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 {
|
||||
return p_left->debug_references > p_right->debug_references;
|
||||
}
|
||||
};
|
||||
|
||||
static inline bool debug_stringname = false;
|
||||
#endif
|
||||
|
||||
StringName(_Data *p_data) { _data = p_data; }
|
||||
|
||||
public:
|
||||
_FORCE_INLINE_ explicit operator bool() const { return _data; }
|
||||
|
||||
bool operator==(const String &p_name) const;
|
||||
bool operator==(const char *p_name) const;
|
||||
bool operator!=(const String &p_name) const;
|
||||
bool operator!=(const char *p_name) const;
|
||||
|
||||
const char32_t *get_data() const { return _data ? _data->name.ptr() : U""; }
|
||||
char32_t operator[](int p_index) const;
|
||||
int length() const;
|
||||
_FORCE_INLINE_ bool is_empty() const { return !_data; }
|
||||
|
||||
_FORCE_INLINE_ bool is_node_unique_name() const {
|
||||
if (!_data) {
|
||||
return false;
|
||||
}
|
||||
return (char32_t)_data->name[0] == (char32_t)UNIQUE_NODE_PREFIX[0];
|
||||
}
|
||||
_FORCE_INLINE_ bool operator<(const StringName &p_name) const {
|
||||
return _data < p_name._data;
|
||||
}
|
||||
_FORCE_INLINE_ bool operator<=(const StringName &p_name) const {
|
||||
return _data <= p_name._data;
|
||||
}
|
||||
_FORCE_INLINE_ bool operator>(const StringName &p_name) const {
|
||||
return _data > p_name._data;
|
||||
}
|
||||
_FORCE_INLINE_ bool operator>=(const StringName &p_name) const {
|
||||
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.
|
||||
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 get_empty_hash();
|
||||
}
|
||||
}
|
||||
_FORCE_INLINE_ const void *data_unique_pointer() const {
|
||||
return (void *)_data;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ operator String() const {
|
||||
if (_data) {
|
||||
return _data->name;
|
||||
}
|
||||
|
||||
return String();
|
||||
}
|
||||
|
||||
struct AlphCompare {
|
||||
template <typename LT, typename RT>
|
||||
_FORCE_INLINE_ bool operator()(const LT &l, const RT &r) const {
|
||||
return compare(l, r);
|
||||
}
|
||||
_FORCE_INLINE_ static bool compare(const StringName &l, const StringName &r) {
|
||||
return str_compare(l.get_data(), r.get_data()) < 0;
|
||||
}
|
||||
_FORCE_INLINE_ static bool compare(const String &l, const StringName &r) {
|
||||
return str_compare(l.get_data(), r.get_data()) < 0;
|
||||
}
|
||||
_FORCE_INLINE_ static bool compare(const StringName &l, const String &r) {
|
||||
return str_compare(l.get_data(), r.get_data()) < 0;
|
||||
}
|
||||
_FORCE_INLINE_ static bool compare(const String &l, const String &r) {
|
||||
return str_compare(l.get_data(), r.get_data()) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
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() {}
|
||||
|
||||
#ifdef SIZE_EXTRA
|
||||
_NO_INLINE_
|
||||
#else
|
||||
_FORCE_INLINE_
|
||||
#endif
|
||||
~StringName() {
|
||||
if (likely(configured) && _data) { //only free if configured
|
||||
unref();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
static void set_debug_stringnames(bool p_enable) { debug_stringname = p_enable; }
|
||||
#endif
|
||||
};
|
||||
|
||||
// Zero-constructing StringName initializes _data to nullptr (and thus empty).
|
||||
template <>
|
||||
struct is_zero_constructible<StringName> : std::true_type {};
|
||||
|
||||
bool operator==(const String &p_name, const StringName &p_string_name);
|
||||
bool operator!=(const String &p_name, const StringName &p_string_name);
|
||||
bool operator==(const char *p_name, const StringName &p_string_name);
|
||||
bool operator!=(const char *p_name, const StringName &p_string_name);
|
||||
|
||||
/*
|
||||
* The SNAME macro is used to speed up StringName creation, as it allows caching it after the first usage in a very efficient way.
|
||||
* It should NOT be used everywhere, but instead in places where high performance is required and the creation of a StringName
|
||||
* can be costly. Places where it should be used are:
|
||||
* - Control::get_theme_*(<name> and Window::get_theme_*(<name> functions.
|
||||
* - emit_signal(<name>,..) function
|
||||
* - call_deferred(<name>,..) function
|
||||
* - Comparisons to a StringName in overridden _set and _get methods.
|
||||
*
|
||||
* Use in places that can be called hundreds of times per frame (or more) is recommended, but this situation is very rare. If in doubt, do not use.
|
||||
*/
|
||||
|
||||
#define SNAME(m_arg) ([]() -> const StringName & { static StringName sname = StringName(m_arg, true); return sname; })()
|
153
core/string/translation.cpp
Normal file
153
core/string/translation.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
/**************************************************************************/
|
||||
/* translation.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.h"
|
||||
|
||||
#include "core/os/thread.h"
|
||||
#include "core/string/translation_server.h"
|
||||
|
||||
Dictionary Translation::_get_messages() const {
|
||||
Dictionary d;
|
||||
for (const KeyValue<StringName, StringName> &E : translation_map) {
|
||||
d[E.key] = E.value;
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
Vector<String> Translation::_get_message_list() const {
|
||||
Vector<String> msgs;
|
||||
msgs.resize(translation_map.size());
|
||||
int idx = 0;
|
||||
for (const KeyValue<StringName, StringName> &E : translation_map) {
|
||||
msgs.set(idx, E.key);
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
return msgs;
|
||||
}
|
||||
|
||||
Vector<String> Translation::get_translated_message_list() const {
|
||||
Vector<String> msgs;
|
||||
msgs.resize(translation_map.size());
|
||||
int idx = 0;
|
||||
for (const KeyValue<StringName, StringName> &E : translation_map) {
|
||||
msgs.set(idx, E.value);
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
return msgs;
|
||||
}
|
||||
|
||||
void Translation::_set_messages(const Dictionary &p_messages) {
|
||||
for (const KeyValue<Variant, Variant> &kv : p_messages) {
|
||||
translation_map[kv.key] = kv.value;
|
||||
}
|
||||
}
|
||||
|
||||
void Translation::set_locale(const String &p_locale) {
|
||||
locale = TranslationServer::get_singleton()->standardize_locale(p_locale);
|
||||
}
|
||||
|
||||
void Translation::add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) {
|
||||
translation_map[p_src_text] = p_xlated_text;
|
||||
}
|
||||
|
||||
void Translation::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
|
||||
WARN_PRINT("Translation class doesn't handle plural messages. Calling add_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class");
|
||||
ERR_FAIL_COND_MSG(p_plural_xlated_texts.is_empty(), "Parameter vector p_plural_xlated_texts passed in is empty.");
|
||||
translation_map[p_src_text] = p_plural_xlated_texts[0];
|
||||
}
|
||||
|
||||
StringName Translation::get_message(const StringName &p_src_text, const StringName &p_context) const {
|
||||
StringName ret;
|
||||
if (GDVIRTUAL_CALL(_get_message, p_src_text, p_context, ret)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (p_context != StringName()) {
|
||||
WARN_PRINT("Translation class doesn't handle context. Using context in get_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class");
|
||||
}
|
||||
|
||||
HashMap<StringName, StringName>::ConstIterator E = translation_map.find(p_src_text);
|
||||
if (!E) {
|
||||
return StringName();
|
||||
}
|
||||
|
||||
return E->value;
|
||||
}
|
||||
|
||||
StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
|
||||
StringName ret;
|
||||
if (GDVIRTUAL_CALL(_get_plural_message, p_src_text, p_plural_text, p_n, p_context, ret)) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
WARN_PRINT("Translation class doesn't handle plural messages. Calling get_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class");
|
||||
return get_message(p_src_text);
|
||||
}
|
||||
|
||||
void Translation::erase_message(const StringName &p_src_text, const StringName &p_context) {
|
||||
if (p_context != StringName()) {
|
||||
WARN_PRINT("Translation class doesn't handle context. Using context in erase_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class");
|
||||
}
|
||||
|
||||
translation_map.erase(p_src_text);
|
||||
}
|
||||
|
||||
void Translation::get_message_list(List<StringName> *r_messages) const {
|
||||
for (const KeyValue<StringName, StringName> &E : translation_map) {
|
||||
r_messages->push_back(E.key);
|
||||
}
|
||||
}
|
||||
|
||||
int Translation::get_message_count() const {
|
||||
return translation_map.size();
|
||||
}
|
||||
|
||||
void Translation::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_locale", "locale"), &Translation::set_locale);
|
||||
ClassDB::bind_method(D_METHOD("get_locale"), &Translation::get_locale);
|
||||
ClassDB::bind_method(D_METHOD("add_message", "src_message", "xlated_message", "context"), &Translation::add_message, DEFVAL(StringName()));
|
||||
ClassDB::bind_method(D_METHOD("add_plural_message", "src_message", "xlated_messages", "context"), &Translation::add_plural_message, DEFVAL(StringName()));
|
||||
ClassDB::bind_method(D_METHOD("get_message", "src_message", "context"), &Translation::get_message, DEFVAL(StringName()));
|
||||
ClassDB::bind_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL(StringName()));
|
||||
ClassDB::bind_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL(StringName()));
|
||||
ClassDB::bind_method(D_METHOD("get_message_list"), &Translation::_get_message_list);
|
||||
ClassDB::bind_method(D_METHOD("get_translated_message_list"), &Translation::get_translated_message_list);
|
||||
ClassDB::bind_method(D_METHOD("get_message_count"), &Translation::get_message_count);
|
||||
ClassDB::bind_method(D_METHOD("_set_messages", "messages"), &Translation::_set_messages);
|
||||
ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages);
|
||||
|
||||
GDVIRTUAL_BIND(_get_plural_message, "src_message", "src_plural_message", "n", "context");
|
||||
GDVIRTUAL_BIND(_get_message, "src_message", "context");
|
||||
|
||||
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");
|
||||
}
|
68
core/string/translation.h
Normal file
68
core/string/translation.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/**************************************************************************/
|
||||
/* translation.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/io/resource.h"
|
||||
#include "core/object/gdvirtual.gen.inc"
|
||||
|
||||
class Translation : public Resource {
|
||||
GDCLASS(Translation, Resource);
|
||||
OBJ_SAVE_TYPE(Translation);
|
||||
RES_BASE_EXTENSION("translation");
|
||||
|
||||
String locale = "en";
|
||||
HashMap<StringName, StringName> translation_map;
|
||||
|
||||
virtual Vector<String> _get_message_list() const;
|
||||
virtual Dictionary _get_messages() const;
|
||||
virtual void _set_messages(const Dictionary &p_messages);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
GDVIRTUAL2RC(StringName, _get_message, StringName, StringName);
|
||||
GDVIRTUAL4RC(StringName, _get_plural_message, StringName, StringName, int, StringName);
|
||||
|
||||
public:
|
||||
void set_locale(const String &p_locale);
|
||||
_FORCE_INLINE_ String get_locale() const { return locale; }
|
||||
|
||||
virtual void add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context = "");
|
||||
virtual void add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context = "");
|
||||
virtual StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const; //overridable for other implementations
|
||||
virtual StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const;
|
||||
virtual void erase_message(const StringName &p_src_text, const StringName &p_context = "");
|
||||
virtual void get_message_list(List<StringName> *r_messages) const;
|
||||
virtual int get_message_count() const;
|
||||
virtual Vector<String> get_translated_message_list() const;
|
||||
|
||||
Translation() {}
|
||||
};
|
503
core/string/translation_domain.cpp
Normal file
503
core/string/translation_domain.cpp
Normal file
@@ -0,0 +1,503 @@
|
||||
/**************************************************************************/
|
||||
/* 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 < std::size(_character_to_accented); 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;
|
||||
}
|
||||
|
||||
// Translation objects that could potentially be used for the given locale.
|
||||
HashSet<Ref<Translation>> TranslationDomain::get_potential_translations(const String &p_locale) const {
|
||||
HashSet<Ref<Translation>> res;
|
||||
|
||||
for (const Ref<Translation> &E : translations) {
|
||||
ERR_CONTINUE(E.is_null());
|
||||
|
||||
if (TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale()) > 0) {
|
||||
res.insert(E);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
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 {
|
||||
if (!enabled) {
|
||||
return p_message;
|
||||
}
|
||||
|
||||
const String &locale = locale_override.is_empty() ? TranslationServer::get_singleton()->get_locale() : locale_override;
|
||||
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 {
|
||||
if (!enabled) {
|
||||
return p_n == 1 ? p_message : p_message_plural;
|
||||
}
|
||||
|
||||
const String &locale = locale_override.is_empty() ? TranslationServer::get_singleton()->get_locale() : locale_override;
|
||||
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;
|
||||
}
|
||||
|
||||
String TranslationDomain::get_locale_override() const {
|
||||
return locale_override;
|
||||
}
|
||||
|
||||
void TranslationDomain::set_locale_override(const String &p_locale) {
|
||||
locale_override = p_locale.is_empty() ? p_locale : TranslationServer::get_singleton()->standardize_locale(p_locale);
|
||||
}
|
||||
|
||||
bool TranslationDomain::is_enabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void TranslationDomain::set_enabled(bool p_enabled) {
|
||||
enabled = p_enabled;
|
||||
}
|
||||
|
||||
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("get_locale_override"), &TranslationDomain::get_locale_override);
|
||||
ClassDB::bind_method(D_METHOD("set_locale_override", "locale"), &TranslationDomain::set_locale_override);
|
||||
ClassDB::bind_method(D_METHOD("is_enabled"), &TranslationDomain::is_enabled);
|
||||
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &TranslationDomain::set_enabled);
|
||||
|
||||
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, "enabled"), "set_enabled", "is_enabled");
|
||||
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() {
|
||||
}
|
114
core/string/translation_domain.h
Normal file
114
core/string/translation_domain.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#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 = "]";
|
||||
};
|
||||
|
||||
bool enabled = true;
|
||||
|
||||
String locale_override;
|
||||
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;
|
||||
HashSet<Ref<Translation>> get_potential_translations(const String &p_locale) 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;
|
||||
|
||||
String get_locale_override() const;
|
||||
void set_locale_override(const String &p_locale);
|
||||
|
||||
bool is_enabled() const;
|
||||
void set_enabled(bool p_enabled);
|
||||
|
||||
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();
|
||||
};
|
345
core/string/translation_po.cpp
Normal file
345
core/string/translation_po.cpp
Normal file
@@ -0,0 +1,345 @@
|
||||
/**************************************************************************/
|
||||
/* translation_po.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_po.h"
|
||||
|
||||
#ifdef DEBUG_TRANSLATION_PO
|
||||
#include "core/io/file_access.h"
|
||||
|
||||
void TranslationPO::print_translation_map() {
|
||||
Error err;
|
||||
Ref<FileAccess> file = FileAccess::open("translation_map_print_test.txt", FileAccess::WRITE, &err);
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Failed to open translation_map_print_test.txt");
|
||||
return;
|
||||
}
|
||||
|
||||
file->store_line("NPlural : " + String::num_int64(get_plural_forms()));
|
||||
file->store_line("Plural rule : " + get_plural_rule());
|
||||
file->store_line("");
|
||||
|
||||
List<StringName> context_l;
|
||||
translation_map.get_key_list(&context_l);
|
||||
for (const StringName &ctx : context_l) {
|
||||
file->store_line(" ===== Context: " + String::utf8(String(ctx).utf8()) + " ===== ");
|
||||
const HashMap<StringName, Vector<StringName>> &inner_map = translation_map[ctx];
|
||||
|
||||
List<StringName> id_l;
|
||||
inner_map.get_key_list(&id_l);
|
||||
for (const StringName &id : id_l) {
|
||||
file->store_line("msgid: " + String::utf8(String(id).utf8()));
|
||||
for (int i = 0; i < inner_map[id].size(); i++) {
|
||||
file->store_line("msgstr[" + String::num_int64(i) + "]: " + String::utf8(String(inner_map[id][i]).utf8()));
|
||||
}
|
||||
file->store_line("");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
Dictionary TranslationPO::_get_messages() const {
|
||||
// Return translation_map as a Dictionary.
|
||||
|
||||
Dictionary d;
|
||||
|
||||
for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) {
|
||||
Dictionary d2;
|
||||
|
||||
for (const KeyValue<StringName, Vector<StringName>> &E2 : E.value) {
|
||||
d2[E2.key] = E2.value;
|
||||
}
|
||||
|
||||
d[E.key] = d2;
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void TranslationPO::_set_messages(const Dictionary &p_messages) {
|
||||
// Construct translation_map from a Dictionary.
|
||||
|
||||
for (const KeyValue<Variant, Variant> &kv : p_messages) {
|
||||
const Dictionary &id_str_map = kv.value;
|
||||
|
||||
HashMap<StringName, Vector<StringName>> temp_map;
|
||||
for (const KeyValue<Variant, Variant> &kv_id : id_str_map) {
|
||||
StringName id = kv_id.key;
|
||||
temp_map[id] = kv_id.value;
|
||||
}
|
||||
|
||||
translation_map[kv.key] = temp_map;
|
||||
}
|
||||
}
|
||||
|
||||
Vector<String> TranslationPO::get_translated_message_list() const {
|
||||
Vector<String> msgs;
|
||||
for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) {
|
||||
if (E.key != StringName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const KeyValue<StringName, Vector<StringName>> &E2 : E.value) {
|
||||
for (const StringName &E3 : E2.value) {
|
||||
msgs.push_back(E3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return msgs;
|
||||
}
|
||||
|
||||
Vector<String> TranslationPO::_get_message_list() const {
|
||||
// Return all keys in translation_map.
|
||||
|
||||
List<StringName> msgs;
|
||||
get_message_list(&msgs);
|
||||
|
||||
Vector<String> v;
|
||||
for (const StringName &E : msgs) {
|
||||
v.push_back(E);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
int TranslationPO::_get_plural_index(int p_n) const {
|
||||
// Get a number between [0;number of plural forms).
|
||||
|
||||
input_val.clear();
|
||||
input_val.push_back(p_n);
|
||||
|
||||
return _eq_test(equi_tests, 0);
|
||||
}
|
||||
|
||||
int TranslationPO::_eq_test(const Ref<EQNode> &p_node, const Variant &p_result) const {
|
||||
if (p_node.is_valid()) {
|
||||
Error err = expr->parse(p_node->regex, input_name);
|
||||
ERR_FAIL_COND_V_MSG(err != OK, 0, vformat("Cannot parse expression \"%s\". Error: %s", p_node->regex, expr->get_error_text()));
|
||||
|
||||
Variant result = expr->execute(input_val);
|
||||
ERR_FAIL_COND_V_MSG(expr->has_execute_failed(), 0, vformat("Cannot evaluate expression \"%s\".", p_node->regex));
|
||||
|
||||
if (bool(result)) {
|
||||
return _eq_test(p_node->left, result);
|
||||
} else {
|
||||
return _eq_test(p_node->right, result);
|
||||
}
|
||||
} else {
|
||||
return p_result;
|
||||
}
|
||||
}
|
||||
|
||||
int TranslationPO::_find_unquoted(const String &p_src, char32_t p_chr) const {
|
||||
const int len = p_src.length();
|
||||
if (len == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char32_t *src = p_src.get_data();
|
||||
bool in_quote = false;
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (in_quote) {
|
||||
if (src[i] == ')') {
|
||||
in_quote = false;
|
||||
}
|
||||
} else {
|
||||
if (src[i] == '(') {
|
||||
in_quote = true;
|
||||
} else if (src[i] == p_chr) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TranslationPO::_cache_plural_tests(const String &p_plural_rule, Ref<EQNode> &p_node) {
|
||||
// Some examples of p_plural_rule passed in can have the form:
|
||||
// "n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5" (Arabic)
|
||||
// "n >= 2" (French) // When evaluating the last, especially careful with this one.
|
||||
// "n != 1" (English)
|
||||
|
||||
String rule = p_plural_rule;
|
||||
if (rule.begins_with("(") && rule.ends_with(")")) {
|
||||
int bcount = 0;
|
||||
for (int i = 1; i < rule.length() - 1 && bcount >= 0; i++) {
|
||||
if (rule[i] == '(') {
|
||||
bcount++;
|
||||
} else if (rule[i] == ')') {
|
||||
bcount--;
|
||||
}
|
||||
}
|
||||
if (bcount == 0) {
|
||||
rule = rule.substr(1, rule.length() - 2);
|
||||
}
|
||||
}
|
||||
|
||||
int first_ques_mark = _find_unquoted(rule, '?');
|
||||
int first_colon = _find_unquoted(rule, ':');
|
||||
|
||||
if (first_ques_mark == -1) {
|
||||
p_node->regex = rule.strip_edges();
|
||||
return;
|
||||
}
|
||||
|
||||
p_node->regex = rule.substr(0, first_ques_mark).strip_edges();
|
||||
|
||||
p_node->left.instantiate();
|
||||
_cache_plural_tests(rule.substr(first_ques_mark + 1, first_colon - first_ques_mark - 1).strip_edges(), p_node->left);
|
||||
p_node->right.instantiate();
|
||||
_cache_plural_tests(rule.substr(first_colon + 1).strip_edges(), p_node->right);
|
||||
}
|
||||
|
||||
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_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_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.
|
||||
equi_tests.instantiate();
|
||||
_cache_plural_tests(plural_rule, equi_tests);
|
||||
|
||||
expr.instantiate();
|
||||
input_name.push_back("n");
|
||||
}
|
||||
|
||||
void TranslationPO::add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) {
|
||||
HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
|
||||
|
||||
if (map_id_str.has(p_src_text)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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, 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(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();
|
||||
}
|
||||
|
||||
for (int i = 0; i < p_plural_xlated_texts.size(); i++) {
|
||||
map_id_str[p_src_text].push_back(p_plural_xlated_texts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int TranslationPO::get_plural_forms() const {
|
||||
return plural_forms;
|
||||
}
|
||||
|
||||
String TranslationPO::get_plural_rule() const {
|
||||
return plural_rule;
|
||||
}
|
||||
|
||||
StringName TranslationPO::get_message(const StringName &p_src_text, const StringName &p_context) 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(), 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];
|
||||
}
|
||||
|
||||
StringName TranslationPO::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
|
||||
ERR_FAIL_COND_V_MSG(p_n < 0, StringName(), "N passed into translation to get a plural message should not be negative. For negative numbers, use singular translation please. Search \"gettext PO Plural Forms\" online for the documentation on translating negative numbers.");
|
||||
|
||||
// If the query is the same as last time, return the cached result.
|
||||
if (p_n == last_plural_n && p_context == last_plural_context && p_src_text == last_plural_key) {
|
||||
return translation_map[p_context][p_src_text][last_plural_mapped_index];
|
||||
}
|
||||
|
||||
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(), 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.");
|
||||
|
||||
// Cache result so that if the next entry is the same, we can return directly.
|
||||
// _get_plural_index(p_n) can get very costly, especially when evaluating long plural-rule (Arabic)
|
||||
last_plural_key = p_src_text;
|
||||
last_plural_context = p_context;
|
||||
last_plural_n = p_n;
|
||||
last_plural_mapped_index = plural_index;
|
||||
|
||||
return translation_map[p_context][p_src_text][plural_index];
|
||||
}
|
||||
|
||||
void TranslationPO::erase_message(const StringName &p_src_text, const StringName &p_context) {
|
||||
if (!translation_map.has(p_context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
translation_map[p_context].erase(p_src_text);
|
||||
}
|
||||
|
||||
void TranslationPO::get_message_list(List<StringName> *r_messages) const {
|
||||
// OptimizedTranslation uses this function to get the list of msgid.
|
||||
// Return all the keys of translation_map under "" context.
|
||||
|
||||
for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) {
|
||||
if (E.key != StringName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const KeyValue<StringName, Vector<StringName>> &E2 : E.value) {
|
||||
r_messages->push_back(E2.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int TranslationPO::get_message_count() const {
|
||||
int count = 0;
|
||||
|
||||
for (const KeyValue<StringName, HashMap<StringName, Vector<StringName>>> &E : translation_map) {
|
||||
count += E.value.size();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void TranslationPO::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_plural_forms"), &TranslationPO::get_plural_forms);
|
||||
ClassDB::bind_method(D_METHOD("get_plural_rule"), &TranslationPO::get_plural_rule);
|
||||
}
|
100
core/string/translation_po.h
Normal file
100
core/string/translation_po.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/**************************************************************************/
|
||||
/* translation_po.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
//#define DEBUG_TRANSLATION_PO
|
||||
|
||||
#include "core/math/expression.h"
|
||||
#include "core/string/translation.h"
|
||||
|
||||
class TranslationPO : public Translation {
|
||||
GDCLASS(TranslationPO, Translation);
|
||||
|
||||
// TLDR: Maps context to a list of source strings and translated strings. In PO terms, maps msgctxt to a list of msgid and msgstr.
|
||||
// The first key corresponds to context, and the second key (of the contained HashMap) corresponds to source string.
|
||||
// The value Vector<StringName> in the second map stores the translated strings. Index 0, 1, 2 matches msgstr[0], msgstr[1], msgstr[2]... in the case of plurals.
|
||||
// Otherwise index 0 matches to msgstr in a singular translation.
|
||||
// Strings without context have "" as first key.
|
||||
HashMap<StringName, HashMap<StringName, Vector<StringName>>> translation_map;
|
||||
|
||||
int plural_forms = 0; // 0 means no "Plural-Forms" is given in the PO header file. The min for all languages is 1.
|
||||
String plural_rule;
|
||||
|
||||
// Cache temporary variables related to _get_plural_index() to make it faster
|
||||
class EQNode : public RefCounted {
|
||||
public:
|
||||
String regex;
|
||||
Ref<EQNode> left;
|
||||
Ref<EQNode> right;
|
||||
};
|
||||
Ref<EQNode> equi_tests;
|
||||
|
||||
int _find_unquoted(const String &p_src, char32_t p_chr) const;
|
||||
int _eq_test(const Ref<EQNode> &p_node, const Variant &p_result) const;
|
||||
|
||||
Vector<String> input_name;
|
||||
mutable Ref<Expression> expr;
|
||||
mutable Array input_val;
|
||||
mutable StringName last_plural_key;
|
||||
mutable StringName last_plural_context;
|
||||
mutable int last_plural_n = -1; // Set it to an impossible value at the beginning.
|
||||
mutable int last_plural_mapped_index = 0;
|
||||
|
||||
void _cache_plural_tests(const String &p_plural_rule, Ref<EQNode> &p_node);
|
||||
int _get_plural_index(int p_n) const;
|
||||
|
||||
Vector<String> _get_message_list() const override;
|
||||
Dictionary _get_messages() const override;
|
||||
void _set_messages(const Dictionary &p_messages) override;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
Vector<String> get_translated_message_list() const override;
|
||||
void get_message_list(List<StringName> *r_messages) const override;
|
||||
int get_message_count() const override;
|
||||
void add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context = "") override;
|
||||
void add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context = "") override;
|
||||
StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const override;
|
||||
StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const override;
|
||||
void erase_message(const StringName &p_src_text, const StringName &p_context = "") override;
|
||||
|
||||
void set_plural_rule(const String &p_plural_rule);
|
||||
int get_plural_forms() const;
|
||||
String get_plural_rule() const;
|
||||
|
||||
#ifdef DEBUG_TRANSLATION_PO
|
||||
void print_translation_map();
|
||||
#endif
|
||||
|
||||
TranslationPO() {}
|
||||
};
|
41
core/string/translation_server.compat.inc
Normal file
41
core/string/translation_server.compat.inc
Normal file
@@ -0,0 +1,41 @@
|
||||
/**************************************************************************/
|
||||
/* translation_server.compat.inc */
|
||||
/**************************************************************************/
|
||||
/* 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 DISABLE_DEPRECATED
|
||||
|
||||
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("standardize_locale", "locale"), &TranslationServer::_standardize_locale_bind_compat_98972);
|
||||
}
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
631
core/string/translation_server.cpp
Normal file
631
core/string/translation_server.cpp
Normal file
@@ -0,0 +1,631 @@
|
||||
/**************************************************************************/
|
||||
/* 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"
|
||||
|
||||
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_char('-', '_');
|
||||
|
||||
// Extract locale elements.
|
||||
Vector<String> locale_elements = univ_locale.get_slicec('@', 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_slicec('@', 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;
|
||||
}
|
||||
|
||||
void TranslationServer::set_fallback_locale(const String &p_locale) {
|
||||
fallback = p_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 {
|
||||
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 {
|
||||
return main_domain->translate_plural(p_message, p_message_plural, p_n, p_context);
|
||||
}
|
||||
|
||||
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.has(locale)) {
|
||||
return locale;
|
||||
}
|
||||
return "en";
|
||||
} 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() {
|
||||
const String prop = "internationalization/locale/translations";
|
||||
if (!ProjectSettings::get_singleton()->has_setting(prop)) {
|
||||
return;
|
||||
}
|
||||
const Vector<String> &translations = GLOBAL_GET(prop);
|
||||
for (const String &path : translations) {
|
||||
Ref<Translation> tr = ResourceLoader::load(path);
|
||||
if (tr.is_valid()) {
|
||||
add_translation(tr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
157
core/string/translation_server.h
Normal file
157
core/string/translation_server.h
Normal file
@@ -0,0 +1,157 @@
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#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;
|
||||
|
||||
static inline TranslationServer *singleton = nullptr;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
explicit 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_main_domain() const { return main_domain; }
|
||||
Ref<TranslationDomain> get_editor_domain() const { return editor_domain; }
|
||||
|
||||
void set_locale(const String &p_locale);
|
||||
String get_locale() const;
|
||||
void set_fallback_locale(const String &p_locale);
|
||||
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();
|
||||
};
|
3019
core/string/ucaps.h
Normal file
3019
core/string/ucaps.h
Normal file
File diff suppressed because it is too large
Load Diff
6052
core/string/ustring.cpp
Normal file
6052
core/string/ustring.cpp
Normal file
File diff suppressed because it is too large
Load Diff
793
core/string/ustring.h
Normal file
793
core/string/ustring.h
Normal file
@@ -0,0 +1,793 @@
|
||||
/**************************************************************************/
|
||||
/* ustring.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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Note: _GODOT suffix added to header guard to avoid conflict with ICU header.
|
||||
|
||||
#include "core/string/char_utils.h" // IWYU pragma: export
|
||||
#include "core/templates/cowdata.h"
|
||||
#include "core/templates/vector.h"
|
||||
#include "core/typedefs.h"
|
||||
#include "core/variant/array.h"
|
||||
|
||||
class String;
|
||||
template <typename T>
|
||||
class CharStringT;
|
||||
|
||||
/*************************************************************************/
|
||||
/* 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 *p_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 *>(p_str)));
|
||||
#else
|
||||
// wchar_t is 32-bit
|
||||
return strlen(static_cast<const char32_t *>(static_cast<const void *>(p_str)));
|
||||
#endif
|
||||
}
|
||||
|
||||
// strnlen equivalent function for char16_t * arguments.
|
||||
constexpr size_t strnlen(const char16_t *p_str, size_t p_clip_to_len) {
|
||||
size_t len = 0;
|
||||
while (len < p_clip_to_len && *(p_str++) != 0) {
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// strnlen equivalent function for char32_t * arguments.
|
||||
constexpr size_t strnlen(const char32_t *p_str, size_t p_clip_to_len) {
|
||||
size_t len = 0;
|
||||
while (len < p_clip_to_len && *(p_str++) != 0) {
|
||||
len++;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
// strnlen equivalent function for wchar_t * arguments; depends on the platform.
|
||||
constexpr size_t strnlen(const wchar_t *p_str, size_t p_clip_to_len) {
|
||||
// Use static_cast twice because reinterpret_cast is not allowed in constexpr
|
||||
#ifdef WINDOWS_ENABLED
|
||||
// wchar_t is 16-bit
|
||||
return strnlen(static_cast<const char16_t *>(static_cast<const void *>(p_str)), p_clip_to_len);
|
||||
#else
|
||||
// wchar_t is 32-bit
|
||||
return strnlen(static_cast<const char32_t *>(static_cast<const void *>(p_str)), p_clip_to_len);
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename L, typename R>
|
||||
constexpr int64_t str_compare(const L *l_ptr, const R *r_ptr) {
|
||||
while (true) {
|
||||
const char32_t l = *l_ptr;
|
||||
const char32_t r = *r_ptr;
|
||||
|
||||
if (l == 0 || l != r) {
|
||||
return static_cast<int64_t>(l) - static_cast<int64_t>(r);
|
||||
}
|
||||
|
||||
l_ptr++;
|
||||
r_ptr++;
|
||||
}
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
/* CharProxy */
|
||||
/*************************************************************************/
|
||||
|
||||
template <typename T>
|
||||
class [[nodiscard]] CharProxy {
|
||||
friend String;
|
||||
friend CharStringT<T>;
|
||||
|
||||
const int _index;
|
||||
CowData<T> &_cowdata;
|
||||
static constexpr T _null = 0;
|
||||
|
||||
_FORCE_INLINE_ CharProxy(const int &p_index, CowData<T> &p_cowdata) :
|
||||
_index(p_index),
|
||||
_cowdata(p_cowdata) {}
|
||||
|
||||
public:
|
||||
_FORCE_INLINE_ CharProxy(const CharProxy<T> &p_other) :
|
||||
_index(p_other._index),
|
||||
_cowdata(p_other._cowdata) {}
|
||||
|
||||
_FORCE_INLINE_ operator T() const {
|
||||
if (unlikely(_index == _cowdata.size())) {
|
||||
return _null;
|
||||
}
|
||||
|
||||
return _cowdata.get(_index);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ const T *operator&() const {
|
||||
return _cowdata.ptr() + _index;
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void operator=(const T &p_other) const {
|
||||
_cowdata.set(_index, p_other);
|
||||
}
|
||||
|
||||
_FORCE_INLINE_ void operator=(const CharProxy<T> &p_other) const {
|
||||
_cowdata.set(_index, p_other.operator T());
|
||||
}
|
||||
};
|
||||
|
||||
/*************************************************************************/
|
||||
/* CharStringT */
|
||||
/*************************************************************************/
|
||||
|
||||
template <typename T>
|
||||
class [[nodiscard]] CharStringT {
|
||||
CowData<T> _cowdata;
|
||||
static constexpr T _null = 0;
|
||||
|
||||
public:
|
||||
_FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); }
|
||||
_FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); }
|
||||
_FORCE_INLINE_ const T *get_data() const { return ptr() ? ptr() : &_null; }
|
||||
|
||||
_FORCE_INLINE_ int size() const { return _cowdata.size(); }
|
||||
_FORCE_INLINE_ int length() const { return ptr() ? size() - 1 : 0; }
|
||||
_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
|
||||
|
||||
_FORCE_INLINE_ operator Span<T>() const { return Span(ptr(), length()); }
|
||||
_FORCE_INLINE_ Span<T> span() const { return Span(ptr(), length()); }
|
||||
|
||||
/// Resizes the string. The given size must include the null terminator.
|
||||
/// New characters are not initialized, and should be set by the caller.
|
||||
_FORCE_INLINE_ Error resize_uninitialized(int64_t p_size) { return _cowdata.template resize<false>(p_size); }
|
||||
|
||||
_FORCE_INLINE_ T get(int p_index) const { return _cowdata.get(p_index); }
|
||||
_FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); }
|
||||
_FORCE_INLINE_ const T &operator[](int p_index) const {
|
||||
if (unlikely(p_index == _cowdata.size())) {
|
||||
return _null;
|
||||
}
|
||||
return _cowdata.get(p_index);
|
||||
}
|
||||
_FORCE_INLINE_ CharProxy<T> operator[](int p_index) { return CharProxy<T>(p_index, _cowdata); }
|
||||
|
||||
_FORCE_INLINE_ CharStringT() = default;
|
||||
_FORCE_INLINE_ CharStringT(const CharStringT &p_str) = default;
|
||||
_FORCE_INLINE_ CharStringT(CharStringT &&p_str) = default;
|
||||
_FORCE_INLINE_ void operator=(const CharStringT &p_str) { _cowdata = p_str._cowdata; }
|
||||
_FORCE_INLINE_ void operator=(CharStringT &&p_str) { _cowdata = std::move(p_str._cowdata); }
|
||||
_FORCE_INLINE_ CharStringT(const T *p_cstr) { copy_from(p_cstr); }
|
||||
_FORCE_INLINE_ void operator=(const T *p_cstr) { copy_from(p_cstr); }
|
||||
|
||||
_FORCE_INLINE_ bool operator==(const CharStringT<T> &p_other) const {
|
||||
if (length() != p_other.length()) {
|
||||
return false;
|
||||
}
|
||||
return memcmp(ptr(), p_other.ptr(), length() * sizeof(T)) == 0;
|
||||
}
|
||||
_FORCE_INLINE_ bool operator!=(const CharStringT<T> &p_other) const { return !(*this == p_other); }
|
||||
_FORCE_INLINE_ bool operator<(const CharStringT<T> &p_other) const {
|
||||
if (length() == 0) {
|
||||
return p_other.length() != 0;
|
||||
}
|
||||
return str_compare(get_data(), p_other.get_data()) < 0;
|
||||
}
|
||||
_FORCE_INLINE_ CharStringT<T> &operator+=(T p_char) {
|
||||
const int lhs_len = length();
|
||||
resize_uninitialized(lhs_len + 2);
|
||||
|
||||
T *dst = ptrw();
|
||||
dst[lhs_len] = p_char;
|
||||
dst[lhs_len + 1] = _null;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
protected:
|
||||
void copy_from(const T *p_cstr) {
|
||||
if (!p_cstr) {
|
||||
resize_uninitialized(0);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(p_cstr);
|
||||
if (len == 0) {
|
||||
resize_uninitialized(0);
|
||||
return;
|
||||
}
|
||||
|
||||
Error err = resize_uninitialized(++len); // include terminating null char.
|
||||
|
||||
ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string.");
|
||||
|
||||
memcpy(ptrw(), p_cstr, len * sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct is_zero_constructible<CharStringT<T>> : std::true_type {};
|
||||
|
||||
using CharString = CharStringT<char>;
|
||||
using Char16String = CharStringT<char16_t>;
|
||||
|
||||
/*************************************************************************/
|
||||
/* String */
|
||||
/*************************************************************************/
|
||||
|
||||
class [[nodiscard]] String {
|
||||
CowData<char32_t> _cowdata;
|
||||
static constexpr char32_t _null = 0;
|
||||
static constexpr char32_t _replacement_char = 0xfffd;
|
||||
|
||||
// Known-length copy.
|
||||
void copy_from_unchecked(const char32_t *p_char, int p_length);
|
||||
|
||||
// NULL-terminated c string copy - automatically parse the string to find the length.
|
||||
void append_latin1(const char *p_cstr) {
|
||||
append_latin1(Span(p_cstr, p_cstr ? strlen(p_cstr) : 0));
|
||||
}
|
||||
void append_utf32(const char32_t *p_cstr) {
|
||||
append_utf32(Span(p_cstr, p_cstr ? strlen(p_cstr) : 0));
|
||||
}
|
||||
|
||||
// wchar_t copy_from depends on the platform.
|
||||
void append_wstring(const Span<wchar_t> &p_cstr) {
|
||||
#ifdef WINDOWS_ENABLED
|
||||
// wchar_t is 16-bit, parse as UTF-16
|
||||
append_utf16((const char16_t *)p_cstr.ptr(), p_cstr.size());
|
||||
#else
|
||||
// wchar_t is 32-bit, copy directly
|
||||
append_utf32((Span<char32_t> &)p_cstr);
|
||||
#endif
|
||||
}
|
||||
void append_wstring(const wchar_t *p_cstr) {
|
||||
#ifdef WINDOWS_ENABLED
|
||||
// wchar_t is 16-bit, parse as UTF-16
|
||||
append_utf16((const char16_t *)p_cstr);
|
||||
#else
|
||||
// wchar_t is 32-bit, copy directly
|
||||
append_utf32((const char32_t *)p_cstr);
|
||||
#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;
|
||||
int _count(const char *p_string, int p_from, int p_to, bool p_case_insensitive) const;
|
||||
String _separate_compound_words() const;
|
||||
|
||||
public:
|
||||
enum {
|
||||
npos = -1 ///<for "some" compatibility with std::string (npos is a huge value in std::string)
|
||||
};
|
||||
|
||||
_FORCE_INLINE_ char32_t *ptrw() { return _cowdata.ptrw(); }
|
||||
_FORCE_INLINE_ const char32_t *ptr() const { return _cowdata.ptr(); }
|
||||
_FORCE_INLINE_ const char32_t *get_data() const { return ptr() ? ptr() : &_null; }
|
||||
|
||||
_FORCE_INLINE_ int size() const { return _cowdata.size(); }
|
||||
_FORCE_INLINE_ int length() const { return ptr() ? size() - 1 : 0; }
|
||||
_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
|
||||
|
||||
_FORCE_INLINE_ operator Span<char32_t>() const { return Span(ptr(), length()); }
|
||||
_FORCE_INLINE_ Span<char32_t> span() const { return Span(ptr(), length()); }
|
||||
|
||||
void remove_at(int p_index) { _cowdata.remove_at(p_index); }
|
||||
|
||||
_FORCE_INLINE_ void clear() { resize_uninitialized(0); }
|
||||
|
||||
_FORCE_INLINE_ char32_t get(int p_index) const { return _cowdata.get(p_index); }
|
||||
_FORCE_INLINE_ void set(int p_index, const char32_t &p_elem) { _cowdata.set(p_index, p_elem); }
|
||||
|
||||
/// Resizes the string. The given size must include the null terminator.
|
||||
/// New characters are not initialized, and should be set by the caller.
|
||||
Error resize_uninitialized(int64_t p_size) { return _cowdata.resize<false>(p_size); }
|
||||
|
||||
_FORCE_INLINE_ const char32_t &operator[](int p_index) const {
|
||||
if (unlikely(p_index == _cowdata.size())) {
|
||||
return _null;
|
||||
}
|
||||
|
||||
return _cowdata.get(p_index);
|
||||
}
|
||||
_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;
|
||||
String operator+(const char *p_char) const;
|
||||
String operator+(const wchar_t *p_char) const;
|
||||
String operator+(const char32_t *p_char) const;
|
||||
String operator+(char32_t p_char) const;
|
||||
|
||||
String &operator+=(const String &);
|
||||
String &operator+=(char32_t p_char);
|
||||
String &operator+=(const char *p_str);
|
||||
String &operator+=(const wchar_t *p_str);
|
||||
String &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 Span<char32_t> &p_str_range) const;
|
||||
|
||||
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 char32_t *p_str) const;
|
||||
bool operator<(const char *p_str) const;
|
||||
bool operator<(const wchar_t *p_str) const;
|
||||
|
||||
bool operator<(const String &p_str) const;
|
||||
bool operator<=(const String &p_str) const;
|
||||
bool operator>(const String &p_str) const;
|
||||
bool operator>=(const String &p_str) const;
|
||||
|
||||
signed char casecmp_to(const String &p_str) const;
|
||||
signed char nocasecmp_to(const String &p_str) const;
|
||||
signed char naturalcasecmp_to(const String &p_str) const;
|
||||
signed char naturalnocasecmp_to(const String &p_str) const;
|
||||
// Special sorting for file names. Names starting with `_` are put before all others except those starting with `.`, otherwise natural comparison is used.
|
||||
signed char filecasecmp_to(const String &p_str) const;
|
||||
signed char filenocasecmp_to(const String &p_str) const;
|
||||
|
||||
bool is_valid_string() const;
|
||||
|
||||
/* debug, error messages */
|
||||
void print_unicode_error(const String &p_message, bool p_critical = false) const;
|
||||
|
||||
/* complex helpers */
|
||||
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(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
|
||||
bool match(const String &p_wildcard) const;
|
||||
bool matchn(const String &p_wildcard) const;
|
||||
bool begins_with(const String &p_string) const;
|
||||
bool begins_with(const char *p_string) const;
|
||||
bool ends_with(const String &p_string) const;
|
||||
bool ends_with(const char *p_string) const;
|
||||
bool is_enclosed_in(const String &p_string) const;
|
||||
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;
|
||||
String replace_first(const String &p_key, const String &p_with) const;
|
||||
String replace_first(const char *p_key, const char *p_with) const;
|
||||
String replace(const String &p_key, const String &p_with) const;
|
||||
String replace(const char *p_key, const char *p_with) const;
|
||||
String replace_char(char32_t p_key, char32_t p_with) const;
|
||||
String replace_chars(const String &p_keys, char32_t p_with) const;
|
||||
String replace_chars(const char *p_keys, char32_t p_with) const;
|
||||
String replacen(const String &p_key, const String &p_with) const;
|
||||
String replacen(const char *p_key, const char *p_with) const;
|
||||
String repeat(int p_count) const;
|
||||
String reverse() const;
|
||||
String insert(int p_at_pos, const String &p_string) const;
|
||||
String erase(int p_pos, int p_chars = 1) const;
|
||||
String remove_char(char32_t p_what) const;
|
||||
String remove_chars(const String &p_chars) const;
|
||||
String remove_chars(const char *p_chars) const;
|
||||
String pad_decimals(int p_digits) const;
|
||||
String pad_zeros(int p_digits) const;
|
||||
String trim_prefix(const String &p_prefix) const;
|
||||
String trim_prefix(const char *p_prefix) const;
|
||||
String trim_suffix(const String &p_suffix) const;
|
||||
String trim_suffix(const char *p_suffix) const;
|
||||
String lpad(int min_length, const String &character = " ") const;
|
||||
String rpad(int min_length, const String &character = " ") const;
|
||||
String sprintf(const Array &values, bool *error) const;
|
||||
String quote(const String "echar = "\"") const;
|
||||
String unquote() const;
|
||||
static String num(double p_num, int p_decimals = -1);
|
||||
static String num_scientific(double p_num);
|
||||
static String num_scientific(float 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) {
|
||||
String string;
|
||||
string.append_utf32(Span(&p_char, 1));
|
||||
return string;
|
||||
}
|
||||
static String md5(const uint8_t *p_md5);
|
||||
static String hex_encode_buffer(const uint8_t *p_buffer, int p_len);
|
||||
Vector<uint8_t> hex_decode() const;
|
||||
|
||||
bool is_numeric() const;
|
||||
|
||||
double to_float() const;
|
||||
int64_t hex_to_int() const;
|
||||
int64_t bin_to_int() const;
|
||||
int64_t to_int() const;
|
||||
|
||||
static int64_t to_int(const char *p_str, int p_len = -1);
|
||||
static int64_t to_int(const wchar_t *p_str, int p_len = -1);
|
||||
static int64_t to_int(const char32_t *p_str, int p_len = -1, bool p_clamp = false);
|
||||
|
||||
static double to_float(const char *p_str);
|
||||
static double to_float(const wchar_t *p_str, const wchar_t **r_end = nullptr);
|
||||
static double to_float(const char32_t *p_str, const char32_t **r_end = nullptr);
|
||||
static uint32_t num_characters(int64_t p_int);
|
||||
|
||||
String capitalize() const;
|
||||
String to_camel_case() const;
|
||||
String to_pascal_case() const;
|
||||
String to_snake_case() const;
|
||||
String to_kebab_case() const;
|
||||
|
||||
String get_with_code_lines() const;
|
||||
int get_slice_count(const String &p_splitter) const;
|
||||
int get_slice_count(const char *p_splitter) const;
|
||||
String get_slice(const String &p_splitter, int p_slice) const;
|
||||
String get_slice(const char *p_splitter, int p_slice) const;
|
||||
String get_slicec(char32_t p_splitter, int p_slice) const;
|
||||
|
||||
Vector<String> split(const String &p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
|
||||
Vector<String> split(const char *p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
|
||||
Vector<String> rsplit(const String &p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
|
||||
Vector<String> rsplit(const char *p_splitter = "", bool p_allow_empty = true, int p_maxsplit = 0) const;
|
||||
Vector<String> split_spaces(int p_maxsplit = 0) const;
|
||||
Vector<double> split_floats(const String &p_splitter, bool p_allow_empty = true) const;
|
||||
Vector<float> split_floats_mk(const Vector<String> &p_splitters, bool p_allow_empty = true) const;
|
||||
Vector<int> split_ints(const String &p_splitter, bool p_allow_empty = true) const;
|
||||
Vector<int> split_ints_mk(const Vector<String> &p_splitters, bool p_allow_empty = true) const;
|
||||
|
||||
String join(const Vector<String> &parts) const;
|
||||
|
||||
static char32_t char_uppercase(char32_t p_char);
|
||||
static char32_t char_lowercase(char32_t p_char);
|
||||
String to_upper() const;
|
||||
String to_lower() const;
|
||||
|
||||
int count(const String &p_string, int p_from = 0, int p_to = 0) const;
|
||||
int count(const char *p_string, int p_from = 0, int p_to = 0) const;
|
||||
int countn(const String &p_string, int p_from = 0, int p_to = 0) const;
|
||||
int countn(const char *p_string, int p_from = 0, int p_to = 0) const;
|
||||
|
||||
String left(int p_len) const;
|
||||
String right(int p_len) const;
|
||||
String indent(const String &p_prefix) const;
|
||||
String dedent() const;
|
||||
String strip_edges(bool left = true, bool right = true) const;
|
||||
String strip_escapes() const;
|
||||
String lstrip(const String &p_chars) const;
|
||||
String rstrip(const String &p_chars) const;
|
||||
String get_extension() const;
|
||||
String get_basename() const;
|
||||
String path_join(const String &p_path) const;
|
||||
char32_t unicode_at(int p_idx) const;
|
||||
|
||||
CharString ascii(bool p_allow_extended = false) const;
|
||||
// Parse an ascii string.
|
||||
// If any character is > 127, an error will be logged, and 0xfffd will be inserted.
|
||||
Error append_ascii(const Span<char> &p_range);
|
||||
static String ascii(const Span<char> &p_range) {
|
||||
String s;
|
||||
s.append_ascii(p_range);
|
||||
return s;
|
||||
}
|
||||
CharString latin1() const { return ascii(true); }
|
||||
void append_latin1(const Span<char> &p_cstr);
|
||||
static String latin1(const Span<char> &p_string) {
|
||||
String string;
|
||||
string.append_latin1(p_string);
|
||||
return string;
|
||||
}
|
||||
|
||||
CharString utf8(Vector<uint8_t> *r_ch_length_map = nullptr) const;
|
||||
Error append_utf8(const char *p_utf8, int p_len = -1, bool p_skip_cr = false);
|
||||
Error append_utf8(const Span<char> &p_range, bool p_skip_cr = false) {
|
||||
return append_utf8(p_range.ptr(), p_range.size(), p_skip_cr);
|
||||
}
|
||||
static String utf8(const char *p_utf8, int p_len = -1) {
|
||||
String ret;
|
||||
ret.append_utf8(p_utf8, p_len);
|
||||
return ret;
|
||||
}
|
||||
static String utf8(const Span<char> &p_range) { return utf8(p_range.ptr(), p_range.size()); }
|
||||
|
||||
Char16String utf16() const;
|
||||
Error append_utf16(const char16_t *p_utf16, int p_len = -1, bool p_default_little_endian = true);
|
||||
Error append_utf16(const Span<char16_t> p_range, bool p_skip_cr = false) {
|
||||
return append_utf16(p_range.ptr(), p_range.size(), p_skip_cr);
|
||||
}
|
||||
static String utf16(const char16_t *p_utf16, int p_len = -1) {
|
||||
String ret;
|
||||
ret.append_utf16(p_utf16, p_len);
|
||||
return ret;
|
||||
}
|
||||
static String utf16(const Span<char16_t> &p_range) { return utf16(p_range.ptr(), p_range.size()); }
|
||||
|
||||
void append_utf32(const Span<char32_t> &p_cstr);
|
||||
static String utf32(const Span<char32_t> &p_span) {
|
||||
String string;
|
||||
string.append_utf32(p_span);
|
||||
return string;
|
||||
}
|
||||
|
||||
static uint32_t hash(const char32_t *p_cstr, int p_len); /* hash the string */
|
||||
static uint32_t hash(const char32_t *p_cstr); /* hash the string */
|
||||
static uint32_t hash(const wchar_t *p_cstr, int p_len); /* hash the string */
|
||||
static uint32_t hash(const wchar_t *p_cstr); /* hash the string */
|
||||
static uint32_t hash(const char *p_cstr, int p_len); /* hash the string */
|
||||
static uint32_t hash(const char *p_cstr); /* hash the string */
|
||||
uint32_t hash() const; /* hash the string */
|
||||
uint64_t hash64() const; /* hash the string */
|
||||
String md5_text() const;
|
||||
String sha1_text() const;
|
||||
String sha256_text() const;
|
||||
Vector<uint8_t> md5_buffer() const;
|
||||
Vector<uint8_t> sha1_buffer() const;
|
||||
Vector<uint8_t> sha256_buffer() const;
|
||||
|
||||
_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; }
|
||||
|
||||
// path functions
|
||||
bool is_absolute_path() const;
|
||||
bool is_relative_path() const;
|
||||
bool is_resource_file() const;
|
||||
String path_to(const String &p_path) const;
|
||||
String path_to_file(const String &p_path) const;
|
||||
String get_base_dir() const;
|
||||
String get_file() const;
|
||||
static String humanize_size(uint64_t p_size);
|
||||
String simplify_path() const;
|
||||
bool is_network_share_path() const;
|
||||
|
||||
String xml_escape(bool p_escape_quotes = false) const;
|
||||
String xml_unescape() const;
|
||||
String uri_encode() const;
|
||||
String uri_decode() const;
|
||||
String uri_file_decode() const;
|
||||
String c_escape() const;
|
||||
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, 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_ascii_identifier() const;
|
||||
String validate_unicode_identifier() const;
|
||||
String validate_filename() 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;
|
||||
bool is_valid_html_color() const;
|
||||
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) = default;
|
||||
_FORCE_INLINE_ String(String &&p_str) = default;
|
||||
#ifdef SIZE_EXTRA
|
||||
_NO_INLINE_ ~String() {}
|
||||
#endif
|
||||
_FORCE_INLINE_ void operator=(const String &p_str) { _cowdata = 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;
|
||||
Vector<uint8_t> to_utf16_buffer() const;
|
||||
Vector<uint8_t> to_utf32_buffer() const;
|
||||
Vector<uint8_t> to_wchar_buffer() const;
|
||||
Vector<uint8_t> to_multibyte_char_buffer(const String &p_encoding = String()) const;
|
||||
|
||||
// Constructors for NULL terminated C strings.
|
||||
String(const char *p_cstr) {
|
||||
append_latin1(p_cstr);
|
||||
}
|
||||
String(const wchar_t *p_cstr) {
|
||||
append_wstring(p_cstr);
|
||||
}
|
||||
String(const char32_t *p_cstr) {
|
||||
append_utf32(p_cstr);
|
||||
}
|
||||
|
||||
// Copy assignment for NULL terminated C strings.
|
||||
void operator=(const char *p_cstr) {
|
||||
clear();
|
||||
append_latin1(p_cstr);
|
||||
}
|
||||
void operator=(const wchar_t *p_cstr) {
|
||||
clear();
|
||||
append_wstring(p_cstr);
|
||||
}
|
||||
void operator=(const char32_t *p_cstr) {
|
||||
clear();
|
||||
append_utf32(p_cstr);
|
||||
}
|
||||
};
|
||||
|
||||
// Zero-constructing String initializes _cowdata.ptr() to nullptr and thus empty.
|
||||
template <>
|
||||
struct is_zero_constructible<String> : std::true_type {};
|
||||
|
||||
bool operator==(const char *p_chr, const String &p_str);
|
||||
bool operator==(const wchar_t *p_chr, const String &p_str);
|
||||
bool operator!=(const char *p_chr, const String &p_str);
|
||||
bool operator!=(const wchar_t *p_chr, const String &p_str);
|
||||
|
||||
String operator+(const char *p_chr, const String &p_str);
|
||||
String operator+(const wchar_t *p_chr, const String &p_str);
|
||||
String operator+(char32_t p_chr, const String &p_str);
|
||||
|
||||
String itos(int64_t p_val);
|
||||
String uitos(uint64_t p_val);
|
||||
String rtos(double p_val);
|
||||
String rtoss(double p_val); //scientific version
|
||||
|
||||
struct NoCaseComparator {
|
||||
bool operator()(const String &p_a, const String &p_b) const {
|
||||
return p_a.nocasecmp_to(p_b) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct NaturalNoCaseComparator {
|
||||
bool operator()(const String &p_a, const String &p_b) const {
|
||||
return p_a.naturalnocasecmp_to(p_b) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct FileNoCaseComparator {
|
||||
bool operator()(const String &p_a, const String &p_b) const {
|
||||
return p_a.filenocasecmp_to(p_b) < 0;
|
||||
}
|
||||
};
|
||||
|
||||
/* end of namespace */
|
||||
|
||||
// Tool translate (TTR and variants) for the editor UI,
|
||||
// and doc translate for the class reference (DTR).
|
||||
#ifdef TOOLS_ENABLED
|
||||
// Gets parsed.
|
||||
String TTR(const String &p_text, const String &p_context = "");
|
||||
String TTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
|
||||
String DTR(const String &p_text, const String &p_context = "");
|
||||
String DTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
|
||||
// Use for C strings.
|
||||
#define TTRC(m_value) (m_value)
|
||||
// Use to avoid parsing (for use later with C strings).
|
||||
#define TTRGET(m_value) TTR(m_value)
|
||||
|
||||
#else
|
||||
#define TTRC(m_value) (m_value)
|
||||
#define TTRGET(m_value) (m_value)
|
||||
#endif
|
||||
|
||||
// Use this to mark property names for editor translation.
|
||||
// Often for dynamic properties defined in _get_property_list().
|
||||
// Property names defined directly inside EDITOR_DEF, GLOBAL_DEF, and ADD_PROPERTY macros don't need this.
|
||||
#define PNAME(m_value) (m_value)
|
||||
|
||||
// Similar to PNAME, but to mark groups, i.e. properties with PROPERTY_USAGE_GROUP.
|
||||
// Groups defined directly inside ADD_GROUP macros don't need this.
|
||||
// The arguments are the same as ADD_GROUP. m_prefix is only used for extraction.
|
||||
#define GNAME(m_value, m_prefix) (m_value)
|
||||
|
||||
// Runtime translate for the public node API.
|
||||
String RTR(const String &p_text, const String &p_context = "");
|
||||
String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
|
||||
|
||||
/**
|
||||
* "Extractable TRanslate". Used for strings that can appear inside an exported
|
||||
* project (such as the ones in nodes like `FileDialog`), which are made possible
|
||||
* to add in the POT generator. A translation context can optionally be specified
|
||||
* to disambiguate between identical source strings in translations.
|
||||
* When placeholders are desired, use vformat(ETR("Example: %s"), some_string)`.
|
||||
* If a string mentions a quantity (and may therefore need a dynamic plural form),
|
||||
* use `ETRN()` instead of `ETR()`.
|
||||
*
|
||||
* NOTE: This function is for string extraction only, and will just return the
|
||||
* string it was given. The translation itself should be done internally by nodes
|
||||
* with `atr()` instead.
|
||||
*/
|
||||
_FORCE_INLINE_ String ETR(const String &p_text, const String &p_context = "") {
|
||||
return p_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* "Extractable TRanslate for N items". Used for strings that can appear inside an
|
||||
* exported project (such as the ones in nodes like `FileDialog`), which are made
|
||||
* possible to add in the POT generator. A translation context can optionally be
|
||||
* specified to disambiguate between identical source strings in translations.
|
||||
* Use `ETR()` if the string doesn't need dynamic plural form. When placeholders
|
||||
* are desired, use `vformat(ETRN("%d item", "%d items", some_integer), some_integer)`.
|
||||
* The placeholder must be present in both strings to avoid run-time warnings in `vformat()`.
|
||||
*
|
||||
* NOTE: This function is for string extraction only, and will just return the
|
||||
* string it was given. The translation itself should be done internally by nodes
|
||||
* with `atr()` instead.
|
||||
*/
|
||||
_FORCE_INLINE_ String ETRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "") {
|
||||
if (p_n == 1) {
|
||||
return p_text;
|
||||
}
|
||||
return p_text_plural;
|
||||
}
|
||||
|
||||
template <typename... P>
|
||||
_FORCE_INLINE_ Vector<String> sarray(P... p_args) {
|
||||
return Vector<String>({ String(p_args)... });
|
||||
}
|
Reference in New Issue
Block a user