summaryrefslogtreecommitdiffstats
path: root/core/string
diff options
context:
space:
mode:
Diffstat (limited to 'core/string')
-rw-r--r--core/string/char_range.inc663
-rw-r--r--core/string/char_utils.h6
-rw-r--r--core/string/node_path.cpp10
-rw-r--r--core/string/string_name.cpp9
-rw-r--r--core/string/string_name.h8
-rw-r--r--core/string/translation.compat.inc5
-rw-r--r--core/string/translation.cpp921
-rw-r--r--core/string/translation.h130
-rw-r--r--core/string/translation_po.cpp90
-rw-r--r--core/string/translation_po.h14
-rw-r--r--core/string/translation_server.compat.inc38
-rw-r--r--core/string/translation_server.cpp947
-rw-r--r--core/string/translation_server.h164
-rw-r--r--core/string/ustring.cpp804
-rw-r--r--core/string/ustring.h25
15 files changed, 2636 insertions, 1198 deletions
diff --git a/core/string/char_range.inc b/core/string/char_range.inc
index b7d6bbdb61..2b081b96de 100644
--- a/core/string/char_range.inc
+++ b/core/string/char_range.inc
@@ -2761,4 +2761,667 @@ inline constexpr CharRange lowercase_letter[] = {
{ 0x1e922, 0x1e943 },
};
+inline constexpr CharRange unicode_letter[] = {
+ { 0x41, 0x5a },
+ { 0x61, 0x7a },
+ { 0xaa, 0xaa },
+ { 0xb5, 0xb5 },
+ { 0xba, 0xba },
+ { 0xc0, 0xd6 },
+ { 0xd8, 0xf6 },
+ { 0xf8, 0x2c1 },
+ { 0x2c6, 0x2d1 },
+ { 0x2e0, 0x2e4 },
+ { 0x2ec, 0x2ec },
+ { 0x2ee, 0x2ee },
+ { 0x370, 0x374 },
+ { 0x376, 0x377 },
+ { 0x37a, 0x37d },
+ { 0x37f, 0x37f },
+ { 0x386, 0x386 },
+ { 0x388, 0x38a },
+ { 0x38c, 0x38c },
+ { 0x38e, 0x3a1 },
+ { 0x3a3, 0x3f5 },
+ { 0x3f7, 0x481 },
+ { 0x48a, 0x52f },
+ { 0x531, 0x556 },
+ { 0x559, 0x559 },
+ { 0x560, 0x588 },
+ { 0x5d0, 0x5ea },
+ { 0x5ef, 0x5f2 },
+ { 0x620, 0x64a },
+ { 0x66e, 0x66f },
+ { 0x671, 0x6d3 },
+ { 0x6d5, 0x6d5 },
+ { 0x6e5, 0x6e6 },
+ { 0x6ee, 0x6ef },
+ { 0x6fa, 0x6fc },
+ { 0x6ff, 0x6ff },
+ { 0x710, 0x710 },
+ { 0x712, 0x72f },
+ { 0x74d, 0x7a5 },
+ { 0x7b1, 0x7b1 },
+ { 0x7ca, 0x7ea },
+ { 0x7f4, 0x7f5 },
+ { 0x7fa, 0x7fa },
+ { 0x800, 0x815 },
+ { 0x81a, 0x81a },
+ { 0x824, 0x824 },
+ { 0x828, 0x828 },
+ { 0x840, 0x858 },
+ { 0x860, 0x86a },
+ { 0x870, 0x887 },
+ { 0x889, 0x88e },
+ { 0x8a0, 0x8c9 },
+ { 0x904, 0x939 },
+ { 0x93d, 0x93d },
+ { 0x950, 0x950 },
+ { 0x958, 0x961 },
+ { 0x971, 0x980 },
+ { 0x985, 0x98c },
+ { 0x98f, 0x990 },
+ { 0x993, 0x9a8 },
+ { 0x9aa, 0x9b0 },
+ { 0x9b2, 0x9b2 },
+ { 0x9b6, 0x9b9 },
+ { 0x9bd, 0x9bd },
+ { 0x9ce, 0x9ce },
+ { 0x9dc, 0x9dd },
+ { 0x9df, 0x9e1 },
+ { 0x9f0, 0x9f1 },
+ { 0x9fc, 0x9fc },
+ { 0xa05, 0xa0a },
+ { 0xa0f, 0xa10 },
+ { 0xa13, 0xa28 },
+ { 0xa2a, 0xa30 },
+ { 0xa32, 0xa33 },
+ { 0xa35, 0xa36 },
+ { 0xa38, 0xa39 },
+ { 0xa59, 0xa5c },
+ { 0xa5e, 0xa5e },
+ { 0xa72, 0xa74 },
+ { 0xa85, 0xa8d },
+ { 0xa8f, 0xa91 },
+ { 0xa93, 0xaa8 },
+ { 0xaaa, 0xab0 },
+ { 0xab2, 0xab3 },
+ { 0xab5, 0xab9 },
+ { 0xabd, 0xabd },
+ { 0xad0, 0xad0 },
+ { 0xae0, 0xae1 },
+ { 0xaf9, 0xaf9 },
+ { 0xb05, 0xb0c },
+ { 0xb0f, 0xb10 },
+ { 0xb13, 0xb28 },
+ { 0xb2a, 0xb30 },
+ { 0xb32, 0xb33 },
+ { 0xb35, 0xb39 },
+ { 0xb3d, 0xb3d },
+ { 0xb5c, 0xb5d },
+ { 0xb5f, 0xb61 },
+ { 0xb71, 0xb71 },
+ { 0xb83, 0xb83 },
+ { 0xb85, 0xb8a },
+ { 0xb8e, 0xb90 },
+ { 0xb92, 0xb95 },
+ { 0xb99, 0xb9a },
+ { 0xb9c, 0xb9c },
+ { 0xb9e, 0xb9f },
+ { 0xba3, 0xba4 },
+ { 0xba8, 0xbaa },
+ { 0xbae, 0xbb9 },
+ { 0xbd0, 0xbd0 },
+ { 0xc05, 0xc0c },
+ { 0xc0e, 0xc10 },
+ { 0xc12, 0xc28 },
+ { 0xc2a, 0xc39 },
+ { 0xc3d, 0xc3d },
+ { 0xc58, 0xc5a },
+ { 0xc5d, 0xc5d },
+ { 0xc60, 0xc61 },
+ { 0xc80, 0xc80 },
+ { 0xc85, 0xc8c },
+ { 0xc8e, 0xc90 },
+ { 0xc92, 0xca8 },
+ { 0xcaa, 0xcb3 },
+ { 0xcb5, 0xcb9 },
+ { 0xcbd, 0xcbd },
+ { 0xcdd, 0xcde },
+ { 0xce0, 0xce1 },
+ { 0xcf1, 0xcf2 },
+ { 0xd04, 0xd0c },
+ { 0xd0e, 0xd10 },
+ { 0xd12, 0xd3a },
+ { 0xd3d, 0xd3d },
+ { 0xd4e, 0xd4e },
+ { 0xd54, 0xd56 },
+ { 0xd5f, 0xd61 },
+ { 0xd7a, 0xd7f },
+ { 0xd85, 0xd96 },
+ { 0xd9a, 0xdb1 },
+ { 0xdb3, 0xdbb },
+ { 0xdbd, 0xdbd },
+ { 0xdc0, 0xdc6 },
+ { 0xe01, 0xe30 },
+ { 0xe32, 0xe33 },
+ { 0xe40, 0xe46 },
+ { 0xe81, 0xe82 },
+ { 0xe84, 0xe84 },
+ { 0xe86, 0xe8a },
+ { 0xe8c, 0xea3 },
+ { 0xea5, 0xea5 },
+ { 0xea7, 0xeb0 },
+ { 0xeb2, 0xeb3 },
+ { 0xebd, 0xebd },
+ { 0xec0, 0xec4 },
+ { 0xec6, 0xec6 },
+ { 0xedc, 0xedf },
+ { 0xf00, 0xf00 },
+ { 0xf40, 0xf47 },
+ { 0xf49, 0xf6c },
+ { 0xf88, 0xf8c },
+ { 0x1000, 0x102a },
+ { 0x103f, 0x103f },
+ { 0x1050, 0x1055 },
+ { 0x105a, 0x105d },
+ { 0x1061, 0x1061 },
+ { 0x1065, 0x1066 },
+ { 0x106e, 0x1070 },
+ { 0x1075, 0x1081 },
+ { 0x108e, 0x108e },
+ { 0x10a0, 0x10c5 },
+ { 0x10c7, 0x10c7 },
+ { 0x10cd, 0x10cd },
+ { 0x10d0, 0x10fa },
+ { 0x10fc, 0x1248 },
+ { 0x124a, 0x124d },
+ { 0x1250, 0x1256 },
+ { 0x1258, 0x1258 },
+ { 0x125a, 0x125d },
+ { 0x1260, 0x1288 },
+ { 0x128a, 0x128d },
+ { 0x1290, 0x12b0 },
+ { 0x12b2, 0x12b5 },
+ { 0x12b8, 0x12be },
+ { 0x12c0, 0x12c0 },
+ { 0x12c2, 0x12c5 },
+ { 0x12c8, 0x12d6 },
+ { 0x12d8, 0x1310 },
+ { 0x1312, 0x1315 },
+ { 0x1318, 0x135a },
+ { 0x1380, 0x138f },
+ { 0x13a0, 0x13f5 },
+ { 0x13f8, 0x13fd },
+ { 0x1401, 0x166c },
+ { 0x166f, 0x167f },
+ { 0x1681, 0x169a },
+ { 0x16a0, 0x16ea },
+ { 0x16f1, 0x16f8 },
+ { 0x1700, 0x1711 },
+ { 0x171f, 0x1731 },
+ { 0x1740, 0x1751 },
+ { 0x1760, 0x176c },
+ { 0x176e, 0x1770 },
+ { 0x1780, 0x17b3 },
+ { 0x17d7, 0x17d7 },
+ { 0x17dc, 0x17dc },
+ { 0x1820, 0x1878 },
+ { 0x1880, 0x1884 },
+ { 0x1887, 0x18a8 },
+ { 0x18aa, 0x18aa },
+ { 0x18b0, 0x18f5 },
+ { 0x1900, 0x191e },
+ { 0x1950, 0x196d },
+ { 0x1970, 0x1974 },
+ { 0x1980, 0x19ab },
+ { 0x19b0, 0x19c9 },
+ { 0x1a00, 0x1a16 },
+ { 0x1a20, 0x1a54 },
+ { 0x1aa7, 0x1aa7 },
+ { 0x1b05, 0x1b33 },
+ { 0x1b45, 0x1b4c },
+ { 0x1b83, 0x1ba0 },
+ { 0x1bae, 0x1baf },
+ { 0x1bba, 0x1be5 },
+ { 0x1c00, 0x1c23 },
+ { 0x1c4d, 0x1c4f },
+ { 0x1c5a, 0x1c7d },
+ { 0x1c80, 0x1c88 },
+ { 0x1c90, 0x1cba },
+ { 0x1cbd, 0x1cbf },
+ { 0x1ce9, 0x1cec },
+ { 0x1cee, 0x1cf3 },
+ { 0x1cf5, 0x1cf6 },
+ { 0x1cfa, 0x1cfa },
+ { 0x1d00, 0x1dbf },
+ { 0x1e00, 0x1f15 },
+ { 0x1f18, 0x1f1d },
+ { 0x1f20, 0x1f45 },
+ { 0x1f48, 0x1f4d },
+ { 0x1f50, 0x1f57 },
+ { 0x1f59, 0x1f59 },
+ { 0x1f5b, 0x1f5b },
+ { 0x1f5d, 0x1f5d },
+ { 0x1f5f, 0x1f7d },
+ { 0x1f80, 0x1fb4 },
+ { 0x1fb6, 0x1fbc },
+ { 0x1fbe, 0x1fbe },
+ { 0x1fc2, 0x1fc4 },
+ { 0x1fc6, 0x1fcc },
+ { 0x1fd0, 0x1fd3 },
+ { 0x1fd6, 0x1fdb },
+ { 0x1fe0, 0x1fec },
+ { 0x1ff2, 0x1ff4 },
+ { 0x1ff6, 0x1ffc },
+ { 0x2071, 0x2071 },
+ { 0x207f, 0x207f },
+ { 0x2090, 0x209c },
+ { 0x2102, 0x2102 },
+ { 0x2107, 0x2107 },
+ { 0x210a, 0x2113 },
+ { 0x2115, 0x2115 },
+ { 0x2119, 0x211d },
+ { 0x2124, 0x2124 },
+ { 0x2126, 0x2126 },
+ { 0x2128, 0x2128 },
+ { 0x212a, 0x212d },
+ { 0x212f, 0x2139 },
+ { 0x213c, 0x213f },
+ { 0x2145, 0x2149 },
+ { 0x214e, 0x214e },
+ { 0x2183, 0x2184 },
+ { 0x2c00, 0x2ce4 },
+ { 0x2ceb, 0x2cee },
+ { 0x2cf2, 0x2cf3 },
+ { 0x2d00, 0x2d25 },
+ { 0x2d27, 0x2d27 },
+ { 0x2d2d, 0x2d2d },
+ { 0x2d30, 0x2d67 },
+ { 0x2d6f, 0x2d6f },
+ { 0x2d80, 0x2d96 },
+ { 0x2da0, 0x2da6 },
+ { 0x2da8, 0x2dae },
+ { 0x2db0, 0x2db6 },
+ { 0x2db8, 0x2dbe },
+ { 0x2dc0, 0x2dc6 },
+ { 0x2dc8, 0x2dce },
+ { 0x2dd0, 0x2dd6 },
+ { 0x2dd8, 0x2dde },
+ { 0x2e2f, 0x2e2f },
+ { 0x3005, 0x3006 },
+ { 0x3031, 0x3035 },
+ { 0x303b, 0x303c },
+ { 0x3041, 0x3096 },
+ { 0x309d, 0x309f },
+ { 0x30a1, 0x30fa },
+ { 0x30fc, 0x30ff },
+ { 0x3105, 0x312f },
+ { 0x3131, 0x318e },
+ { 0x31a0, 0x31bf },
+ { 0x31f0, 0x31ff },
+ { 0x3400, 0x4dbf },
+ { 0x4e00, 0xa48c },
+ { 0xa4d0, 0xa4fd },
+ { 0xa500, 0xa60c },
+ { 0xa610, 0xa61f },
+ { 0xa62a, 0xa62b },
+ { 0xa640, 0xa66e },
+ { 0xa67f, 0xa69d },
+ { 0xa6a0, 0xa6e5 },
+ { 0xa717, 0xa71f },
+ { 0xa722, 0xa788 },
+ { 0xa78b, 0xa7ca },
+ { 0xa7d0, 0xa7d1 },
+ { 0xa7d3, 0xa7d3 },
+ { 0xa7d5, 0xa7d9 },
+ { 0xa7f2, 0xa801 },
+ { 0xa803, 0xa805 },
+ { 0xa807, 0xa80a },
+ { 0xa80c, 0xa822 },
+ { 0xa840, 0xa873 },
+ { 0xa882, 0xa8b3 },
+ { 0xa8f2, 0xa8f7 },
+ { 0xa8fb, 0xa8fb },
+ { 0xa8fd, 0xa8fe },
+ { 0xa90a, 0xa925 },
+ { 0xa930, 0xa946 },
+ { 0xa960, 0xa97c },
+ { 0xa984, 0xa9b2 },
+ { 0xa9cf, 0xa9cf },
+ { 0xa9e0, 0xa9e4 },
+ { 0xa9e6, 0xa9ef },
+ { 0xa9fa, 0xa9fe },
+ { 0xaa00, 0xaa28 },
+ { 0xaa40, 0xaa42 },
+ { 0xaa44, 0xaa4b },
+ { 0xaa60, 0xaa76 },
+ { 0xaa7a, 0xaa7a },
+ { 0xaa7e, 0xaaaf },
+ { 0xaab1, 0xaab1 },
+ { 0xaab5, 0xaab6 },
+ { 0xaab9, 0xaabd },
+ { 0xaac0, 0xaac0 },
+ { 0xaac2, 0xaac2 },
+ { 0xaadb, 0xaadd },
+ { 0xaae0, 0xaaea },
+ { 0xaaf2, 0xaaf4 },
+ { 0xab01, 0xab06 },
+ { 0xab09, 0xab0e },
+ { 0xab11, 0xab16 },
+ { 0xab20, 0xab26 },
+ { 0xab28, 0xab2e },
+ { 0xab30, 0xab5a },
+ { 0xab5c, 0xab69 },
+ { 0xab70, 0xabe2 },
+ { 0xac00, 0xd7a3 },
+ { 0xd7b0, 0xd7c6 },
+ { 0xd7cb, 0xd7fb },
+ { 0xf900, 0xfa6d },
+ { 0xfa70, 0xfad9 },
+ { 0xfb00, 0xfb06 },
+ { 0xfb13, 0xfb17 },
+ { 0xfb1d, 0xfb1d },
+ { 0xfb1f, 0xfb28 },
+ { 0xfb2a, 0xfb36 },
+ { 0xfb38, 0xfb3c },
+ { 0xfb3e, 0xfb3e },
+ { 0xfb40, 0xfb41 },
+ { 0xfb43, 0xfb44 },
+ { 0xfb46, 0xfbb1 },
+ { 0xfbd3, 0xfd3d },
+ { 0xfd50, 0xfd8f },
+ { 0xfd92, 0xfdc7 },
+ { 0xfdf0, 0xfdfb },
+ { 0xfe70, 0xfe74 },
+ { 0xfe76, 0xfefc },
+ { 0xff21, 0xff3a },
+ { 0xff41, 0xff5a },
+ { 0xff66, 0xffbe },
+ { 0xffc2, 0xffc7 },
+ { 0xffca, 0xffcf },
+ { 0xffd2, 0xffd7 },
+ { 0xffda, 0xffdc },
+ { 0x10000, 0x1000b },
+ { 0x1000d, 0x10026 },
+ { 0x10028, 0x1003a },
+ { 0x1003c, 0x1003d },
+ { 0x1003f, 0x1004d },
+ { 0x10050, 0x1005d },
+ { 0x10080, 0x100fa },
+ { 0x10280, 0x1029c },
+ { 0x102a0, 0x102d0 },
+ { 0x10300, 0x1031f },
+ { 0x1032d, 0x10340 },
+ { 0x10342, 0x10349 },
+ { 0x10350, 0x10375 },
+ { 0x10380, 0x1039d },
+ { 0x103a0, 0x103c3 },
+ { 0x103c8, 0x103cf },
+ { 0x10400, 0x1049d },
+ { 0x104b0, 0x104d3 },
+ { 0x104d8, 0x104fb },
+ { 0x10500, 0x10527 },
+ { 0x10530, 0x10563 },
+ { 0x10570, 0x1057a },
+ { 0x1057c, 0x1058a },
+ { 0x1058c, 0x10592 },
+ { 0x10594, 0x10595 },
+ { 0x10597, 0x105a1 },
+ { 0x105a3, 0x105b1 },
+ { 0x105b3, 0x105b9 },
+ { 0x105bb, 0x105bc },
+ { 0x10600, 0x10736 },
+ { 0x10740, 0x10755 },
+ { 0x10760, 0x10767 },
+ { 0x10780, 0x10785 },
+ { 0x10787, 0x107b0 },
+ { 0x107b2, 0x107ba },
+ { 0x10800, 0x10805 },
+ { 0x10808, 0x10808 },
+ { 0x1080a, 0x10835 },
+ { 0x10837, 0x10838 },
+ { 0x1083c, 0x1083c },
+ { 0x1083f, 0x10855 },
+ { 0x10860, 0x10876 },
+ { 0x10880, 0x1089e },
+ { 0x108e0, 0x108f2 },
+ { 0x108f4, 0x108f5 },
+ { 0x10900, 0x10915 },
+ { 0x10920, 0x10939 },
+ { 0x10980, 0x109b7 },
+ { 0x109be, 0x109bf },
+ { 0x10a00, 0x10a00 },
+ { 0x10a10, 0x10a13 },
+ { 0x10a15, 0x10a17 },
+ { 0x10a19, 0x10a35 },
+ { 0x10a60, 0x10a7c },
+ { 0x10a80, 0x10a9c },
+ { 0x10ac0, 0x10ac7 },
+ { 0x10ac9, 0x10ae4 },
+ { 0x10b00, 0x10b35 },
+ { 0x10b40, 0x10b55 },
+ { 0x10b60, 0x10b72 },
+ { 0x10b80, 0x10b91 },
+ { 0x10c00, 0x10c48 },
+ { 0x10c80, 0x10cb2 },
+ { 0x10cc0, 0x10cf2 },
+ { 0x10d00, 0x10d23 },
+ { 0x10e80, 0x10ea9 },
+ { 0x10eb0, 0x10eb1 },
+ { 0x10f00, 0x10f1c },
+ { 0x10f27, 0x10f27 },
+ { 0x10f30, 0x10f45 },
+ { 0x10f70, 0x10f81 },
+ { 0x10fb0, 0x10fc4 },
+ { 0x10fe0, 0x10ff6 },
+ { 0x11003, 0x11037 },
+ { 0x11071, 0x11072 },
+ { 0x11075, 0x11075 },
+ { 0x11083, 0x110af },
+ { 0x110d0, 0x110e8 },
+ { 0x11103, 0x11126 },
+ { 0x11144, 0x11144 },
+ { 0x11147, 0x11147 },
+ { 0x11150, 0x11172 },
+ { 0x11176, 0x11176 },
+ { 0x11183, 0x111b2 },
+ { 0x111c1, 0x111c4 },
+ { 0x111da, 0x111da },
+ { 0x111dc, 0x111dc },
+ { 0x11200, 0x11211 },
+ { 0x11213, 0x1122b },
+ { 0x1123f, 0x11240 },
+ { 0x11280, 0x11286 },
+ { 0x11288, 0x11288 },
+ { 0x1128a, 0x1128d },
+ { 0x1128f, 0x1129d },
+ { 0x1129f, 0x112a8 },
+ { 0x112b0, 0x112de },
+ { 0x11305, 0x1130c },
+ { 0x1130f, 0x11310 },
+ { 0x11313, 0x11328 },
+ { 0x1132a, 0x11330 },
+ { 0x11332, 0x11333 },
+ { 0x11335, 0x11339 },
+ { 0x1133d, 0x1133d },
+ { 0x11350, 0x11350 },
+ { 0x1135d, 0x11361 },
+ { 0x11400, 0x11434 },
+ { 0x11447, 0x1144a },
+ { 0x1145f, 0x11461 },
+ { 0x11480, 0x114af },
+ { 0x114c4, 0x114c5 },
+ { 0x114c7, 0x114c7 },
+ { 0x11580, 0x115ae },
+ { 0x115d8, 0x115db },
+ { 0x11600, 0x1162f },
+ { 0x11644, 0x11644 },
+ { 0x11680, 0x116aa },
+ { 0x116b8, 0x116b8 },
+ { 0x11700, 0x1171a },
+ { 0x11740, 0x11746 },
+ { 0x11800, 0x1182b },
+ { 0x118a0, 0x118df },
+ { 0x118ff, 0x11906 },
+ { 0x11909, 0x11909 },
+ { 0x1190c, 0x11913 },
+ { 0x11915, 0x11916 },
+ { 0x11918, 0x1192f },
+ { 0x1193f, 0x1193f },
+ { 0x11941, 0x11941 },
+ { 0x119a0, 0x119a7 },
+ { 0x119aa, 0x119d0 },
+ { 0x119e1, 0x119e1 },
+ { 0x119e3, 0x119e3 },
+ { 0x11a00, 0x11a00 },
+ { 0x11a0b, 0x11a32 },
+ { 0x11a3a, 0x11a3a },
+ { 0x11a50, 0x11a50 },
+ { 0x11a5c, 0x11a89 },
+ { 0x11a9d, 0x11a9d },
+ { 0x11ab0, 0x11af8 },
+ { 0x11c00, 0x11c08 },
+ { 0x11c0a, 0x11c2e },
+ { 0x11c40, 0x11c40 },
+ { 0x11c72, 0x11c8f },
+ { 0x11d00, 0x11d06 },
+ { 0x11d08, 0x11d09 },
+ { 0x11d0b, 0x11d30 },
+ { 0x11d46, 0x11d46 },
+ { 0x11d60, 0x11d65 },
+ { 0x11d67, 0x11d68 },
+ { 0x11d6a, 0x11d89 },
+ { 0x11d98, 0x11d98 },
+ { 0x11ee0, 0x11ef2 },
+ { 0x11f02, 0x11f02 },
+ { 0x11f04, 0x11f10 },
+ { 0x11f12, 0x11f33 },
+ { 0x11fb0, 0x11fb0 },
+ { 0x12000, 0x12399 },
+ { 0x12480, 0x12543 },
+ { 0x12f90, 0x12ff0 },
+ { 0x13000, 0x1342f },
+ { 0x13441, 0x13446 },
+ { 0x14400, 0x14646 },
+ { 0x16800, 0x16a38 },
+ { 0x16a40, 0x16a5e },
+ { 0x16a70, 0x16abe },
+ { 0x16ad0, 0x16aed },
+ { 0x16b00, 0x16b2f },
+ { 0x16b40, 0x16b43 },
+ { 0x16b63, 0x16b77 },
+ { 0x16b7d, 0x16b8f },
+ { 0x16e40, 0x16e7f },
+ { 0x16f00, 0x16f4a },
+ { 0x16f50, 0x16f50 },
+ { 0x16f93, 0x16f9f },
+ { 0x16fe0, 0x16fe1 },
+ { 0x16fe3, 0x16fe3 },
+ { 0x17000, 0x187f7 },
+ { 0x18800, 0x18cd5 },
+ { 0x18d00, 0x18d08 },
+ { 0x1aff0, 0x1aff3 },
+ { 0x1aff5, 0x1affb },
+ { 0x1affd, 0x1affe },
+ { 0x1b000, 0x1b122 },
+ { 0x1b132, 0x1b132 },
+ { 0x1b150, 0x1b152 },
+ { 0x1b155, 0x1b155 },
+ { 0x1b164, 0x1b167 },
+ { 0x1b170, 0x1b2fb },
+ { 0x1bc00, 0x1bc6a },
+ { 0x1bc70, 0x1bc7c },
+ { 0x1bc80, 0x1bc88 },
+ { 0x1bc90, 0x1bc99 },
+ { 0x1d400, 0x1d454 },
+ { 0x1d456, 0x1d49c },
+ { 0x1d49e, 0x1d49f },
+ { 0x1d4a2, 0x1d4a2 },
+ { 0x1d4a5, 0x1d4a6 },
+ { 0x1d4a9, 0x1d4ac },
+ { 0x1d4ae, 0x1d4b9 },
+ { 0x1d4bb, 0x1d4bb },
+ { 0x1d4bd, 0x1d4c3 },
+ { 0x1d4c5, 0x1d505 },
+ { 0x1d507, 0x1d50a },
+ { 0x1d50d, 0x1d514 },
+ { 0x1d516, 0x1d51c },
+ { 0x1d51e, 0x1d539 },
+ { 0x1d53b, 0x1d53e },
+ { 0x1d540, 0x1d544 },
+ { 0x1d546, 0x1d546 },
+ { 0x1d54a, 0x1d550 },
+ { 0x1d552, 0x1d6a5 },
+ { 0x1d6a8, 0x1d6c0 },
+ { 0x1d6c2, 0x1d6da },
+ { 0x1d6dc, 0x1d6fa },
+ { 0x1d6fc, 0x1d714 },
+ { 0x1d716, 0x1d734 },
+ { 0x1d736, 0x1d74e },
+ { 0x1d750, 0x1d76e },
+ { 0x1d770, 0x1d788 },
+ { 0x1d78a, 0x1d7a8 },
+ { 0x1d7aa, 0x1d7c2 },
+ { 0x1d7c4, 0x1d7cb },
+ { 0x1df00, 0x1df1e },
+ { 0x1df25, 0x1df2a },
+ { 0x1e030, 0x1e06d },
+ { 0x1e100, 0x1e12c },
+ { 0x1e137, 0x1e13d },
+ { 0x1e14e, 0x1e14e },
+ { 0x1e290, 0x1e2ad },
+ { 0x1e2c0, 0x1e2eb },
+ { 0x1e4d0, 0x1e4eb },
+ { 0x1e7e0, 0x1e7e6 },
+ { 0x1e7e8, 0x1e7eb },
+ { 0x1e7ed, 0x1e7ee },
+ { 0x1e7f0, 0x1e7fe },
+ { 0x1e800, 0x1e8c4 },
+ { 0x1e900, 0x1e943 },
+ { 0x1e94b, 0x1e94b },
+ { 0x1ee00, 0x1ee03 },
+ { 0x1ee05, 0x1ee1f },
+ { 0x1ee21, 0x1ee22 },
+ { 0x1ee24, 0x1ee24 },
+ { 0x1ee27, 0x1ee27 },
+ { 0x1ee29, 0x1ee32 },
+ { 0x1ee34, 0x1ee37 },
+ { 0x1ee39, 0x1ee39 },
+ { 0x1ee3b, 0x1ee3b },
+ { 0x1ee42, 0x1ee42 },
+ { 0x1ee47, 0x1ee47 },
+ { 0x1ee49, 0x1ee49 },
+ { 0x1ee4b, 0x1ee4b },
+ { 0x1ee4d, 0x1ee4f },
+ { 0x1ee51, 0x1ee52 },
+ { 0x1ee54, 0x1ee54 },
+ { 0x1ee57, 0x1ee57 },
+ { 0x1ee59, 0x1ee59 },
+ { 0x1ee5b, 0x1ee5b },
+ { 0x1ee5d, 0x1ee5d },
+ { 0x1ee5f, 0x1ee5f },
+ { 0x1ee61, 0x1ee62 },
+ { 0x1ee64, 0x1ee64 },
+ { 0x1ee67, 0x1ee6a },
+ { 0x1ee6c, 0x1ee72 },
+ { 0x1ee74, 0x1ee77 },
+ { 0x1ee79, 0x1ee7c },
+ { 0x1ee7e, 0x1ee7e },
+ { 0x1ee80, 0x1ee89 },
+ { 0x1ee8b, 0x1ee9b },
+ { 0x1eea1, 0x1eea3 },
+ { 0x1eea5, 0x1eea9 },
+ { 0x1eeab, 0x1eebb },
+ { 0x20000, 0x2a6df },
+ { 0x2a700, 0x2b739 },
+ { 0x2b740, 0x2b81d },
+ { 0x2b820, 0x2cea1 },
+ { 0x2ceb0, 0x2ebe0 },
+ { 0x2ebf0, 0x2ee5d },
+ { 0x2f800, 0x2fa1d },
+ { 0x30000, 0x3134a },
+ { 0x31350, 0x323af },
+};
+
#endif // CHAR_RANGE_INC
diff --git a/core/string/char_utils.h b/core/string/char_utils.h
index aa9bc198ca..4acb81253f 100644
--- a/core/string/char_utils.h
+++ b/core/string/char_utils.h
@@ -70,6 +70,10 @@ static _FORCE_INLINE_ bool is_unicode_lower_case(char32_t c) {
BSEARCH_CHAR_RANGE(lowercase_letter);
}
+static _FORCE_INLINE_ bool is_unicode_letter(char32_t c) {
+ BSEARCH_CHAR_RANGE(unicode_letter);
+}
+
#undef BSEARCH_CHAR_RANGE
static _FORCE_INLINE_ bool is_ascii_upper_case(char32_t c) {
@@ -92,7 +96,7 @@ static _FORCE_INLINE_ bool is_binary_digit(char32_t c) {
return (c == '0' || c == '1');
}
-static _FORCE_INLINE_ bool is_ascii_char(char32_t c) {
+static _FORCE_INLINE_ bool is_ascii_alphabet_char(char32_t c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
diff --git a/core/string/node_path.cpp b/core/string/node_path.cpp
index 8ae2efb787..fdc72bc8dc 100644
--- a/core/string/node_path.cpp
+++ b/core/string/node_path.cpp
@@ -215,7 +215,10 @@ StringName NodePath::get_concatenated_names() const {
String concatenated;
const StringName *sn = data->path.ptr();
for (int i = 0; i < pc; i++) {
- concatenated += i == 0 ? sn[i].operator String() : "/" + sn[i];
+ if (i > 0) {
+ concatenated += "/";
+ }
+ concatenated += sn[i].operator String();
}
data->concatenated_path = concatenated;
}
@@ -230,7 +233,10 @@ StringName NodePath::get_concatenated_subnames() const {
String concatenated;
const StringName *ssn = data->subpath.ptr();
for (int i = 0; i < spc; i++) {
- concatenated += i == 0 ? ssn[i].operator String() : ":" + ssn[i];
+ if (i > 0) {
+ concatenated += ":";
+ }
+ concatenated += ssn[i].operator String();
}
data->concatenated_subpath = concatenated;
}
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 658297d805..5d59d65f92 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -39,19 +39,10 @@ StaticCString StaticCString::create(const char *p_ptr) {
return scs;
}
-StringName::_Data *StringName::_table[STRING_TABLE_LEN];
-
StringName _scs_create(const char *p_chr, bool p_static) {
return (p_chr[0] ? StringName(StaticCString::create(p_chr), p_static) : StringName());
}
-bool StringName::configured = false;
-Mutex StringName::mutex;
-
-#ifdef DEBUG_ENABLED
-bool StringName::debug_stringname = false;
-#endif
-
void StringName::setup() {
ERR_FAIL_COND(configured);
for (int i = 0; i < STRING_TABLE_LEN; i++) {
diff --git a/core/string/string_name.h b/core/string/string_name.h
index 89b4c07e0e..0eb98cf64b 100644
--- a/core/string/string_name.h
+++ b/core/string/string_name.h
@@ -67,7 +67,7 @@ class StringName {
_Data() {}
};
- static _Data *_table[STRING_TABLE_LEN];
+ static inline _Data *_table[STRING_TABLE_LEN];
_Data *_data = nullptr;
@@ -75,10 +75,10 @@ class StringName {
friend void register_core_types();
friend void unregister_core_types();
friend class Main;
- static Mutex mutex;
+ static inline Mutex mutex;
static void setup();
static void cleanup();
- static bool configured;
+ static inline bool configured = false;
#ifdef DEBUG_ENABLED
struct DebugSortReferences {
bool operator()(const _Data *p_left, const _Data *p_right) const {
@@ -86,7 +86,7 @@ class StringName {
}
};
- static bool debug_stringname;
+ static inline bool debug_stringname = false;
#endif
StringName(_Data *p_data) { _data = p_data; }
diff --git a/core/string/translation.compat.inc b/core/string/translation.compat.inc
index d792d4a6fc..68bd1831e4 100644
--- a/core/string/translation.compat.inc
+++ b/core/string/translation.compat.inc
@@ -38,9 +38,4 @@ void Translation::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL(""));
}
-void TranslationServer::_bind_compatibility_methods() {
- ClassDB::bind_compatibility_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(""));
- ClassDB::bind_compatibility_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(""));
-}
-
#endif
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 613edd11cd..33d4a1bcde 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -31,14 +31,9 @@
#include "translation.h"
#include "translation.compat.inc"
-#include "core/config/project_settings.h"
-#include "core/io/resource_loader.h"
#include "core/os/os.h"
-#include "core/string/locales.h"
-
-#ifdef TOOLS_ENABLED
-#include "main/main.h"
-#endif
+#include "core/os/thread.h"
+#include "core/string/translation_server.h"
Dictionary Translation::_get_messages() const {
Dictionary d;
@@ -173,915 +168,3 @@ void Translation::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale");
}
-
-///////////////////////////////////////////////
-
-struct _character_accent_pair {
- const char32_t character;
- const char32_t *accented_character;
-};
-
-static _character_accent_pair _character_to_accented[] = {
- { 'A', U"Å" },
- { 'B', U"ß" },
- { 'C', U"Ç" },
- { 'D', U"Ð" },
- { 'E', U"É" },
- { 'F', U"F́" },
- { 'G', U"Ĝ" },
- { 'H', U"Ĥ" },
- { 'I', U"Ĩ" },
- { 'J', U"Ĵ" },
- { 'K', U"ĸ" },
- { 'L', U"Ł" },
- { 'M', U"Ḿ" },
- { 'N', U"й" },
- { 'O', U"Ö" },
- { 'P', U"Ṕ" },
- { 'Q', U"Q́" },
- { 'R', U"Ř" },
- { 'S', U"Ŝ" },
- { 'T', U"Ŧ" },
- { 'U', U"Ũ" },
- { 'V', U"Ṽ" },
- { 'W', U"Ŵ" },
- { 'X', U"X́" },
- { 'Y', U"Ÿ" },
- { 'Z', U"Ž" },
- { 'a', U"á" },
- { 'b', U"ḅ" },
- { 'c', U"ć" },
- { 'd', U"d́" },
- { 'e', U"é" },
- { 'f', U"f́" },
- { 'g', U"ǵ" },
- { 'h', U"h̀" },
- { 'i', U"í" },
- { 'j', U"ǰ" },
- { 'k', U"ḱ" },
- { 'l', U"ł" },
- { 'm', U"m̀" },
- { 'n', U"ή" },
- { 'o', U"ô" },
- { 'p', U"ṕ" },
- { 'q', U"q́" },
- { 'r', U"ŕ" },
- { 's', U"š" },
- { 't', U"ŧ" },
- { 'u', U"ü" },
- { 'v', U"ṽ" },
- { 'w', U"ŵ" },
- { 'x', U"x́" },
- { 'y', U"ý" },
- { 'z', U"ź" },
-};
-
-Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
-
-HashMap<String, String> TranslationServer::language_map;
-HashMap<String, String> TranslationServer::script_map;
-HashMap<String, String> TranslationServer::locale_rename_map;
-HashMap<String, String> TranslationServer::country_name_map;
-HashMap<String, String> TranslationServer::variant_map;
-HashMap<String, String> TranslationServer::country_rename_map;
-
-void TranslationServer::init_locale_info() {
- // Init locale info.
- language_map.clear();
- int idx = 0;
- while (language_list[idx][0] != nullptr) {
- language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]);
- idx++;
- }
-
- // Init locale-script map.
- locale_script_info.clear();
- idx = 0;
- while (locale_scripts[idx][0] != nullptr) {
- LocaleScriptInfo info;
- info.name = locale_scripts[idx][0];
- info.script = locale_scripts[idx][1];
- info.default_country = locale_scripts[idx][2];
- Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false);
- for (int i = 0; i < supported_countries.size(); i++) {
- info.supported_countries.insert(supported_countries[i]);
- }
- locale_script_info.push_back(info);
- idx++;
- }
-
- // Init supported script list.
- script_map.clear();
- idx = 0;
- while (script_list[idx][0] != nullptr) {
- script_map[script_list[idx][1]] = String::utf8(script_list[idx][0]);
- idx++;
- }
-
- // Init regional variant map.
- variant_map.clear();
- idx = 0;
- while (locale_variants[idx][0] != nullptr) {
- variant_map[locale_variants[idx][0]] = locale_variants[idx][1];
- idx++;
- }
-
- // Init locale renames.
- locale_rename_map.clear();
- idx = 0;
- while (locale_renames[idx][0] != nullptr) {
- if (!String(locale_renames[idx][1]).is_empty()) {
- locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1];
- }
- idx++;
- }
-
- // Init country names.
- country_name_map.clear();
- idx = 0;
- while (country_names[idx][0] != nullptr) {
- country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]);
- idx++;
- }
-
- // Init country renames.
- country_rename_map.clear();
- idx = 0;
- while (country_renames[idx][0] != nullptr) {
- if (!String(country_renames[idx][1]).is_empty()) {
- country_rename_map[country_renames[idx][0]] = country_renames[idx][1];
- }
- idx++;
- }
-}
-
-String TranslationServer::standardize_locale(const String &p_locale) const {
- return _standardize_locale(p_locale, false);
-}
-
-String TranslationServer::_standardize_locale(const String &p_locale, bool p_add_defaults) const {
- // Replaces '-' with '_' for macOS style locales.
- String univ_locale = p_locale.replace("-", "_");
-
- // Extract locale elements.
- String lang_name, script_name, country_name, variant_name;
- Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_");
- lang_name = locale_elements[0];
- if (locale_elements.size() >= 2) {
- if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
- script_name = locale_elements[1];
- }
- if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
- country_name = locale_elements[1];
- }
- }
- if (locale_elements.size() >= 3) {
- if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
- country_name = locale_elements[2];
- } else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang_name) {
- variant_name = locale_elements[2].to_lower();
- }
- }
- if (locale_elements.size() >= 4) {
- if (variant_map.has(locale_elements[3].to_lower()) && variant_map[locale_elements[3].to_lower()] == lang_name) {
- variant_name = locale_elements[3].to_lower();
- }
- }
-
- // Try extract script and variant from the extra part.
- Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";");
- for (int i = 0; i < script_extra.size(); i++) {
- if (script_extra[i].to_lower() == "cyrillic") {
- script_name = "Cyrl";
- break;
- } else if (script_extra[i].to_lower() == "latin") {
- script_name = "Latn";
- break;
- } else if (script_extra[i].to_lower() == "devanagari") {
- script_name = "Deva";
- break;
- } else if (variant_map.has(script_extra[i].to_lower()) && variant_map[script_extra[i].to_lower()] == lang_name) {
- variant_name = script_extra[i].to_lower();
- }
- }
-
- // Handles known non-ISO language names used e.g. on Windows.
- if (locale_rename_map.has(lang_name)) {
- lang_name = locale_rename_map[lang_name];
- }
-
- // Handle country renames.
- if (country_rename_map.has(country_name)) {
- country_name = country_rename_map[country_name];
- }
-
- // Remove unsupported script codes.
- if (!script_map.has(script_name)) {
- script_name = "";
- }
-
- // Add script code base on language and country codes for some ambiguous cases.
- if (p_add_defaults) {
- if (script_name.is_empty()) {
- for (int i = 0; i < locale_script_info.size(); i++) {
- const LocaleScriptInfo &info = locale_script_info[i];
- if (info.name == lang_name) {
- if (country_name.is_empty() || info.supported_countries.has(country_name)) {
- script_name = info.script;
- break;
- }
- }
- }
- }
- if (!script_name.is_empty() && country_name.is_empty()) {
- // Add conntry code based on script for some ambiguous cases.
- for (int i = 0; i < locale_script_info.size(); i++) {
- const LocaleScriptInfo &info = locale_script_info[i];
- if (info.name == lang_name && info.script == script_name) {
- country_name = info.default_country;
- break;
- }
- }
- }
- }
-
- // Combine results.
- String out = lang_name;
- if (!script_name.is_empty()) {
- out = out + "_" + script_name;
- }
- if (!country_name.is_empty()) {
- out = out + "_" + country_name;
- }
- if (!variant_name.is_empty()) {
- out = out + "_" + variant_name;
- }
- return out;
-}
-
-int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
- String locale_a = _standardize_locale(p_locale_a, true);
- String locale_b = _standardize_locale(p_locale_b, true);
-
- if (locale_a == locale_b) {
- // Exact match.
- return 10;
- }
-
- Vector<String> locale_a_elements = locale_a.split("_");
- Vector<String> locale_b_elements = locale_b.split("_");
- if (locale_a_elements[0] == locale_b_elements[0]) {
- // Matching language, both locales have extra parts.
- // Return number of matching elements.
- int matching_elements = 1;
- for (int i = 1; i < locale_a_elements.size(); i++) {
- for (int j = 1; j < locale_b_elements.size(); j++) {
- if (locale_a_elements[i] == locale_b_elements[j]) {
- matching_elements++;
- }
- }
- }
- return matching_elements;
- } else {
- // No match.
- return 0;
- }
-}
-
-String TranslationServer::get_locale_name(const String &p_locale) const {
- String lang_name, script_name, country_name;
- Vector<String> locale_elements = standardize_locale(p_locale).split("_");
- lang_name = locale_elements[0];
- if (locale_elements.size() >= 2) {
- if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
- script_name = locale_elements[1];
- }
- if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
- country_name = locale_elements[1];
- }
- }
- if (locale_elements.size() >= 3) {
- if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
- country_name = locale_elements[2];
- }
- }
-
- String name = language_map[lang_name];
- if (!script_name.is_empty()) {
- name = name + " (" + script_map[script_name] + ")";
- }
- if (!country_name.is_empty()) {
- name = name + ", " + country_name_map[country_name];
- }
- return name;
-}
-
-Vector<String> TranslationServer::get_all_languages() const {
- Vector<String> languages;
-
- for (const KeyValue<String, String> &E : language_map) {
- languages.push_back(E.key);
- }
-
- return languages;
-}
-
-String TranslationServer::get_language_name(const String &p_language) const {
- return language_map[p_language];
-}
-
-Vector<String> TranslationServer::get_all_scripts() const {
- Vector<String> scripts;
-
- for (const KeyValue<String, String> &E : script_map) {
- scripts.push_back(E.key);
- }
-
- return scripts;
-}
-
-String TranslationServer::get_script_name(const String &p_script) const {
- return script_map[p_script];
-}
-
-Vector<String> TranslationServer::get_all_countries() const {
- Vector<String> countries;
-
- for (const KeyValue<String, String> &E : country_name_map) {
- countries.push_back(E.key);
- }
-
- return countries;
-}
-
-String TranslationServer::get_country_name(const String &p_country) const {
- return country_name_map[p_country];
-}
-
-void TranslationServer::set_locale(const String &p_locale) {
- String new_locale = standardize_locale(p_locale);
- if (locale == new_locale) {
- return;
- }
-
- locale = new_locale;
- ResourceLoader::reload_translation_remaps();
-
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
- }
-}
-
-String TranslationServer::get_locale() const {
- return locale;
-}
-
-PackedStringArray TranslationServer::get_loaded_locales() const {
- PackedStringArray locales;
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), PackedStringArray());
- String l = t->get_locale();
-
- locales.push_back(l);
- }
-
- return locales;
-}
-
-void TranslationServer::add_translation(const Ref<Translation> &p_translation) {
- translations.insert(p_translation);
-}
-
-void TranslationServer::remove_translation(const Ref<Translation> &p_translation) {
- translations.erase(p_translation);
-}
-
-Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
- Ref<Translation> res;
- int best_score = 0;
-
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), nullptr);
- String l = t->get_locale();
-
- int score = compare_locales(p_locale, l);
- if (score > 0 && score >= best_score) {
- res = t;
- best_score = score;
- if (score == 10) {
- break; // Exact match, skip the rest.
- }
- }
- }
- return res;
-}
-
-void TranslationServer::clear() {
- translations.clear();
-}
-
-StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const {
- // Match given message against the translation catalog for the project locale.
-
- if (!enabled) {
- return p_message;
- }
-
- StringName res = _get_message_from_translations(p_message, p_context, locale, false);
-
- if (!res && fallback.length() >= 2) {
- res = _get_message_from_translations(p_message, p_context, fallback, false);
- }
-
- if (!res) {
- return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message;
- }
-
- return pseudolocalization_enabled ? pseudolocalize(res) : res;
-}
-
-StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (!enabled) {
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
- }
-
- StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n);
-
- if (!res && fallback.length() >= 2) {
- res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n);
- }
-
- if (!res) {
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
- }
-
- return res;
-}
-
-StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const {
- StringName res;
- int best_score = 0;
-
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), p_message);
- String l = t->get_locale();
-
- int score = compare_locales(p_locale, l);
- if (score > 0 && score >= best_score) {
- StringName r;
- if (!plural) {
- r = t->get_message(p_message, p_context);
- } else {
- r = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
- }
- if (!r) {
- continue;
- }
- res = r;
- best_score = score;
- if (score == 10) {
- break; // Exact match, skip the rest.
- }
- }
- }
-
- return res;
-}
-
-TranslationServer *TranslationServer::singleton = nullptr;
-
-bool TranslationServer::_load_translations(const String &p_from) {
- if (ProjectSettings::get_singleton()->has_setting(p_from)) {
- const Vector<String> &translation_names = GLOBAL_GET(p_from);
-
- int tcount = translation_names.size();
-
- if (tcount) {
- const String *r = translation_names.ptr();
-
- for (int i = 0; i < tcount; i++) {
- Ref<Translation> tr = ResourceLoader::load(r[i]);
- if (tr.is_valid()) {
- add_translation(tr);
- }
- }
- }
- return true;
- }
-
- return false;
-}
-
-void TranslationServer::setup() {
- String test = GLOBAL_DEF("internationalization/locale/test", "");
- test = test.strip_edges();
- if (!test.is_empty()) {
- set_locale(test);
- } else {
- set_locale(OS::get_singleton()->get_locale());
- }
-
- fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
- pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
- pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
- pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false);
- pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false);
- pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false);
- expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
- pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
- pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
- pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
-
-#ifdef TOOLS_ENABLED
- ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
-#endif
-}
-
-void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) {
- tool_translation = p_translation;
-}
-
-Ref<Translation> TranslationServer::get_tool_translation() const {
- return tool_translation;
-}
-
-String TranslationServer::get_tool_locale() {
-#ifdef TOOLS_ENABLED
- if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
- if (TranslationServer::get_singleton()->get_tool_translation().is_valid()) {
- return tool_translation->get_locale();
- } else {
- return "en";
- }
- } else {
-#else
- {
-#endif
- // Look for best matching loaded translation.
- String best_locale = "en";
- int best_score = 0;
-
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), best_locale);
- String l = t->get_locale();
-
- int score = compare_locales(locale, l);
- if (score > 0 && score >= best_score) {
- best_locale = l;
- best_score = score;
- if (score == 10) {
- break; // Exact match, skip the rest.
- }
- }
- }
- return best_locale;
- }
-}
-
-StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
- if (tool_translation.is_valid()) {
- StringName r = tool_translation->get_message(p_message, p_context);
- if (r) {
- return editor_pseudolocalization ? tool_pseudolocalize(r) : r;
- }
- }
- return editor_pseudolocalization ? tool_pseudolocalize(p_message) : p_message;
-}
-
-StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (tool_translation.is_valid()) {
- StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
- if (r) {
- return r;
- }
- }
-
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
-}
-
-void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) {
- property_translation = p_translation;
-}
-
-StringName TranslationServer::property_translate(const StringName &p_message, const StringName &p_context) const {
- if (property_translation.is_valid()) {
- StringName r = property_translation->get_message(p_message, p_context);
- if (r) {
- return r;
- }
- }
- return p_message;
-}
-
-void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
- doc_translation = p_translation;
-}
-
-StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const {
- if (doc_translation.is_valid()) {
- StringName r = doc_translation->get_message(p_message, p_context);
- if (r) {
- return r;
- }
- }
- return p_message;
-}
-
-StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (doc_translation.is_valid()) {
- StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
- if (r) {
- return r;
- }
- }
-
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
-}
-
-void TranslationServer::set_extractable_translation(const Ref<Translation> &p_translation) {
- extractable_translation = p_translation;
-}
-
-StringName TranslationServer::extractable_translate(const StringName &p_message, const StringName &p_context) const {
- if (extractable_translation.is_valid()) {
- StringName r = extractable_translation->get_message(p_message, p_context);
- if (r) {
- return r;
- }
- }
- return p_message;
-}
-
-StringName TranslationServer::extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (extractable_translation.is_valid()) {
- StringName r = extractable_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
- if (r) {
- return r;
- }
- }
-
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
-}
-
-bool TranslationServer::is_pseudolocalization_enabled() const {
- return pseudolocalization_enabled;
-}
-
-void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
- pseudolocalization_enabled = p_enabled;
-
- ResourceLoader::reload_translation_remaps();
-
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
- }
-}
-
-void TranslationServer::set_editor_pseudolocalization(bool p_enabled) {
- editor_pseudolocalization = p_enabled;
-}
-
-void TranslationServer::reload_pseudolocalization() {
- pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents");
- pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels");
- pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi");
- pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override");
- expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
- pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
- pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
- pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
-
- ResourceLoader::reload_translation_remaps();
-
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
- }
-}
-
-StringName TranslationServer::pseudolocalize(const StringName &p_message) const {
- String message = p_message;
- int length = message.length();
- if (pseudolocalization_override_enabled) {
- message = get_override_string(message);
- }
-
- if (pseudolocalization_double_vowels_enabled) {
- message = double_vowels(message);
- }
-
- if (pseudolocalization_accents_enabled) {
- message = replace_with_accented_string(message);
- }
-
- if (pseudolocalization_fake_bidi_enabled) {
- message = wrap_with_fakebidi_characters(message);
- }
-
- StringName res = add_padding(message, length);
- return res;
-}
-
-StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const {
- String message = p_message;
- message = double_vowels(message);
- message = replace_with_accented_string(message);
- StringName res = "[!!! " + message + " !!!]";
- return res;
-}
-
-String TranslationServer::get_override_string(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- res += '*';
- }
- return res;
-}
-
-String TranslationServer::double_vowels(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- res += p_message[i];
- if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
- p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
- res += p_message[i];
- }
- }
- return res;
-};
-
-String TranslationServer::replace_with_accented_string(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- const char32_t *accented = get_accented_version(p_message[i]);
- if (accented) {
- res += accented;
- } else {
- res += p_message[i];
- }
- }
- return res;
-}
-
-String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
- String res;
- char32_t fakebidiprefix = U'\u202e';
- char32_t fakebidisuffix = U'\u202c';
- res += fakebidiprefix;
- // The fake bidi unicode gets popped at every newline so pushing it back at every newline.
- for (int i = 0; i < p_message.length(); i++) {
- if (p_message[i] == '\n') {
- res += fakebidisuffix;
- res += p_message[i];
- res += fakebidiprefix;
- } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += fakebidisuffix;
- res += p_message[i];
- res += p_message[i + 1];
- res += fakebidiprefix;
- i++;
- } else {
- res += p_message[i];
- }
- }
- res += fakebidisuffix;
- return res;
-}
-
-String TranslationServer::add_padding(const String &p_message, int p_length) const {
- String underscores = String("_").repeat(p_length * expansion_ratio / 2);
- String prefix = pseudolocalization_prefix + underscores;
- String suffix = underscores + pseudolocalization_suffix;
-
- return prefix + p_message + suffix;
-}
-
-const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
- if (!is_ascii_char(p_character)) {
- return nullptr;
- }
-
- for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
- if (_character_to_accented[i].character == p_character) {
- return _character_to_accented[i].accented_character;
- }
- }
-
- return nullptr;
-}
-
-bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
- return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
- (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
- p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
-}
-
-#ifdef TOOLS_ENABLED
-void TranslationServer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
- const String pf = p_function;
- if (p_idx == 0) {
- HashMap<String, String> *target_hash_map = nullptr;
- if (pf == "get_language_name") {
- target_hash_map = &language_map;
- } else if (pf == "get_script_name") {
- target_hash_map = &script_map;
- } else if (pf == "get_country_name") {
- target_hash_map = &country_name_map;
- }
-
- if (target_hash_map) {
- for (const KeyValue<String, String> &E : *target_hash_map) {
- r_options->push_back(E.key.quote());
- }
- }
- }
- Object::get_argument_options(p_function, p_idx, r_options);
-}
-#endif // TOOLS_ENABLED
-
-void TranslationServer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
- ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
- ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale);
-
- ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales);
- ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale);
-
- ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages);
- ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name);
-
- ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts);
- ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name);
-
- ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries);
- ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name);
-
- ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
-
- ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(StringName()));
- ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(StringName()));
-
- ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
- ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
- ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
-
- ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
-
- ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
-
- ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
- ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
- ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
- ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
- ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
-}
-
-void TranslationServer::load_translations() {
- _load_translations("internationalization/locale/translations"); //all
- _load_translations("internationalization/locale/translations_" + locale.substr(0, 2));
-
- if (locale.substr(0, 2) != locale) {
- _load_translations("internationalization/locale/translations_" + locale);
- }
-}
-
-TranslationServer::TranslationServer() {
- singleton = this;
- init_locale_info();
-}
diff --git a/core/string/translation.h b/core/string/translation.h
index 78d6721347..2c5baae8b7 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -74,134 +74,4 @@ public:
Translation() {}
};
-class TranslationServer : public Object {
- GDCLASS(TranslationServer, Object);
-
- String locale = "en";
- String fallback;
-
- HashSet<Ref<Translation>> translations;
- Ref<Translation> tool_translation;
- Ref<Translation> property_translation;
- Ref<Translation> doc_translation;
- Ref<Translation> extractable_translation;
-
- bool enabled = true;
-
- bool pseudolocalization_enabled = false;
- bool pseudolocalization_accents_enabled = false;
- bool pseudolocalization_double_vowels_enabled = false;
- bool pseudolocalization_fake_bidi_enabled = false;
- bool pseudolocalization_override_enabled = false;
- bool pseudolocalization_skip_placeholders_enabled = false;
- bool editor_pseudolocalization = false;
- float expansion_ratio = 0.0;
- String pseudolocalization_prefix;
- String pseudolocalization_suffix;
-
- StringName tool_pseudolocalize(const StringName &p_message) const;
- String get_override_string(String &p_message) const;
- String double_vowels(String &p_message) const;
- String replace_with_accented_string(String &p_message) const;
- String wrap_with_fakebidi_characters(String &p_message) const;
- String add_padding(const String &p_message, int p_length) const;
- const char32_t *get_accented_version(char32_t p_character) const;
- bool is_placeholder(String &p_message, int p_index) const;
-
- static TranslationServer *singleton;
- bool _load_translations(const String &p_from);
- String _standardize_locale(const String &p_locale, bool p_add_defaults) const;
-
- StringName _get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural = "", int p_n = 0) const;
-
- static void _bind_methods();
-
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
- struct LocaleScriptInfo {
- String name;
- String script;
- String default_country;
- HashSet<String> supported_countries;
- };
- static Vector<LocaleScriptInfo> locale_script_info;
-
- static HashMap<String, String> language_map;
- static HashMap<String, String> script_map;
- static HashMap<String, String> locale_rename_map;
- static HashMap<String, String> country_name_map;
- static HashMap<String, String> country_rename_map;
- static HashMap<String, String> variant_map;
-
- void init_locale_info();
-
-public:
- _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
-
- void set_enabled(bool p_enabled) { enabled = p_enabled; }
- _FORCE_INLINE_ bool is_enabled() const { return enabled; }
-
- void set_locale(const String &p_locale);
- String get_locale() const;
- Ref<Translation> get_translation_object(const String &p_locale);
-
- Vector<String> get_all_languages() const;
- String get_language_name(const String &p_language) const;
-
- Vector<String> get_all_scripts() const;
- String get_script_name(const String &p_script) const;
-
- Vector<String> get_all_countries() const;
- String get_country_name(const String &p_country) const;
-
- String get_locale_name(const String &p_locale) const;
-
- PackedStringArray get_loaded_locales() const;
-
- void add_translation(const Ref<Translation> &p_translation);
- void remove_translation(const Ref<Translation> &p_translation);
-
- StringName translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
-
- StringName pseudolocalize(const StringName &p_message) const;
-
- bool is_pseudolocalization_enabled() const;
- void set_pseudolocalization_enabled(bool p_enabled);
- void set_editor_pseudolocalization(bool p_enabled);
- void reload_pseudolocalization();
-
- String standardize_locale(const String &p_locale) const;
-
- int compare_locales(const String &p_locale_a, const String &p_locale_b) const;
-
- String get_tool_locale();
- void set_tool_translation(const Ref<Translation> &p_translation);
- Ref<Translation> get_tool_translation() const;
- StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
- void set_property_translation(const Ref<Translation> &p_translation);
- StringName property_translate(const StringName &p_message, const StringName &p_context = "") const;
- void set_doc_translation(const Ref<Translation> &p_translation);
- StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
- void set_extractable_translation(const Ref<Translation> &p_translation);
- StringName extractable_translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
-
- void setup();
-
- void clear();
-
- void load_translations();
-
-#ifdef TOOLS_ENABLED
- virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
-#endif // TOOLS_ENABLED
-
- TranslationServer();
-};
-
#endif // TRANSLATION_H
diff --git a/core/string/translation_po.cpp b/core/string/translation_po.cpp
index 06fd4717d7..8e275505b0 100644
--- a/core/string/translation_po.cpp
+++ b/core/string/translation_po.cpp
@@ -140,43 +140,87 @@ int TranslationPO::_get_plural_index(int p_n) const {
input_val.clear();
input_val.push_back(p_n);
- Variant result;
- for (int i = 0; i < equi_tests.size(); i++) {
- Error err = expr->parse(equi_tests[i], input_name);
- ERR_FAIL_COND_V_MSG(err != OK, 0, "Cannot parse expression. Error: " + expr->get_error_text());
+ return _eq_test(equi_tests, 0);
+}
- result = expr->execute(input_val);
- ERR_FAIL_COND_V_MSG(expr->has_execute_failed(), 0, "Cannot evaluate expression.");
+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()));
- // Last expression. Variant result will either map to a bool or an integer, in both cases returning it will give the correct plural index.
- if (i + 1 == equi_tests.size()) {
- return result;
- }
+ 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 i;
+ 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;
}
- ERR_FAIL_V_MSG(0, "Unexpected. Function should have returned. Please report this bug.");
+ 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) {
+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)
- int first_ques_mark = p_plural_rule.find("?");
+
+ 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) {
- equi_tests.push_back(p_plural_rule.strip_edges());
+ p_node->regex = rule.strip_edges();
return;
}
- String equi_test = p_plural_rule.substr(0, first_ques_mark).strip_edges();
- equi_tests.push_back(equi_test);
+ p_node->regex = rule.substr(0, first_ques_mark).strip_edges();
- String after_colon = p_plural_rule.substr(p_plural_rule.find(":") + 1, p_plural_rule.length());
- _cache_plural_tests(after_colon);
+ 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) {
@@ -188,12 +232,12 @@ void TranslationPO::set_plural_rule(const String &p_plural_rule) {
int expression_start = p_plural_rule.find("=", first_semi_col) + 1;
int second_semi_col = p_plural_rule.rfind(";");
- plural_rule = p_plural_rule.substr(expression_start, second_semi_col - expression_start);
+ 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.
- plural_rule = plural_rule.replacen("(", "");
- plural_rule = plural_rule.replacen(")", "");
- _cache_plural_tests(plural_rule);
+ equi_tests.instantiate();
+ _cache_plural_tests(plural_rule, equi_tests);
+
expr.instantiate();
input_name.push_back("n");
}
diff --git a/core/string/translation_po.h b/core/string/translation_po.h
index 73f9b33a87..ba820c6ee4 100644
--- a/core/string/translation_po.h
+++ b/core/string/translation_po.h
@@ -50,7 +50,17 @@ class TranslationPO : public Translation {
String plural_rule;
// Cache temporary variables related to _get_plural_index() to make it faster
- Vector<String> equi_tests;
+ 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;
@@ -59,7 +69,7 @@ class TranslationPO : public Translation {
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);
+ 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;
diff --git a/core/string/translation_server.compat.inc b/core/string/translation_server.compat.inc
new file mode 100644
index 0000000000..9d1ee8b9df
--- /dev/null
+++ b/core/string/translation_server.compat.inc
@@ -0,0 +1,38 @@
+/**************************************************************************/
+/* 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
+
+void TranslationServer::_bind_compatibility_methods() {
+ ClassDB::bind_compatibility_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(""));
+ ClassDB::bind_compatibility_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(""));
+}
+
+#endif
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
new file mode 100644
index 0000000000..6e784881d0
--- /dev/null
+++ b/core/string/translation_server.cpp
@@ -0,0 +1,947 @@
+/**************************************************************************/
+/* translation_server.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "translation_server.h"
+#include "translation_server.compat.inc"
+
+#include "core/config/project_settings.h"
+#include "core/io/resource_loader.h"
+#include "core/os/os.h"
+#include "core/string/locales.h"
+
+#ifdef TOOLS_ENABLED
+#include "main/main.h"
+#endif
+
+struct _character_accent_pair {
+ const char32_t character;
+ const char32_t *accented_character;
+};
+
+static _character_accent_pair _character_to_accented[] = {
+ { 'A', U"Å" },
+ { 'B', U"ß" },
+ { 'C', U"Ç" },
+ { 'D', U"Ð" },
+ { 'E', U"É" },
+ { 'F', U"F́" },
+ { 'G', U"Ĝ" },
+ { 'H', U"Ĥ" },
+ { 'I', U"Ĩ" },
+ { 'J', U"Ĵ" },
+ { 'K', U"ĸ" },
+ { 'L', U"Ł" },
+ { 'M', U"Ḿ" },
+ { 'N', U"й" },
+ { 'O', U"Ö" },
+ { 'P', U"Ṕ" },
+ { 'Q', U"Q́" },
+ { 'R', U"Ř" },
+ { 'S', U"Ŝ" },
+ { 'T', U"Ŧ" },
+ { 'U', U"Ũ" },
+ { 'V', U"Ṽ" },
+ { 'W', U"Ŵ" },
+ { 'X', U"X́" },
+ { 'Y', U"Ÿ" },
+ { 'Z', U"Ž" },
+ { 'a', U"á" },
+ { 'b', U"ḅ" },
+ { 'c', U"ć" },
+ { 'd', U"d́" },
+ { 'e', U"é" },
+ { 'f', U"f́" },
+ { 'g', U"ǵ" },
+ { 'h', U"h̀" },
+ { 'i', U"í" },
+ { 'j', U"ǰ" },
+ { 'k', U"ḱ" },
+ { 'l', U"ł" },
+ { 'm', U"m̀" },
+ { 'n', U"ή" },
+ { 'o', U"ô" },
+ { 'p', U"ṕ" },
+ { 'q', U"q́" },
+ { 'r', U"ŕ" },
+ { 's', U"š" },
+ { 't', U"ŧ" },
+ { 'u', U"ü" },
+ { 'v', U"ṽ" },
+ { 'w', U"ŵ" },
+ { 'x', U"x́" },
+ { 'y', U"ý" },
+ { 'z', U"ź" },
+};
+
+Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
+
+HashMap<String, String> TranslationServer::language_map;
+HashMap<String, String> TranslationServer::script_map;
+HashMap<String, String> TranslationServer::locale_rename_map;
+HashMap<String, String> TranslationServer::country_name_map;
+HashMap<String, String> TranslationServer::variant_map;
+HashMap<String, String> TranslationServer::country_rename_map;
+
+void TranslationServer::init_locale_info() {
+ // Init locale info.
+ language_map.clear();
+ int idx = 0;
+ while (language_list[idx][0] != nullptr) {
+ language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]);
+ idx++;
+ }
+
+ // Init locale-script map.
+ locale_script_info.clear();
+ idx = 0;
+ while (locale_scripts[idx][0] != nullptr) {
+ LocaleScriptInfo info;
+ info.name = locale_scripts[idx][0];
+ info.script = locale_scripts[idx][1];
+ info.default_country = locale_scripts[idx][2];
+ Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false);
+ for (int i = 0; i < supported_countries.size(); i++) {
+ info.supported_countries.insert(supported_countries[i]);
+ }
+ locale_script_info.push_back(info);
+ idx++;
+ }
+
+ // Init supported script list.
+ script_map.clear();
+ idx = 0;
+ while (script_list[idx][0] != nullptr) {
+ script_map[script_list[idx][1]] = String::utf8(script_list[idx][0]);
+ idx++;
+ }
+
+ // Init regional variant map.
+ variant_map.clear();
+ idx = 0;
+ while (locale_variants[idx][0] != nullptr) {
+ variant_map[locale_variants[idx][0]] = locale_variants[idx][1];
+ idx++;
+ }
+
+ // Init locale renames.
+ locale_rename_map.clear();
+ idx = 0;
+ while (locale_renames[idx][0] != nullptr) {
+ if (!String(locale_renames[idx][1]).is_empty()) {
+ locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1];
+ }
+ idx++;
+ }
+
+ // Init country names.
+ country_name_map.clear();
+ idx = 0;
+ while (country_names[idx][0] != nullptr) {
+ country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]);
+ idx++;
+ }
+
+ // Init country renames.
+ country_rename_map.clear();
+ idx = 0;
+ while (country_renames[idx][0] != nullptr) {
+ if (!String(country_renames[idx][1]).is_empty()) {
+ country_rename_map[country_renames[idx][0]] = country_renames[idx][1];
+ }
+ idx++;
+ }
+}
+
+String TranslationServer::standardize_locale(const String &p_locale) const {
+ return _standardize_locale(p_locale, false);
+}
+
+String TranslationServer::_standardize_locale(const String &p_locale, bool p_add_defaults) const {
+ // Replaces '-' with '_' for macOS style locales.
+ String univ_locale = p_locale.replace("-", "_");
+
+ // Extract locale elements.
+ String lang_name, script_name, country_name, variant_name;
+ Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_");
+ lang_name = locale_elements[0];
+ if (locale_elements.size() >= 2) {
+ if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
+ script_name = locale_elements[1];
+ }
+ if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
+ country_name = locale_elements[1];
+ }
+ }
+ if (locale_elements.size() >= 3) {
+ if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
+ country_name = locale_elements[2];
+ } else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang_name) {
+ variant_name = locale_elements[2].to_lower();
+ }
+ }
+ if (locale_elements.size() >= 4) {
+ if (variant_map.has(locale_elements[3].to_lower()) && variant_map[locale_elements[3].to_lower()] == lang_name) {
+ variant_name = locale_elements[3].to_lower();
+ }
+ }
+
+ // Try extract script and variant from the extra part.
+ Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";");
+ for (int i = 0; i < script_extra.size(); i++) {
+ if (script_extra[i].to_lower() == "cyrillic") {
+ script_name = "Cyrl";
+ break;
+ } else if (script_extra[i].to_lower() == "latin") {
+ script_name = "Latn";
+ break;
+ } else if (script_extra[i].to_lower() == "devanagari") {
+ script_name = "Deva";
+ break;
+ } else if (variant_map.has(script_extra[i].to_lower()) && variant_map[script_extra[i].to_lower()] == lang_name) {
+ variant_name = script_extra[i].to_lower();
+ }
+ }
+
+ // Handles known non-ISO language names used e.g. on Windows.
+ if (locale_rename_map.has(lang_name)) {
+ lang_name = locale_rename_map[lang_name];
+ }
+
+ // Handle country renames.
+ if (country_rename_map.has(country_name)) {
+ country_name = country_rename_map[country_name];
+ }
+
+ // Remove unsupported script codes.
+ if (!script_map.has(script_name)) {
+ script_name = "";
+ }
+
+ // Add script code base on language and country codes for some ambiguous cases.
+ if (p_add_defaults) {
+ if (script_name.is_empty()) {
+ for (int i = 0; i < locale_script_info.size(); i++) {
+ const LocaleScriptInfo &info = locale_script_info[i];
+ if (info.name == lang_name) {
+ if (country_name.is_empty() || info.supported_countries.has(country_name)) {
+ script_name = info.script;
+ break;
+ }
+ }
+ }
+ }
+ if (!script_name.is_empty() && country_name.is_empty()) {
+ // Add conntry code based on script for some ambiguous cases.
+ for (int i = 0; i < locale_script_info.size(); i++) {
+ const LocaleScriptInfo &info = locale_script_info[i];
+ if (info.name == lang_name && info.script == script_name) {
+ country_name = info.default_country;
+ break;
+ }
+ }
+ }
+ }
+
+ // Combine results.
+ String out = lang_name;
+ if (!script_name.is_empty()) {
+ out = out + "_" + script_name;
+ }
+ if (!country_name.is_empty()) {
+ out = out + "_" + country_name;
+ }
+ if (!variant_name.is_empty()) {
+ out = out + "_" + variant_name;
+ }
+ return out;
+}
+
+int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
+ String locale_a = _standardize_locale(p_locale_a, true);
+ String locale_b = _standardize_locale(p_locale_b, true);
+
+ if (locale_a == locale_b) {
+ // Exact match.
+ return 10;
+ }
+
+ Vector<String> locale_a_elements = locale_a.split("_");
+ Vector<String> locale_b_elements = locale_b.split("_");
+ if (locale_a_elements[0] == locale_b_elements[0]) {
+ // Matching language, both locales have extra parts.
+ // Return number of matching elements.
+ int matching_elements = 1;
+ for (int i = 1; i < locale_a_elements.size(); i++) {
+ for (int j = 1; j < locale_b_elements.size(); j++) {
+ if (locale_a_elements[i] == locale_b_elements[j]) {
+ matching_elements++;
+ }
+ }
+ }
+ return matching_elements;
+ } else {
+ // No match.
+ return 0;
+ }
+}
+
+String TranslationServer::get_locale_name(const String &p_locale) const {
+ String lang_name, script_name, country_name;
+ Vector<String> locale_elements = standardize_locale(p_locale).split("_");
+ lang_name = locale_elements[0];
+ if (locale_elements.size() >= 2) {
+ if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
+ script_name = locale_elements[1];
+ }
+ if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
+ country_name = locale_elements[1];
+ }
+ }
+ if (locale_elements.size() >= 3) {
+ if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
+ country_name = locale_elements[2];
+ }
+ }
+
+ String name = language_map[lang_name];
+ if (!script_name.is_empty()) {
+ name = name + " (" + script_map[script_name] + ")";
+ }
+ if (!country_name.is_empty()) {
+ name = name + ", " + country_name_map[country_name];
+ }
+ return name;
+}
+
+Vector<String> TranslationServer::get_all_languages() const {
+ Vector<String> languages;
+
+ for (const KeyValue<String, String> &E : language_map) {
+ languages.push_back(E.key);
+ }
+
+ return languages;
+}
+
+String TranslationServer::get_language_name(const String &p_language) const {
+ return language_map[p_language];
+}
+
+Vector<String> TranslationServer::get_all_scripts() const {
+ Vector<String> scripts;
+
+ for (const KeyValue<String, String> &E : script_map) {
+ scripts.push_back(E.key);
+ }
+
+ return scripts;
+}
+
+String TranslationServer::get_script_name(const String &p_script) const {
+ return script_map[p_script];
+}
+
+Vector<String> TranslationServer::get_all_countries() const {
+ Vector<String> countries;
+
+ for (const KeyValue<String, String> &E : country_name_map) {
+ countries.push_back(E.key);
+ }
+
+ return countries;
+}
+
+String TranslationServer::get_country_name(const String &p_country) const {
+ return country_name_map[p_country];
+}
+
+void TranslationServer::set_locale(const String &p_locale) {
+ String new_locale = standardize_locale(p_locale);
+ if (locale == new_locale) {
+ return;
+ }
+
+ locale = new_locale;
+ ResourceLoader::reload_translation_remaps();
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+}
+
+String TranslationServer::get_locale() const {
+ return locale;
+}
+
+PackedStringArray TranslationServer::get_loaded_locales() const {
+ PackedStringArray locales;
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), PackedStringArray());
+ String l = t->get_locale();
+
+ locales.push_back(l);
+ }
+
+ return locales;
+}
+
+void TranslationServer::add_translation(const Ref<Translation> &p_translation) {
+ translations.insert(p_translation);
+}
+
+void TranslationServer::remove_translation(const Ref<Translation> &p_translation) {
+ translations.erase(p_translation);
+}
+
+Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
+ Ref<Translation> res;
+ int best_score = 0;
+
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), nullptr);
+ String l = t->get_locale();
+
+ int score = compare_locales(p_locale, l);
+ if (score > 0 && score >= best_score) {
+ res = t;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
+ }
+ }
+ return res;
+}
+
+void TranslationServer::clear() {
+ translations.clear();
+}
+
+StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const {
+ // Match given message against the translation catalog for the project locale.
+
+ if (!enabled) {
+ return p_message;
+ }
+
+ StringName res = _get_message_from_translations(p_message, p_context, locale, false);
+
+ if (!res && fallback.length() >= 2) {
+ res = _get_message_from_translations(p_message, p_context, fallback, false);
+ }
+
+ if (!res) {
+ return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message;
+ }
+
+ return pseudolocalization_enabled ? pseudolocalize(res) : res;
+}
+
+StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (!enabled) {
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+ }
+
+ StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n);
+
+ if (!res && fallback.length() >= 2) {
+ res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n);
+ }
+
+ if (!res) {
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+ }
+
+ return res;
+}
+
+StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const {
+ StringName res;
+ int best_score = 0;
+
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), p_message);
+ String l = t->get_locale();
+
+ int score = compare_locales(p_locale, l);
+ if (score > 0 && score >= best_score) {
+ StringName r;
+ if (!plural) {
+ r = t->get_message(p_message, p_context);
+ } else {
+ r = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ }
+ if (!r) {
+ continue;
+ }
+ res = r;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
+ }
+ }
+
+ return res;
+}
+
+TranslationServer *TranslationServer::singleton = nullptr;
+
+bool TranslationServer::_load_translations(const String &p_from) {
+ if (ProjectSettings::get_singleton()->has_setting(p_from)) {
+ const Vector<String> &translation_names = GLOBAL_GET(p_from);
+
+ int tcount = translation_names.size();
+
+ if (tcount) {
+ const String *r = translation_names.ptr();
+
+ for (int i = 0; i < tcount; i++) {
+ Ref<Translation> tr = ResourceLoader::load(r[i]);
+ if (tr.is_valid()) {
+ add_translation(tr);
+ }
+ }
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void TranslationServer::setup() {
+ String test = GLOBAL_DEF("internationalization/locale/test", "");
+ test = test.strip_edges();
+ if (!test.is_empty()) {
+ set_locale(test);
+ } else {
+ set_locale(OS::get_singleton()->get_locale());
+ }
+
+ fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
+ pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
+ pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
+ pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false);
+ pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false);
+ pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false);
+ expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
+ pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
+ pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
+
+#ifdef TOOLS_ENABLED
+ ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
+#endif
+}
+
+void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) {
+ tool_translation = p_translation;
+}
+
+Ref<Translation> TranslationServer::get_tool_translation() const {
+ return tool_translation;
+}
+
+String TranslationServer::get_tool_locale() {
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
+ if (TranslationServer::get_singleton()->get_tool_translation().is_valid()) {
+ return tool_translation->get_locale();
+ } else {
+ return "en";
+ }
+ } else {
+#else
+ {
+#endif
+ // Look for best matching loaded translation.
+ String best_locale = "en";
+ int best_score = 0;
+
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), best_locale);
+ String l = t->get_locale();
+
+ int score = compare_locales(locale, l);
+ if (score > 0 && score >= best_score) {
+ best_locale = l;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
+ }
+ }
+ return best_locale;
+ }
+}
+
+StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
+ if (tool_translation.is_valid()) {
+ StringName r = tool_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (tool_translation.is_valid()) {
+ StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) {
+ property_translation = p_translation;
+}
+
+StringName TranslationServer::property_translate(const StringName &p_message, const StringName &p_context) const {
+ if (property_translation.is_valid()) {
+ StringName r = property_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
+ doc_translation = p_translation;
+}
+
+StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const {
+ if (doc_translation.is_valid()) {
+ StringName r = doc_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (doc_translation.is_valid()) {
+ StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+void TranslationServer::set_extractable_translation(const Ref<Translation> &p_translation) {
+ extractable_translation = p_translation;
+}
+
+StringName TranslationServer::extractable_translate(const StringName &p_message, const StringName &p_context) const {
+ if (extractable_translation.is_valid()) {
+ StringName r = extractable_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+StringName TranslationServer::extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (extractable_translation.is_valid()) {
+ StringName r = extractable_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+bool TranslationServer::is_pseudolocalization_enabled() const {
+ return pseudolocalization_enabled;
+}
+
+void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
+ pseudolocalization_enabled = p_enabled;
+
+ ResourceLoader::reload_translation_remaps();
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+}
+
+void TranslationServer::reload_pseudolocalization() {
+ pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents");
+ pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels");
+ pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi");
+ pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override");
+ expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
+ pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
+ pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
+
+ ResourceLoader::reload_translation_remaps();
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+}
+
+StringName TranslationServer::pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ int length = message.length();
+ if (pseudolocalization_override_enabled) {
+ message = get_override_string(message);
+ }
+
+ if (pseudolocalization_double_vowels_enabled) {
+ message = double_vowels(message);
+ }
+
+ if (pseudolocalization_accents_enabled) {
+ message = replace_with_accented_string(message);
+ }
+
+ if (pseudolocalization_fake_bidi_enabled) {
+ message = wrap_with_fakebidi_characters(message);
+ }
+
+ StringName res = add_padding(message, length);
+ return res;
+}
+
+StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const {
+ String message = p_message;
+ message = double_vowels(message);
+ message = replace_with_accented_string(message);
+ StringName res = "[!!! " + message + " !!!]";
+ return res;
+}
+
+String TranslationServer::get_override_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += '*';
+ }
+ return res;
+}
+
+String TranslationServer::double_vowels(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += p_message[i];
+ if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
+ p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
+ res += p_message[i];
+ }
+ }
+ return res;
+};
+
+String TranslationServer::replace_with_accented_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ const char32_t *accented = get_accented_version(p_message[i]);
+ if (accented) {
+ res += accented;
+ } else {
+ res += p_message[i];
+ }
+ }
+ return res;
+}
+
+String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
+ String res;
+ char32_t fakebidiprefix = U'\u202e';
+ char32_t fakebidisuffix = U'\u202c';
+ res += fakebidiprefix;
+ // The fake bidi unicode gets popped at every newline so pushing it back at every newline.
+ for (int i = 0; i < p_message.length(); i++) {
+ if (p_message[i] == '\n') {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += fakebidiprefix;
+ } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += p_message[i + 1];
+ res += fakebidiprefix;
+ i++;
+ } else {
+ res += p_message[i];
+ }
+ }
+ res += fakebidisuffix;
+ return res;
+}
+
+String TranslationServer::add_padding(const String &p_message, int p_length) const {
+ String underscores = String("_").repeat(p_length * expansion_ratio / 2);
+ String prefix = pseudolocalization_prefix + underscores;
+ String suffix = underscores + pseudolocalization_suffix;
+
+ return prefix + p_message + suffix;
+}
+
+const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
+ if (!is_ascii_alphabet_char(p_character)) {
+ return nullptr;
+ }
+
+ for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
+ if (_character_to_accented[i].character == p_character) {
+ return _character_to_accented[i].accented_character;
+ }
+ }
+
+ return nullptr;
+}
+
+bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
+ return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
+ (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
+ p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
+}
+
+#ifdef TOOLS_ENABLED
+void TranslationServer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
+ const String pf = p_function;
+ if (p_idx == 0) {
+ HashMap<String, String> *target_hash_map = nullptr;
+ if (pf == "get_language_name") {
+ target_hash_map = &language_map;
+ } else if (pf == "get_script_name") {
+ target_hash_map = &script_map;
+ } else if (pf == "get_country_name") {
+ target_hash_map = &country_name_map;
+ }
+
+ if (target_hash_map) {
+ for (const KeyValue<String, String> &E : *target_hash_map) {
+ r_options->push_back(E.key.quote());
+ }
+ }
+ }
+ Object::get_argument_options(p_function, p_idx, r_options);
+}
+#endif // TOOLS_ENABLED
+
+void TranslationServer::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
+ ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
+ ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale);
+
+ ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales);
+ ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale);
+
+ ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages);
+ ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name);
+
+ ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts);
+ ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name);
+
+ ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries);
+ ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name);
+
+ ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
+
+ ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(StringName()));
+ ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(StringName()));
+
+ ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
+ ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
+ ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
+
+ ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
+
+ ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
+
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
+ ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
+}
+
+void TranslationServer::load_translations() {
+ _load_translations("internationalization/locale/translations"); //all
+ _load_translations("internationalization/locale/translations_" + locale.substr(0, 2));
+
+ if (locale.substr(0, 2) != locale) {
+ _load_translations("internationalization/locale/translations_" + locale);
+ }
+}
+
+TranslationServer::TranslationServer() {
+ singleton = this;
+ init_locale_info();
+}
diff --git a/core/string/translation_server.h b/core/string/translation_server.h
new file mode 100644
index 0000000000..ebe81d9712
--- /dev/null
+++ b/core/string/translation_server.h
@@ -0,0 +1,164 @@
+/**************************************************************************/
+/* translation_server.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TRANSLATION_SERVER_H
+#define TRANSLATION_SERVER_H
+
+#include "core/string/translation.h"
+
+class TranslationServer : public Object {
+ GDCLASS(TranslationServer, Object);
+
+ String locale = "en";
+ String fallback;
+
+ HashSet<Ref<Translation>> translations;
+ Ref<Translation> tool_translation;
+ Ref<Translation> property_translation;
+ Ref<Translation> doc_translation;
+ Ref<Translation> extractable_translation;
+
+ bool enabled = true;
+
+ bool pseudolocalization_enabled = false;
+ bool pseudolocalization_accents_enabled = false;
+ bool pseudolocalization_double_vowels_enabled = false;
+ bool pseudolocalization_fake_bidi_enabled = false;
+ bool pseudolocalization_override_enabled = false;
+ bool pseudolocalization_skip_placeholders_enabled = false;
+ float expansion_ratio = 0.0;
+ String pseudolocalization_prefix;
+ String pseudolocalization_suffix;
+
+ StringName tool_pseudolocalize(const StringName &p_message) const;
+ String get_override_string(String &p_message) const;
+ String double_vowels(String &p_message) const;
+ String replace_with_accented_string(String &p_message) const;
+ String wrap_with_fakebidi_characters(String &p_message) const;
+ String add_padding(const String &p_message, int p_length) const;
+ const char32_t *get_accented_version(char32_t p_character) const;
+ bool is_placeholder(String &p_message, int p_index) const;
+
+ static TranslationServer *singleton;
+ bool _load_translations(const String &p_from);
+ String _standardize_locale(const String &p_locale, bool p_add_defaults) const;
+
+ StringName _get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural = "", int p_n = 0) const;
+
+ static void _bind_methods();
+
+#ifndef DISABLE_DEPRECATED
+ static void _bind_compatibility_methods();
+#endif
+
+ struct LocaleScriptInfo {
+ String name;
+ String script;
+ String default_country;
+ HashSet<String> supported_countries;
+ };
+ static Vector<LocaleScriptInfo> locale_script_info;
+
+ static HashMap<String, String> language_map;
+ static HashMap<String, String> script_map;
+ static HashMap<String, String> locale_rename_map;
+ static HashMap<String, String> country_name_map;
+ static HashMap<String, String> country_rename_map;
+ static HashMap<String, String> variant_map;
+
+ void init_locale_info();
+
+public:
+ _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
+
+ void set_enabled(bool p_enabled) { enabled = p_enabled; }
+ _FORCE_INLINE_ bool is_enabled() const { return enabled; }
+
+ void set_locale(const String &p_locale);
+ String get_locale() const;
+ Ref<Translation> get_translation_object(const String &p_locale);
+
+ Vector<String> get_all_languages() const;
+ String get_language_name(const String &p_language) const;
+
+ Vector<String> get_all_scripts() const;
+ String get_script_name(const String &p_script) const;
+
+ Vector<String> get_all_countries() const;
+ String get_country_name(const String &p_country) const;
+
+ String get_locale_name(const String &p_locale) const;
+
+ PackedStringArray get_loaded_locales() const;
+
+ void add_translation(const Ref<Translation> &p_translation);
+ void remove_translation(const Ref<Translation> &p_translation);
+
+ StringName translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+
+ StringName pseudolocalize(const StringName &p_message) const;
+
+ bool is_pseudolocalization_enabled() const;
+ void set_pseudolocalization_enabled(bool p_enabled);
+ void reload_pseudolocalization();
+
+ String standardize_locale(const String &p_locale) const;
+
+ int compare_locales(const String &p_locale_a, const String &p_locale_b) const;
+
+ String get_tool_locale();
+ void set_tool_translation(const Ref<Translation> &p_translation);
+ Ref<Translation> get_tool_translation() const;
+ StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+ void set_property_translation(const Ref<Translation> &p_translation);
+ StringName property_translate(const StringName &p_message, const StringName &p_context = "") const;
+ void set_doc_translation(const Ref<Translation> &p_translation);
+ StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+ void set_extractable_translation(const Ref<Translation> &p_translation);
+ StringName extractable_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+
+ void setup();
+
+ void clear();
+
+ void load_translations();
+
+#ifdef TOOLS_ENABLED
+ virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
+#endif // TOOLS_ENABLED
+
+ TranslationServer();
+};
+
+#endif // TRANSLATION_SERVER_H
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 2b62b72a51..2683addd4b 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -36,7 +36,7 @@
#include "core/os/memory.h"
#include "core/string/print_string.h"
#include "core/string/string_name.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/string/ucaps.h"
#include "core/variant/variant.h"
#include "core/version_generated.gen.h"
@@ -1184,6 +1184,26 @@ int String::get_slice_count(const String &p_splitter) const {
return slices;
}
+int String::get_slice_count(const char *p_splitter) const {
+ if (is_empty()) {
+ return 0;
+ }
+ if (p_splitter == nullptr || *p_splitter == '\0') {
+ return 0;
+ }
+
+ int pos = 0;
+ int slices = 1;
+ int splitter_length = strlen(p_splitter);
+
+ while ((pos = find(p_splitter, pos)) >= 0) {
+ slices++;
+ pos += splitter_length;
+ }
+
+ return slices;
+}
+
String String::get_slice(const String &p_splitter, int p_slice) const {
if (is_empty() || p_splitter.is_empty()) {
return "";
@@ -1224,6 +1244,47 @@ String String::get_slice(const String &p_splitter, int p_slice) const {
return ""; //no find!
}
+String String::get_slice(const char *p_splitter, int p_slice) const {
+ if (is_empty() || p_splitter == nullptr || *p_splitter == '\0') {
+ return "";
+ }
+
+ int pos = 0;
+ int prev_pos = 0;
+ //int slices=1;
+ if (p_slice < 0) {
+ return "";
+ }
+ if (find(p_splitter) == -1) {
+ return *this;
+ }
+
+ int i = 0;
+ int splitter_length = strlen(p_splitter);
+ while (true) {
+ pos = find(p_splitter, pos);
+ if (pos == -1) {
+ pos = length(); //reached end
+ }
+
+ int from = prev_pos;
+ //int to=pos;
+
+ if (p_slice == i) {
+ return substr(from, pos - from);
+ }
+
+ if (pos == length()) { //reached end and no find
+ break;
+ }
+ pos += splitter_length;
+ prev_pos = pos;
+ i++;
+ }
+
+ return ""; //no find!
+}
+
String String::get_slicec(char32_t p_splitter, int p_slice) const {
if (is_empty()) {
return String();
@@ -1338,6 +1399,54 @@ Vector<String> String::split(const String &p_splitter, bool p_allow_empty, int p
return ret;
}
+Vector<String> String::split(const char *p_splitter, bool p_allow_empty, int p_maxsplit) const {
+ Vector<String> ret;
+
+ if (is_empty()) {
+ if (p_allow_empty) {
+ ret.push_back("");
+ }
+ return ret;
+ }
+
+ int from = 0;
+ int len = length();
+
+ while (true) {
+ int end;
+ if (p_splitter == nullptr || *p_splitter == '\0') {
+ end = from + 1;
+ } else {
+ end = find(p_splitter, from);
+ if (end < 0) {
+ end = len;
+ }
+ }
+ if (p_allow_empty || (end > from)) {
+ if (p_maxsplit <= 0) {
+ ret.push_back(substr(from, end - from));
+ } else {
+ // Put rest of the string and leave cycle.
+ if (p_maxsplit == ret.size()) {
+ ret.push_back(substr(from, len));
+ break;
+ }
+
+ // Otherwise, push items until positive limit is reached.
+ ret.push_back(substr(from, end - from));
+ }
+ }
+
+ if (end == len) {
+ break;
+ }
+
+ from = end + strlen(p_splitter);
+ }
+
+ return ret;
+}
+
Vector<String> String::rsplit(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const {
Vector<String> ret;
const int len = length();
@@ -1380,18 +1489,64 @@ Vector<String> String::rsplit(const String &p_splitter, bool p_allow_empty, int
return ret;
}
+Vector<String> String::rsplit(const char *p_splitter, bool p_allow_empty, int p_maxsplit) const {
+ Vector<String> ret;
+ const int len = length();
+ const int splitter_length = strlen(p_splitter);
+ int remaining_len = len;
+
+ while (true) {
+ if (remaining_len < splitter_length || (p_maxsplit > 0 && p_maxsplit == ret.size())) {
+ // no room for another splitter or hit max splits, push what's left and we're done
+ if (p_allow_empty || remaining_len > 0) {
+ ret.push_back(substr(0, remaining_len));
+ }
+ break;
+ }
+
+ int left_edge;
+ if (p_splitter == nullptr || *p_splitter == '\0') {
+ left_edge = remaining_len - 1;
+ if (left_edge == 0) {
+ left_edge--; // Skip to the < 0 condition.
+ }
+ } else {
+ left_edge = rfind(p_splitter, remaining_len - splitter_length);
+ }
+
+ if (left_edge < 0) {
+ // no more splitters, we're done
+ ret.push_back(substr(0, remaining_len));
+ break;
+ }
+
+ int substr_start = left_edge + splitter_length;
+ if (p_allow_empty || substr_start < remaining_len) {
+ ret.push_back(substr(substr_start, remaining_len - substr_start));
+ }
+
+ remaining_len = left_edge;
+ }
+
+ ret.reverse();
+ return ret;
+}
+
Vector<double> String::split_floats(const String &p_splitter, bool p_allow_empty) const {
Vector<double> ret;
int from = 0;
int len = length();
+ String buffer = *this;
while (true) {
int end = find(p_splitter, from);
if (end < 0) {
end = len;
}
if (p_allow_empty || (end > from)) {
- ret.push_back(String::to_float(&get_data()[from]));
+ buffer[end] = 0;
+ ret.push_back(String::to_float(&buffer.get_data()[from]));
+ buffer[end] = _cowdata.get(end);
}
if (end == len) {
@@ -1409,6 +1564,7 @@ Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_
int from = 0;
int len = length();
+ String buffer = *this;
while (true) {
int idx;
int end = findmk(p_splitters, from, &idx);
@@ -1420,7 +1576,9 @@ Vector<float> String::split_floats_mk(const Vector<String> &p_splitters, bool p_
}
if (p_allow_empty || (end > from)) {
- ret.push_back(String::to_float(&get_data()[from]));
+ buffer[end] = 0;
+ ret.push_back(String::to_float(&buffer.get_data()[from]));
+ buffer[end] = _cowdata.get(end);
}
if (end == len) {
@@ -1487,13 +1645,43 @@ Vector<int> String::split_ints_mk(const Vector<String> &p_splitters, bool p_allo
}
String String::join(const Vector<String> &parts) const {
+ if (parts.is_empty()) {
+ return String();
+ } else if (parts.size() == 1) {
+ return parts[0];
+ }
+
+ const int this_length = length();
+
+ int new_size = (parts.size() - 1) * this_length;
+ for (const String &part : parts) {
+ new_size += part.length();
+ }
+ new_size += 1;
+
String ret;
- for (int i = 0; i < parts.size(); ++i) {
- if (i > 0) {
- ret += *this;
+ ret.resize(new_size);
+ char32_t *ret_ptrw = ret.ptrw();
+ const char32_t *this_ptr = ptr();
+
+ bool first = true;
+ for (const String &part : parts) {
+ if (first) {
+ first = false;
+ } else if (this_length) {
+ memcpy(ret_ptrw, this_ptr, this_length * sizeof(char32_t));
+ ret_ptrw += this_length;
+ }
+
+ const int part_length = part.length();
+ if (part_length) {
+ memcpy(ret_ptrw, part.ptr(), part_length * sizeof(char32_t));
+ ret_ptrw += part_length;
}
- ret += parts[i];
}
+
+ *ret_ptrw = 0;
+
return ret;
}
@@ -1506,30 +1694,40 @@ char32_t String::char_lowercase(char32_t p_char) {
}
String String::to_upper() const {
- String upper = *this;
+ if (is_empty()) {
+ return *this;
+ }
- for (int i = 0; i < upper.size(); i++) {
- const char32_t s = upper[i];
- const char32_t t = _find_upper(s);
- if (s != t) { // avoid copy on write
- upper[i] = t;
- }
+ String upper;
+ upper.resize(size());
+ const char32_t *old_ptr = ptr();
+ char32_t *upper_ptrw = upper.ptrw();
+
+ while (*old_ptr) {
+ *upper_ptrw++ = _find_upper(*old_ptr++);
}
+ *upper_ptrw = 0;
+
return upper;
}
String String::to_lower() const {
- String lower = *this;
+ if (is_empty()) {
+ return *this;
+ }
- for (int i = 0; i < lower.size(); i++) {
- const char32_t s = lower[i];
- const char32_t t = _find_lower(s);
- if (s != t) { // avoid copy on write
- lower[i] = t;
- }
+ String lower;
+ lower.resize(size());
+ const char32_t *old_ptr = ptr();
+ char32_t *lower_ptrw = lower.ptrw();
+
+ while (*old_ptr) {
+ *lower_ptrw++ = _find_lower(*old_ptr++);
}
+ *lower_ptrw = 0;
+
return lower;
}
@@ -1767,15 +1965,16 @@ String String::hex_encode_buffer(const uint8_t *p_buffer, int p_len) {
static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
String ret;
- char v[2] = { 0, 0 };
+ ret.resize(p_len * 2 + 1);
+ char32_t *ret_ptrw = ret.ptrw();
for (int i = 0; i < p_len; i++) {
- v[0] = hex[p_buffer[i] >> 4];
- ret += v;
- v[0] = hex[p_buffer[i] & 0xF];
- ret += v;
+ *ret_ptrw++ = hex[p_buffer[i] >> 4];
+ *ret_ptrw++ = hex[p_buffer[i] & 0xF];
}
+ *ret_ptrw = 0;
+
return ret;
}
@@ -1798,11 +1997,12 @@ Vector<uint8_t> String::hex_decode() const {
Vector<uint8_t> out;
int len = length() / 2;
out.resize(len);
+ uint8_t *out_ptrw = out.ptrw();
for (int i = 0; i < len; i++) {
char32_t c;
HEX_TO_BYTE(first, i * 2);
HEX_TO_BYTE(second, i * 2 + 1);
- out.write[i] = first * 16 + second;
+ out_ptrw[i] = first * 16 + second;
}
return out;
#undef HEX_TO_BYTE
@@ -1823,14 +2023,16 @@ CharString String::ascii(bool p_allow_extended) const {
CharString cs;
cs.resize(size());
+ char *cs_ptrw = cs.ptrw();
+ const char32_t *this_ptr = ptr();
for (int i = 0; i < size(); i++) {
- char32_t c = operator[](i);
+ char32_t c = this_ptr[i];
if ((c <= 0x7f) || (c <= 0xff && p_allow_extended)) {
- cs[i] = c;
+ cs_ptrw[i] = c;
} else {
print_unicode_error(vformat("Invalid unicode codepoint (%x), cannot represent as ASCII/Latin-1", (uint32_t)c));
- cs[i] = 0x20; // ascii doesn't have a replacement character like unicode, 0x1a is sometimes used but is kinda arcane
+ cs_ptrw[i] = 0x20; // ASCII doesn't have a replacement character like unicode, 0x1a is sometimes used but is kinda arcane.
}
}
@@ -2963,8 +3165,9 @@ Vector<uint8_t> String::md5_buffer() const {
Vector<uint8_t> ret;
ret.resize(16);
+ uint8_t *ret_ptrw = ret.ptrw();
for (int i = 0; i < 16; i++) {
- ret.write[i] = hash[i];
+ ret_ptrw[i] = hash[i];
}
return ret;
}
@@ -2976,8 +3179,9 @@ Vector<uint8_t> String::sha1_buffer() const {
Vector<uint8_t> ret;
ret.resize(20);
+ uint8_t *ret_ptrw = ret.ptrw();
for (int i = 0; i < 20; i++) {
- ret.write[i] = hash[i];
+ ret_ptrw[i] = hash[i];
}
return ret;
@@ -2990,14 +3194,15 @@ Vector<uint8_t> String::sha256_buffer() const {
Vector<uint8_t> ret;
ret.resize(32);
+ uint8_t *ret_ptrw = ret.ptrw();
for (int i = 0; i < 32; i++) {
- ret.write[i] = hash[i];
+ ret_ptrw[i] = hash[i];
}
return ret;
}
String String::insert(int p_at_pos, const String &p_string) const {
- if (p_at_pos < 0) {
+ if (p_string.is_empty() || p_at_pos < 0) {
return *this;
}
@@ -3005,17 +3210,27 @@ String String::insert(int p_at_pos, const String &p_string) const {
p_at_pos = length();
}
- String pre;
+ String ret;
+ ret.resize(length() + p_string.length() + 1);
+ char32_t *ret_ptrw = ret.ptrw();
+ const char32_t *this_ptr = ptr();
+
if (p_at_pos > 0) {
- pre = substr(0, p_at_pos);
+ memcpy(ret_ptrw, this_ptr, p_at_pos * sizeof(char32_t));
+ ret_ptrw += p_at_pos;
}
- String post;
+ memcpy(ret_ptrw, p_string.ptr(), p_string.length() * sizeof(char32_t));
+ ret_ptrw += p_string.length();
+
if (p_at_pos < length()) {
- post = substr(p_at_pos, length() - p_at_pos);
+ memcpy(ret_ptrw, this_ptr + p_at_pos, (length() - p_at_pos) * sizeof(char32_t));
+ ret_ptrw += length() - p_at_pos;
}
- return pre + p_string + post;
+ *ret_ptrw = 0;
+
+ return ret;
}
String String::erase(int p_pos, int p_chars) const {
@@ -3087,23 +3302,20 @@ int String::find(const String &p_str, int p_from) const {
}
int String::find(const char *p_str, int p_from) const {
- if (p_from < 0) {
+ if (p_from < 0 || !p_str) {
return -1;
}
+ const int src_len = strlen(p_str);
+
const int len = length();
- if (len == 0) {
+ if (len == 0 || src_len == 0) {
return -1; // won't find anything!
}
const char32_t *src = get_data();
- int src_len = 0;
- while (p_str[src_len] != '\0') {
- src_len++;
- }
-
if (src_len == 1) {
const char32_t needle = p_str[0];
@@ -3238,6 +3450,46 @@ int String::findn(const String &p_str, int p_from) const {
return -1;
}
+int String::findn(const char *p_str, int p_from) const {
+ if (p_from < 0) {
+ return -1;
+ }
+
+ int src_len = strlen(p_str);
+
+ if (src_len == 0 || length() == 0) {
+ return -1; // won't find anything!
+ }
+
+ const char32_t *srcd = get_data();
+
+ for (int i = p_from; i <= (length() - src_len); i++) {
+ bool found = true;
+ for (int j = 0; j < src_len; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= length()) {
+ ERR_PRINT("read_pos>=length()");
+ return -1;
+ }
+
+ char32_t src = _find_lower(srcd[read_pos]);
+ char32_t dst = _find_lower(p_str[j]);
+
+ if (src != dst) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
int String::rfind(const String &p_str, int p_from) const {
// establish a limit
int limit = length() - p_str.length();
@@ -3285,6 +3537,57 @@ int String::rfind(const String &p_str, int p_from) const {
return -1;
}
+int String::rfind(const char *p_str, int p_from) const {
+ const int source_length = length();
+ int substring_length = strlen(p_str);
+
+ if (source_length == 0 || substring_length == 0) {
+ return -1; // won't find anything!
+ }
+
+ // establish a limit
+ int limit = length() - substring_length;
+ if (limit < 0) {
+ return -1;
+ }
+
+ // establish a starting point
+ int starting_point;
+ if (p_from < 0) {
+ starting_point = limit;
+ } else if (p_from > limit) {
+ starting_point = limit;
+ } else {
+ starting_point = p_from;
+ }
+
+ const char32_t *source = get_data();
+
+ for (int i = starting_point; i >= 0; i--) {
+ bool found = true;
+ for (int j = 0; j < substring_length; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= source_length) {
+ ERR_PRINT("read_pos>=source_length");
+ return -1;
+ }
+
+ const char32_t key_needle = p_str[j];
+ if (source[read_pos] != key_needle) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
int String::rfindn(const String &p_str, int p_from) const {
// establish a limit
int limit = length() - p_str.length();
@@ -3335,6 +3638,60 @@ int String::rfindn(const String &p_str, int p_from) const {
return -1;
}
+int String::rfindn(const char *p_str, int p_from) const {
+ const int source_length = length();
+ int substring_length = strlen(p_str);
+
+ if (source_length == 0 || substring_length == 0) {
+ return -1; // won't find anything!
+ }
+
+ // establish a limit
+ int limit = length() - substring_length;
+ if (limit < 0) {
+ return -1;
+ }
+
+ // establish a starting point
+ int starting_point;
+ if (p_from < 0) {
+ starting_point = limit;
+ } else if (p_from > limit) {
+ starting_point = limit;
+ } else {
+ starting_point = p_from;
+ }
+
+ const char32_t *source = get_data();
+
+ for (int i = starting_point; i >= 0; i--) {
+ bool found = true;
+ for (int j = 0; j < substring_length; j++) {
+ int read_pos = i + j;
+
+ if (read_pos >= source_length) {
+ ERR_PRINT("read_pos>=source_length");
+ return -1;
+ }
+
+ const char32_t key_needle = p_str[j];
+ int srcc = _find_lower(source[read_pos]);
+ int keyc = _find_lower(key_needle);
+
+ if (srcc != keyc) {
+ found = false;
+ break;
+ }
+ }
+
+ if (found) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
bool String::ends_with(const String &p_string) const {
int l = p_string.length();
if (l > length()) {
@@ -3357,6 +3714,31 @@ bool String::ends_with(const String &p_string) const {
return true;
}
+bool String::ends_with(const char *p_string) const {
+ if (!p_string) {
+ return false;
+ }
+
+ int l = strlen(p_string);
+ if (l > length()) {
+ return false;
+ }
+
+ if (l == 0) {
+ return true;
+ }
+
+ const char32_t *s = &operator[](length() - l);
+
+ for (int i = 0; i < l; i++) {
+ if (static_cast<char32_t>(p_string[i]) != s[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
bool String::begins_with(const String &p_string) const {
int l = p_string.length();
if (l > length()) {
@@ -3380,11 +3762,11 @@ bool String::begins_with(const String &p_string) const {
}
bool String::begins_with(const char *p_string) const {
- int l = length();
if (!p_string) {
return false;
}
+ int l = length();
if (l == 0) {
return *p_string == 0;
}
@@ -3456,14 +3838,61 @@ int String::_count(const String &p_string, int p_from, int p_to, bool p_case_ins
return c;
}
+int String::_count(const char *p_string, int p_from, int p_to, bool p_case_insensitive) const {
+ int substring_length = strlen(p_string);
+ if (substring_length == 0) {
+ return 0;
+ }
+ const int source_length = length();
+
+ if (source_length < substring_length) {
+ return 0;
+ }
+ String str;
+ int search_limit = p_to;
+ if (p_from >= 0 && p_to >= 0) {
+ if (p_to == 0) {
+ search_limit = source_length;
+ } else if (p_from >= p_to) {
+ return 0;
+ }
+ if (p_from == 0 && search_limit == source_length) {
+ str = String();
+ str.copy_from_unchecked(&get_data()[0], source_length);
+ } else {
+ str = substr(p_from, search_limit - p_from);
+ }
+ } else {
+ return 0;
+ }
+ int c = 0;
+ int idx = -1;
+ do {
+ idx = p_case_insensitive ? str.findn(p_string) : str.find(p_string);
+ if (idx != -1) {
+ str = str.substr(idx + substring_length, str.length() - substring_length);
+ ++c;
+ }
+ } while (idx != -1);
+ return c;
+}
+
int String::count(const String &p_string, int p_from, int p_to) const {
return _count(p_string, p_from, p_to, false);
}
+int String::count(const char *p_string, int p_from, int p_to) const {
+ return _count(p_string, p_from, p_to, false);
+}
+
int String::countn(const String &p_string, int p_from, int p_to) const {
return _count(p_string, p_from, p_to, true);
}
+int String::countn(const char *p_string, int p_from, int p_to) const {
+ return _count(p_string, p_from, p_to, true);
+}
+
bool String::_base_is_subsequence_of(const String &p_string, bool case_insensitive) const {
int len = length();
if (len == 0) {
@@ -3505,8 +3934,9 @@ Vector<String> String::bigrams() const {
return b;
}
b.resize(n_pairs);
+ String *b_ptrw = b.ptrw();
for (int i = 0; i < n_pairs; i++) {
- b.write[i] = substr(i, 2);
+ b_ptrw[i] = substr(i, 2);
}
return b;
}
@@ -3598,7 +4028,7 @@ String String::format(const Variant &values, const String &placeholder) const {
Variant v_val = values_arr[i];
String val = v_val;
- if (placeholder.find("_") > -1) {
+ if (placeholder.contains("_")) {
new_string = new_string.replace(placeholder.replace("_", i_as_str), val);
} else {
new_string = new_string.replace_first(placeholder, val);
@@ -3620,76 +4050,208 @@ String String::format(const Variant &values, const String &placeholder) const {
return new_string;
}
-String String::replace(const String &p_key, const String &p_with) const {
- String new_string;
+static String _replace_common(const String &p_this, const String &p_key, const String &p_with, bool p_case_insensitive) {
+ if (p_key.is_empty() || p_this.is_empty()) {
+ return p_this;
+ }
+
+ const int key_length = p_key.length();
+
int search_from = 0;
int result = 0;
- while ((result = find(p_key, search_from)) >= 0) {
- new_string += substr(search_from, result - search_from);
- new_string += p_with;
- search_from = result + p_key.length();
+ LocalVector<int> found;
+
+ while ((result = (p_case_insensitive ? p_this.findn(p_key, search_from) : p_this.find(p_key, search_from))) >= 0) {
+ found.push_back(result);
+ search_from = result + key_length;
}
- if (search_from == 0) {
- return *this;
+ if (found.is_empty()) {
+ return p_this;
+ }
+
+ String new_string;
+
+ const int with_length = p_with.length();
+ const int old_length = p_this.length();
+
+ new_string.resize(old_length + found.size() * (with_length - key_length) + 1);
+
+ char32_t *new_ptrw = new_string.ptrw();
+ const char32_t *old_ptr = p_this.ptr();
+ const char32_t *with_ptr = p_with.ptr();
+
+ int last_pos = 0;
+
+ for (const int &pos : found) {
+ if (last_pos != pos) {
+ memcpy(new_ptrw, old_ptr + last_pos, (pos - last_pos) * sizeof(char32_t));
+ new_ptrw += (pos - last_pos);
+ }
+ if (with_length) {
+ memcpy(new_ptrw, with_ptr, with_length * sizeof(char32_t));
+ new_ptrw += with_length;
+ }
+ last_pos = pos + key_length;
+ }
+
+ if (last_pos != old_length) {
+ memcpy(new_ptrw, old_ptr + last_pos, (old_length - last_pos) * sizeof(char32_t));
+ new_ptrw += old_length - last_pos;
}
- new_string += substr(search_from, length() - search_from);
+ *new_ptrw = 0;
return new_string;
}
-String String::replace(const char *p_key, const char *p_with) const {
- String new_string;
+static String _replace_common(const String &p_this, char const *p_key, char const *p_with, bool p_case_insensitive) {
+ int key_length = strlen(p_key);
+
+ if (key_length == 0 || p_this.is_empty()) {
+ return p_this;
+ }
+
int search_from = 0;
int result = 0;
- while ((result = find(p_key, search_from)) >= 0) {
- new_string += substr(search_from, result - search_from);
- new_string += p_with;
- int k = 0;
- while (p_key[k] != '\0') {
- k++;
+ LocalVector<int> found;
+
+ while ((result = (p_case_insensitive ? p_this.findn(p_key, search_from) : p_this.find(p_key, search_from))) >= 0) {
+ found.push_back(result);
+ search_from = result + key_length;
+ }
+
+ if (found.is_empty()) {
+ return p_this;
+ }
+
+ String new_string;
+
+ // Create string to speed up copying as we can't do `memcopy` between `char32_t` and `char`.
+ const String with_string(p_with);
+ const int with_length = with_string.length();
+ const int old_length = p_this.length();
+
+ new_string.resize(old_length + found.size() * (with_length - key_length) + 1);
+
+ char32_t *new_ptrw = new_string.ptrw();
+ const char32_t *old_ptr = p_this.ptr();
+ const char32_t *with_ptr = with_string.ptr();
+
+ int last_pos = 0;
+
+ for (const int &pos : found) {
+ if (last_pos != pos) {
+ memcpy(new_ptrw, old_ptr + last_pos, (pos - last_pos) * sizeof(char32_t));
+ new_ptrw += (pos - last_pos);
+ }
+ if (with_length) {
+ memcpy(new_ptrw, with_ptr, with_length * sizeof(char32_t));
+ new_ptrw += with_length;
}
- search_from = result + k;
+ last_pos = pos + key_length;
}
- if (search_from == 0) {
- return *this;
+ if (last_pos != old_length) {
+ memcpy(new_ptrw, old_ptr + last_pos, (old_length - last_pos) * sizeof(char32_t));
+ new_ptrw += old_length - last_pos;
}
- new_string += substr(search_from, length() - search_from);
+ *new_ptrw = 0;
return new_string;
}
+String String::replace(const String &p_key, const String &p_with) const {
+ return _replace_common(*this, p_key, p_with, false);
+}
+
+String String::replace(const char *p_key, const char *p_with) const {
+ return _replace_common(*this, p_key, p_with, false);
+}
+
String String::replace_first(const String &p_key, const String &p_with) const {
int pos = find(p_key);
if (pos >= 0) {
- return substr(0, pos) + p_with + substr(pos + p_key.length(), length());
+ const int old_length = length();
+ const int key_length = p_key.length();
+ const int with_length = p_with.length();
+
+ String new_string;
+ new_string.resize(old_length + (with_length - key_length) + 1);
+
+ char32_t *new_ptrw = new_string.ptrw();
+ const char32_t *old_ptr = ptr();
+ const char32_t *with_ptr = p_with.ptr();
+
+ if (pos > 0) {
+ memcpy(new_ptrw, old_ptr, pos * sizeof(char32_t));
+ new_ptrw += pos;
+ }
+
+ if (with_length) {
+ memcpy(new_ptrw, with_ptr, with_length * sizeof(char32_t));
+ new_ptrw += with_length;
+ }
+ pos += key_length;
+
+ if (pos != old_length) {
+ memcpy(new_ptrw, old_ptr + pos, (old_length - pos) * sizeof(char32_t));
+ new_ptrw += (old_length - pos);
+ }
+
+ *new_ptrw = 0;
+
+ return new_string;
}
return *this;
}
-String String::replacen(const String &p_key, const String &p_with) const {
- String new_string;
- int search_from = 0;
- int result = 0;
+String String::replace_first(const char *p_key, const char *p_with) const {
+ int pos = find(p_key);
+ if (pos >= 0) {
+ const int old_length = length();
+ const int key_length = strlen(p_key);
+ const int with_length = strlen(p_with);
- while ((result = findn(p_key, search_from)) >= 0) {
- new_string += substr(search_from, result - search_from);
- new_string += p_with;
- search_from = result + p_key.length();
- }
+ String new_string;
+ new_string.resize(old_length + (with_length - key_length) + 1);
- if (search_from == 0) {
- return *this;
+ char32_t *new_ptrw = new_string.ptrw();
+ const char32_t *old_ptr = ptr();
+
+ if (pos > 0) {
+ memcpy(new_ptrw, old_ptr, pos * sizeof(char32_t));
+ new_ptrw += pos;
+ }
+
+ for (int i = 0; i < with_length; ++i) {
+ *new_ptrw++ = p_with[i];
+ }
+ pos += key_length;
+
+ if (pos != old_length) {
+ memcpy(new_ptrw, old_ptr + pos, (old_length - pos) * sizeof(char32_t));
+ new_ptrw += (old_length - pos);
+ }
+
+ *new_ptrw = 0;
+
+ return new_string;
}
- new_string += substr(search_from, length() - search_from);
- return new_string;
+ return *this;
+}
+
+String String::replacen(const String &p_key, const String &p_with) const {
+ return _replace_common(*this, p_key, p_with, true);
+}
+
+String String::replacen(const char *p_key, const char *p_with) const {
+ return _replace_common(*this, p_key, p_with, true);
}
String String::repeat(int p_count) const {
@@ -3983,10 +4545,7 @@ String String::simplify_path() const {
dirs.remove_at(i);
i--;
} else if (d == "..") {
- if (i == 0) {
- dirs.remove_at(i);
- i--;
- } else {
+ if (i != 0) {
dirs.remove_at(i);
dirs.remove_at(i - 1);
i -= 2;
@@ -4065,7 +4624,7 @@ bool String::is_absolute_path() const {
}
}
-String String::validate_identifier() const {
+String String::validate_ascii_identifier() const {
if (is_empty()) {
return "_"; // Empty string is not a valid identifier;
}
@@ -4088,7 +4647,7 @@ String String::validate_identifier() const {
return result;
}
-bool String::is_valid_identifier() const {
+bool String::is_valid_ascii_identifier() const {
int len = length();
if (len == 0) {
@@ -4110,6 +4669,26 @@ bool String::is_valid_identifier() const {
return true;
}
+bool String::is_valid_unicode_identifier() const {
+ const char32_t *str = ptr();
+ int len = length();
+
+ if (len == 0) {
+ return false; // Empty string.
+ }
+
+ if (!is_unicode_identifier_start(str[0])) {
+ return false;
+ }
+
+ for (int i = 1; i < len; i++) {
+ if (!is_unicode_identifier_continue(str[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
bool String::is_valid_string() const {
int l = length();
const char32_t *src = get_data();
@@ -4356,8 +4935,9 @@ String String::xml_unescape() const {
return String();
}
str.resize(len + 1);
- _xml_unescape(get_data(), l, str.ptrw());
- str[len] = 0;
+ char32_t *str_ptrw = str.ptrw();
+ _xml_unescape(get_data(), l, str_ptrw);
+ str_ptrw[len] = 0;
return str;
}
@@ -4420,6 +5000,15 @@ String String::trim_prefix(const String &p_prefix) const {
return s;
}
+String String::trim_prefix(const char *p_prefix) const {
+ String s = *this;
+ if (s.begins_with(p_prefix)) {
+ int prefix_length = strlen(p_prefix);
+ return s.substr(prefix_length, s.length() - prefix_length);
+ }
+ return s;
+}
+
String String::trim_suffix(const String &p_suffix) const {
String s = *this;
if (s.ends_with(p_suffix)) {
@@ -4428,6 +5017,14 @@ String String::trim_suffix(const String &p_suffix) const {
return s;
}
+String String::trim_suffix(const char *p_suffix) const {
+ String s = *this;
+ if (s.ends_with(p_suffix)) {
+ return s.substr(0, s.length() - strlen(p_suffix));
+ }
+ return s;
+}
+
bool String::is_valid_int() const {
int len = length();
@@ -4903,6 +5500,11 @@ String String::lpad(int min_length, const String &character) const {
// "fish %s %d pie" % ["frog", 12]
// In case of an error, the string returned is the error description and "error" is true.
String String::sprintf(const Array &values, bool *error) const {
+ static const String ZERO("0");
+ static const String SPACE(" ");
+ static const String MINUS("-");
+ static const String PLUS("+");
+
String formatted;
char32_t *self = (char32_t *)get_data();
bool in_format = false;
@@ -4925,7 +5527,7 @@ String String::sprintf(const Array &values, bool *error) const {
if (in_format) { // We have % - let's see what else we get.
switch (c) {
case '%': { // Replace %% with %
- formatted += chr(c);
+ formatted += c;
in_format = false;
break;
}
@@ -4975,7 +5577,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Padding.
int pad_chars_count = (negative || show_sign) ? min_chars - 1 : min_chars;
- String pad_char = pad_with_zeros ? String("0") : String(" ");
+ const String &pad_char = pad_with_zeros ? ZERO : SPACE;
if (left_justified) {
str = str.rpad(pad_chars_count, pad_char);
} else {
@@ -4984,7 +5586,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Sign.
if (show_sign || negative) {
- String sign_char = negative ? "-" : "+";
+ const String &sign_char = negative ? MINUS : PLUS;
if (left_justified) {
str = str.insert(0, sign_char);
} else {
@@ -5021,7 +5623,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Padding. Leave room for sign later if required.
int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars;
- String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros
+ const String &pad_char = (pad_with_zeros && is_finite) ? ZERO : SPACE; // Never pad NaN or inf with zeros
if (left_justified) {
str = str.rpad(pad_chars_count, pad_char);
} else {
@@ -5030,7 +5632,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Add sign if needed.
if (show_sign || is_negative) {
- String sign_char = is_negative ? "-" : "+";
+ const String &sign_char = is_negative ? MINUS : PLUS;
if (left_justified) {
str = str.insert(0, sign_char);
} else {
@@ -5083,7 +5685,7 @@ String String::sprintf(const Array &values, bool *error) const {
// Padding. Leave room for sign later if required.
int pad_chars_count = val < 0 ? min_chars - 1 : min_chars;
- String pad_char = (pad_with_zeros && is_finite) ? String("0") : String(" "); // Never pad NaN or inf with zeros
+ const String &pad_char = (pad_with_zeros && is_finite) ? ZERO : SPACE; // Never pad NaN or inf with zeros
if (left_justified) {
number_str = number_str.rpad(pad_chars_count, pad_char);
} else {
@@ -5093,9 +5695,9 @@ String String::sprintf(const Array &values, bool *error) const {
// Add sign if needed.
if (val < 0) {
if (left_justified) {
- number_str = number_str.insert(0, "-");
+ number_str = number_str.insert(0, MINUS);
} else {
- number_str = number_str.insert(pad_with_zeros ? 0 : number_str.length() - initial_len, "-");
+ number_str = number_str.insert(pad_with_zeros ? 0 : number_str.length() - initial_len, MINUS);
}
}
@@ -5260,7 +5862,7 @@ String String::sprintf(const Array &values, bool *error) const {
in_decimals = false;
break;
default:
- formatted += chr(c);
+ formatted += c;
}
}
}
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 693df6dcba..11f15031f9 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -198,6 +198,7 @@ class String {
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 _camelcase_to_underscore() const;
public:
@@ -288,14 +289,18 @@ public:
int find(const char *p_str, int p_from = 0) const; ///< return <0 if failed
int find_char(const char32_t &p_char, int p_from = 0) const; ///< return <0 if failed
int 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 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;
@@ -304,9 +309,11 @@ public:
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 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;
@@ -314,7 +321,9 @@ public:
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;
@@ -353,11 +362,15 @@ public:
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() 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;
@@ -372,7 +385,9 @@ public:
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;
@@ -414,6 +429,8 @@ public:
_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
_FORCE_INLINE_ bool contains(const char *p_str) const { return find(p_str) != -1; }
_FORCE_INLINE_ bool contains(const String &p_str) const { return find(p_str) != -1; }
+ _FORCE_INLINE_ bool 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;
@@ -442,10 +459,11 @@ public:
// node functions
static String get_invalid_node_name_characters(bool p_allow_internal = false);
String validate_node_name() const;
- String validate_identifier() const;
+ String validate_ascii_identifier() const;
String validate_filename() const;
- bool is_valid_identifier() const;
+ bool is_valid_ascii_identifier() const;
+ bool is_valid_unicode_identifier() const;
bool is_valid_int() const;
bool is_valid_float() const;
bool is_valid_hex_number(bool p_with_prefix) const;
@@ -453,6 +471,9 @@ public:
bool is_valid_ip_address() const;
bool is_valid_filename() const;
+ // Use `is_valid_ascii_identifier()` instead. Kept for compatibility.
+ bool is_valid_identifier() const { return is_valid_ascii_identifier(); }
+
/**
* The constructors must not depend on other overloads
*/