diff options
Diffstat (limited to 'core/string')
-rw-r--r-- | core/string/char_range.inc | 663 | ||||
-rw-r--r-- | core/string/char_utils.h | 6 | ||||
-rw-r--r-- | core/string/node_path.cpp | 10 | ||||
-rw-r--r-- | core/string/string_name.cpp | 9 | ||||
-rw-r--r-- | core/string/string_name.h | 8 | ||||
-rw-r--r-- | core/string/translation.compat.inc | 5 | ||||
-rw-r--r-- | core/string/translation.cpp | 921 | ||||
-rw-r--r-- | core/string/translation.h | 130 | ||||
-rw-r--r-- | core/string/translation_po.cpp | 90 | ||||
-rw-r--r-- | core/string/translation_po.h | 14 | ||||
-rw-r--r-- | core/string/translation_server.compat.inc | 38 | ||||
-rw-r--r-- | core/string/translation_server.cpp | 947 | ||||
-rw-r--r-- | core/string/translation_server.h | 164 | ||||
-rw-r--r-- | core/string/ustring.cpp | 804 | ||||
-rw-r--r-- | core/string/ustring.h | 25 |
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 */ |