File size: 72,490 Bytes
b6a38d7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 |
---
--- Specifies a comment that indicates the localization system should ignore the following code block.
--- This comment is used to mark code that should not be localized, such as code that generates localization keys or other metadata.
---
--- @field localization_ignore_header string The header comment that indicates a block of code should be ignored by the localization system.
---
localization_ignore_header = "-- [[localization-ignore]]"
-- command line tool shield
---
--- Provides access to the global `const.TagLookupTable` table, which is used for looking up tag information.
---
--- @type table
--- @field TagLookupTable table The global table that stores tag lookup information.
---
const.TagLookupTable = const.TagLookupTable or {}
local TagLookupTable = const.TagLookupTable
local type = type
local getmetatable = getmetatable
local setmetatable = setmetatable
---
--- Stores a table of random localization IDs.
---
RandomLocIds = {}
---
--- Specifies whether errors should be ignored when using the localization system.
---
--- @field TIgnoreErrors boolean If true, errors will be ignored when using the localization system.
---
local TIgnoreErrors = false
---
--- Converts a localization ID to a light userdata value that can be used to represent the localization ID.
---
--- @param id number The localization ID to convert.
--- @return lightuserdata The light userdata value representing the localization ID.
---
function LocIDToLightUserdata(id)
return id and LightUserData(bor(id, locId_sig))
end
local locId_sig = shift(0xff, 56)
local locId_mask = bnot(locId_sig)
local function LocIDToLightUserdata(id)
return id and LightUserData(bor(id, locId_sig))
end
---
--- Converts a light userdata value back to a localization ID.
---
--- @param value lightuserdata The light userdata value to convert.
--- @return number|nil The localization ID, or nil if the value is not a valid localization ID.
---
function LightUserdataToLocId(value)
value = LightUserDataValue(value)
if value and band(value, locId_sig) == locId_sig then
return band(value, locId_mask)
end
end
local function LightUserdataToLocId(value)
value = LightUserDataValue(value)
if value and band(value, locId_sig) == locId_sig then
return band(value, locId_mask)
end
end
-- checks for any object that may be used where a localized string is expected:
-- 1. Ts
-- 2. concatenated Ts
-- 3. strings that have only tags and punctuation
---
--- Checks if a given value is compatible with the localization system.
---
--- @param T any The value to check for compatibility.
--- @return boolean True if the value is compatible, false otherwise.
---
function IsTCompatible(T)
return true
end
local function IsTCompatible()
return true
end
---
--- Checks if a given value is compatible with the localization system.
---
--- @param T any The value to check for compatibility.
--- @return boolean True if the value is compatible, false otherwise.
---
function IsTCompatible(T)
return
LightUserdataToLocId(T) or
type(T) == "number" or
type(T) == "function" or
type(T) == "string" and IsTagsAndPunctuation(T) or
type(T) == "table" and (getmetatable(T) == TMeta or getmetatable(T) == TConcatMeta)
end
if Platform.debug then
IsTCompatible = function(T)
return
LightUserdataToLocId(T) or
type(T) == "number" or
type(T) == "function" or
type(T) == "string" and IsTagsAndPunctuation(T) or
type(T) == "table" and (getmetatable(T) == TMeta or getmetatable(T) == TConcatMeta)
end
end
-- checks whether the value is a localized string produced by the T function (or a UserText, which is compatible with Ts)
---
--- Checks if a given value is compatible with the localization system.
---
--- @param T any The value to check for compatibility.
--- @return boolean True if the value is compatible, false otherwise.
---
function IsT(T)
return
T == "" or
LightUserdataToLocId(T) or
type(T) == "table" and (getmetatable(T) == TMeta or getmetatable(T) == TConcatMeta)
end
---
--- Checks if a given value is a UserText object.
---
--- @param T any The value to check.
--- @return boolean True if the value is a UserText object, false otherwise.
---
function IsUserText(T)
return type(T) == "table" and getmetatable(T) == TMeta and T._language ~= nil
end
-- T = "" or userdata or { string,...} or { id, string,...}
---
--- Gets the ID of a localized string.
---
--- @param T any The localized string or table containing the localized string.
--- @return number|boolean The ID of the localized string, or false if the input is an empty string.
---
function TGetID(T)
if T == "" then
return false
end
local value = LightUserdataToLocId(T)
if value then
return value
end
if type(T[1]) == "number" then
return T[1]
elseif type(T[1]) == "table" then
return TGetID(T[1])
else
return LightUserdataToLocId(T[1])
end
end
-- T = "" or { string,...} or { id, string,...}
-- debug mode only (otherwise English texts are stripped)
-- if 'deep' handles the case when another T is used as the string
---
--- Gets the English text for a localized string, even in non-debug mode.
---
--- @param T any The localized string or table containing the localized string.
--- @param deep boolean (optional) If true, recursively gets the English text for nested localized strings.
--- @param no_assert boolean (optional) If true, skips the assertion check for the input.
--- @return string The English text for the localized string.
---
function TDevModeGetEnglishText(T, deep, no_assert)
if T == "" then
return ""
end
local no_assert = no_assert or not Platform.pc or Platform.ged
assert(no_assert or Platform.debug and IsT(T))
if type(T) ~= "table" --[[shield for non-debug mode]] then
local id = LightUserdataToLocId(T)
return id and TranslationTable[id] or "Missing text"
end
local ret = type(T[1]) == "number" and T[2] or T[1]
ret = deep and type(ret) ~= "string" and TDevModeGetEnglishText(ret, true, no_assert) or ret
if not Platform.debug and type(ret)=="string" then
ret = ret:gsub("%(design%)%s*", ""):gsub("%(minor%)%s*", "")
end
return ret
end
---
--- Sorts a table of elements by a specified field or a custom sorting function.
---
--- @param t table The table to sort.
--- @param field string|function (optional) The field to sort by, or a custom sorting function.
--- @param case_insensitive boolean (optional) If true, the sorting will be case-insensitive.
---
function TSort(t, field, case_insensitive)
local sortkey_internal_translation
if type(field) == "function" then
for i = 1, #t do
sortkey_internal_translation = _InternalTranslate(field(t[i]))
if Platform.pc then
t[i].__sort_key = utf8.ToUtf16(sortkey_internal_translation)
else
t[i].__sort_key = sortkey_internal_translation
end
end
else
for i = 1, #t do
sortkey_internal_translation = _InternalTranslate(t[i][field])
if Platform.pc then
t[i].__sort_key = utf8.ToUtf16(sortkey_internal_translation)
else
t[i].__sort_key = sortkey_internal_translation
end
end
end
if Platform.pc then
local lang = table.find_value(AllLanguages, "value", GetLanguage())
local wchar_locale = utf8.ToUtf16(lang and lang.locale or "en-US")
table.stable_sort(t, function(a,b) return LocaleCmp(a.__sort_key, b.__sort_key, wchar_locale, case_insensitive) end)
else
if case_insensitive then
table.stable_sort(t, function(a,b) return CmpLower(a.__sort_key, b.__sort_key) end)
else
table.stable_sort(t, function(a,b) return a.__sort_key < b.__sort_key end)
end
end
for i = 1, #t do
t[i].__sort_key = nil
end
end
---
--- Checks if a given string contains only tags and punctuation, without any word characters.
---
--- @param str string The input string to check.
--- @return boolean True if the string contains only tags and punctuation, false otherwise.
---
function IsTagsAndPunctuation(str)
local untagged, tag, first, last = str:nexttag(1)
while tag do
if untagged:find("%w") then
return false
end
untagged, tag, first, last = str:nexttag(last+1)
end
return not untagged:find("%w")
end
---
--- Checks if a given string contains only tags and punctuation, without any word characters.
---
--- @param str string The input string to check.
--- @return boolean True if the string contains only tags and punctuation, false otherwise.
---
function IsLookupTag(str)
local untagged, tag, first, last = str:nexttag(1)
if not tag then return false end
while tag do
if untagged:find("%w") then
return false
end
if not TagLookupTable[tag] then return false end
untagged, tag, first, last = str:nexttag(last+1)
end
return not untagged:find("%w")
end
---
--- Checks if a given table contains any arguments besides the first one (which is assumed to be the ID).
---
--- @param T table The input table to check.
--- @return boolean True if the table contains any additional arguments, false otherwise.
---
function THasArgs(T)
if type(T) == "table" then
local hasID = type(T[1]) == "number"
for k,v in pairs(T) do
if k ~= 1 and (not hasID or k ~= 2) and k ~= "untranslated" and k ~= "_steam_id" and k ~= "_language" then
return true
end
if THasArgs(v) then
return true
end
end
end
return false
end
---
--- Recursively strips any additional arguments from a localization table.
---
--- @param _T table The localization table to strip arguments from.
--- @return table The localization table with only the ID and localized text.
---
function TStripArgs(_T)
if type(_T) == "table" then
local hasID = type(_T[1]) == "number"
if hasID then
return T{_T[1], TStripArgs(_T[2])}
else
return T{TStripArgs(_T[1])}
end
end
return _T
end
local gender_offset = {
[false] = 0,
["m"] = 0,
["M"] = 0,
["Male"] = 0,
["f"] = 1,
["F"] = 1,
["Female"] = 1,
["n"] = 2,
["N"] = 2,
}
---
--- Changes the localization ID to a gender-specific ID based on the provided gender.
---
--- @param id number The original localization ID.
--- @param gender string|table The gender to use for the ID change. Can be a string ("m", "f", "n") or a table with a "Gender" field.
--- @return number The new gender-specific localization ID, or the original ID if the gender is invalid.
---
function GenderChangedID(id, gender)
if type(id) ~= "number" then return id end
if IsT(gender) then
gender = GetTGender(gender)
elseif type(gender) == "table" then
gender = gender.Gender
end
local offset = gender_offset[gender or false]
assert(offset)
local new_id = id + (offset or 0)
return TranslationTable[new_id] and new_id or id
end
-- "T=function" syntax used to avoid LocExtract scanning of T("text") syntax
---
--- Translates a localization ID or table into a localized string, handling gender-specific localization and random localization IDs.
---
--- @param T table|number The localization ID or table to translate.
--- @param Ttext string The localized text to use if `T` is a number.
--- @param gender string|table The gender to use for gender-specific localization. Can be a string ("m", "f", "n") or a table with a "Gender" field.
--- @return string The localized string.
---
T = function(T, Ttext, gender)
if type(T) == "table" then
if getmetatable(T[1]) == TConcatMeta then
return T[1]
end
local id = T[1]
local text = type(id) == "number" and T[2] or T[1]
-- we can use the dev.mode function here before processing the T
if text == "" then
return "" -- this is here to allow checking localized strings for == "" and ~= ""
end
if type(id) == "number" and type(text) == "string" then
if IsRandomLocId(id) then
RandomLocIds[id] = true
end
gender = T.TGender
if gender then
assert(not T.__gender_updated) -- the same T should not be passed more than once to this function
dbg(rawset(T, "__gender_updated", true)) -- mark the T so we can recognise it if we get it again
T[1] = GenderChangedID(id, gender)
end
if not Platform.debug and not Platform.ged and TranslationTable[id] and not THasArgs(T) then
return LocIDToLightUserdata(id)
end
end
return setmetatable(T, TMeta)
else
local id = T
local text = type(id) == "number" and Ttext or T
if text == "" then
return ""
end
if type(id) == "number" and type(text) == "string" then
if IsRandomLocId(id) then
RandomLocIds[id] = true
end
if gender then
id = GenderChangedID(id, gender)
end
if not Platform.debug and not Platform.ged and TranslationTable[id] then
return LocIDToLightUserdata(id)
end
end
return setmetatable({T, Ttext}, TMeta)
end
end
local locId_random_start = 100000000000
local locId_random_range = 899999000000
---
--- Checks if the given ID is a random localization ID.
---
--- @param id number The ID to check.
--- @return boolean True if the ID is a random localization ID, false otherwise.
---
function IsRandomLocId(id)
if type(id) == "number" then
id = id - locId_random_start
return id >= 0 and id < locId_random_range
end
end
---
--- Generates a random localization ID that has not been used before.
---
--- @return number A unique random localization ID.
---
function RandomLocId()
for i = 1, 1000 do
local id = locId_random_start + AsyncRand(locId_random_range)
if not RandomLocIds[id] and not RandomLocIds[id - 1] and not RandomLocIds[id - 2] and not RandomLocIds[id + 1] and not RandomLocIds[id + 2] then
RandomLocIds[id] = true
return id
end
end
assert(not "Failed to allocate translation ID, ID range full?") -- highly unlikely as range is huge; probably something more nefarious
end
---
--- Converts the given value to a localized string, marking it as untranslated.
---
--- @param _T any The value to convert to a localized string.
--- @return table A localized string table with the untranslated flag set.
---
function Untranslated(_T)
if IsT(_T) then return _T end
if type(_T) == "table" then return T(_T) end
assert(not _T or type(_T) == "string" or type(_T) == "number")
assert(type(_T) ~= "string" or not IsLookupTag(_T), "In this case you should use TLookupTag('<tag>') instead of Untranslated('<tag>')")
return T{tostring(_T or ""), untranslated = true}
end
-- This exists so we can always clothe tags with T{} (even in non-debug)
---
--- Converts a lookup tag to a localized string.
---
--- @param _T string The lookup tag to convert.
--- @return table A localized string table with the lookup tag.
---
function TLookupTag(_T)
assert(IsLookupTag(_T), "Use TLookupTag only for lookup tags")
return T{tostring(_T)}
end
---
--- Initializes the localization system on first load.
---
--- This code sets up the metatable for localized strings (`TMeta`) and the metatable for concatenated localized strings (`TConcatMeta`). It also initializes the `TranslationTable` and `TranslationGenderTable` dictionaries, and sets the `g_ignore_translation_errors` flag to `false`.
---
--- This code is executed only on the first load of the module, and is responsible for setting up the necessary infrastructure for the localization system.
---
if FirstLoad then
TMeta = { __name = "T" }
TConcatMeta = { __name = "T(concat)" }
LightUserDataSetMetatable(TMeta)
oldTableConcat = table.concat
TranslationTable = {}
TranslationGenderTable = {}
g_ignore_translation_errors = false
end
---
--- Persists the localization metatable definitions to the given permanents table.
---
--- This function is called during the game's persistence system to ensure that the
--- localization system's metatable definitions are properly serialized and restored
--- when the game is loaded.
---
--- @param permanents table The table to store the metatable definitions in.
---
function OnMsg.PersistGatherPermanents(permanents)
permanents["T.meta"] = TMeta
permanents["TConcat.meta"] = TConcatMeta
permanents["func:type"] = type
end
---
--- Concatenates two localized string values.
---
--- This function is the implementation of the `__concat` metamethod for the `TMeta` metatable.
--- It handles the concatenation of two localized string values, converting them to a table of localized strings if necessary.
---
--- @param T1 table|string A localized string value or a table of localized strings to concatenate.
--- @param T2 table|string A localized string value or a table of localized strings to concatenate.
--- @return table A new table of localized strings, representing the concatenation of `T1` and `T2`.
---
TMeta.__concat = function(T1, T2)
-- convert first parameter to a concatenated T type
if IsTCompatible(T1) then
if type(T1) == "table" and getmetatable(T1) == TConcatMeta then
T1 = table.copy(T1)
else
T1 = { T1 }
end
elseif type(T1) == "string" then
assert(false, string.format("Attempt to concatenate plain text or numbers '%s' to a localized string", T1), 1)
T1 = { T1 }
else
assert(false, string.format("Attempt to concatenate invalid value '%s' to a localized string", tostring(T1)), 1)
return T2
end
-- append the second parameter into the "concatenated T" table
if IsTCompatible(T2) then
if type(T2) == "table" and getmetatable(T2) == TConcatMeta then
local num = #T1
for i = 1, #T2 do
T1[num+i] = T2[i]
end
else
T1[1+#T1] = T2
end
elseif type(T2) == "string" then
assert(false, string.format("Attempt to concatenate plain text or numbers '%s' to a localized string", T2), 1)
T1[1+#T1] = T2
else
assert(false, string.format("Attempt to concatenate invalid value '%s' to a localized string", tostring(T2)), 1)
end
return setmetatable(T1, TConcatMeta)
end
---
--- Prevents modifying localized strings.
---
--- This function is the implementation of the `__newindex` metamethod for the `TMeta` metatable.
--- It asserts that modifying localized strings is forbidden, as in Gold Master they could be a userdata instead of a table.
---
TMeta.__newindex = function()
assert(false, "Modifying localized strings is forbidden - in Gold Master they could be a userdata instead of a table", 1)
end
---
--- Provides a copy of the localized string table.
---
--- This function is the implementation of the `__copy` metamethod for the `TMeta` metatable.
--- It allows the localized string table to be copied using the `table.copy` function.
---
--- @param self table The localized string table to be copied.
--- @return table A new table that is a copy of the localized string table.
---
TMeta.__copy = function(self)
return self -- support for table.copy - treat Ts as simple values
end
---
--- Converts the localized string table to Lua code.
---
--- This function is the implementation of the `__toluacode` metamethod for the `TConcatMeta` metatable.
--- It generates Lua code that represents the concatenated list of localized strings.
---
--- @param self table The concatenated list of localized strings to be converted to Lua code.
--- @param indent string|number The indentation level for the generated Lua code.
--- @param pstr string An optional string to which the generated Lua code will be appended.
--- @return string The generated Lua code that represents the concatenated list of localized strings.
---
TMeta.__toluacode = function(self, indent, pstr)
return TToLuaCode(self, ContextCache[self], pstr)
end
---
--- Compares two localized strings for equality.
---
--- This function is the implementation of the `__eq` metamethod for the `TMeta` metatable.
--- It compares two localized strings for equality by checking if they are both `T` values and if their English text is the same.
---
--- @param op1 table The first localized string to compare.
--- @param op2 table The second localized string to compare.
--- @return boolean `true` if the two localized strings are equal, `false` otherwise.
---
TMeta.__eq = function(op1, op2)
return IsT(op1) and IsT(op2) and TDevModeGetEnglishText(op1, not "deep", "no assert") == TDevModeGetEnglishText(op2, not "deep", "no assert")
end
---
--- Serializes a localized string table for transmission over the network.
---
--- This function is the implementation of the `__serialize` metamethod for the `TMeta` metatable.
--- It asserts that only `UserText` `T` values should be serialized and sent over the network, and returns a table containing the serialized data.
---
--- @param T table The localized string table to be serialized.
--- @return string, table The serialized data, which includes the metatable name and a raw copy of the table.
---
TMeta.__serialize = function(T)
assert(IsUserText(T), "Only UserText T values should go through the network.")
return "TMeta", table.raw_copy(T)
end
---
--- Deserializes a localized string table that was serialized for transmission over the network.
---
--- This function is the implementation of the `__unserialize` metamethod for the `TMeta` metatable.
--- It asserts that only `UserText` `T` values should be deserialized and received over the network, and returns the deserialized table.
---
--- @param serialized_data table The serialized data, which includes the metatable name and a raw copy of the table.
--- @return table The deserialized localized string table.
---
TMeta.__unserialize = function(serialized_data)
local T = setmetatable(serialized_data, TMeta)
assert(IsUserText(T), "Only UserText T values should go through the network.")
return T
end
ContextCache = {}
---
--- Concatenates two localized strings.
---
--- This function is the implementation of the `__concat` metamethod for the `TConcatMeta` metatable.
--- It concatenates two localized strings, ensuring that the resulting string is also a localized string.
---
--- @param op1 table The first localized string to concatenate.
--- @param op2 table The second localized string to concatenate.
--- @return table The concatenated localized string.
---
TConcatMeta.__concat = TMeta.__concat
---
--- Prevents modifying a concatenated list of localized strings.
---
--- This function is the implementation of the `__newindex` metamethod for the `TConcatMeta` metatable.
--- It asserts that attempting to modify a concatenated list of localized strings is not allowed.
---
--- @param ... any Arguments passed to the `__newindex` metamethod.
---
TConcatMeta.__newindex = function(...)
assert(false, "Attempt to modify a concatenated list of localized strings", 1)
end
TConcatMeta.__newindex = function()
assert(false, "Attempt to modify a concatenated list of localized strings", 1)
end
---
--- Generates Lua code for a concatenated list of localized strings.
---
--- This function is the implementation of the `__toluacode` metamethod for the `TConcatMeta` metatable.
--- It generates Lua code that creates a `TConcat` object from the list of localized strings in the `self` table.
---
--- @param self table The concatenated list of localized strings.
--- @param indent string|number The indentation level for the generated Lua code.
--- @param pstr string An optional string to prepend to the generated Lua code.
--- @return string The generated Lua code for the concatenated list of localized strings.
---
TConcatMeta.__toluacode = function(self, indent, pstr)
-- ...
end
TConcatMeta.__toluacode = function(self, indent, pstr) -- for T_list properties
local lines, context = {}, ContextCache[self]
for _, value in ipairs(self) do
lines[#lines + 1] = TToLuaCode(value, context)
end
if type(indent) ~= "string" then
indent = string.rep("\t", indent or 0)
end
lines = "{\n\t" .. indent .. table.concat(lines, ",\n\t" .. indent) .. "\n" .. indent .. "}"
if pstr then
return pstr:append("TConcat(", lines, ")")
else
return string.format("TConcat(%s)", lines)
end
end
---
--- Creates a new `TConcat` object from the given table.
---
--- The `TConcat` object is a metatable-wrapped table that represents a concatenated list of localized strings. This function is used to create such objects, which can be used to safely concatenate localized strings without modifying the original strings.
---
--- @param table table The table of localized strings to be concatenated.
--- @return table A new `TConcat` object representing the concatenated localized strings.
---
function TConcat(table)
return setmetatable(table, TConcatMeta)
end
-- supports concatenation of Ts and concatenated Ts only (can't intermix with plain strings/numbers)
---
--- Concatenates a table of localized strings, handling special cases such as concatenating `TConcat` objects and ensuring that the separator is a localized string.
---
--- @param t table The table of localized strings to concatenate.
--- @param sep string|TConcat The separator to use between the localized strings.
--- @param i number The starting index of the table to concatenate.
--- @param j number The ending index of the table to concatenate.
--- @return string The concatenated string of localized strings.
---
function table.concat(t, sep, i, j)
if not next(t) then return "" end
i = i or 1
j = j or #t
local idx, item = i, t[i]
if i == j then return item end
while item == "" and idx < j do
idx = idx + 1
item = t[idx]
end
if IsT(item) and item ~= "" then
for n = i, j do
local item = t[n]
assert(IsT(item), "All items in table.concat must be localized strings")
end
assert(not sep or IsTCompatible(sep), "Separator in table.concat must be a localized string or tags&punctuation only")
return setmetatable({ setmetatable({table = t, sep = sep, i = i, j = j}, TConcatMeta) }, TConcatMeta)
end
if IsT(sep) then
sep = _InternalTranslate(sep)
end
return oldTableConcat(t, sep, i, j)
end
-- first look in the nested Ts, then in the T table itself
---
--- Recursively evaluates an identifier in the context of a localized string.
---
--- This function is used to resolve identifiers within localized strings, such as parameters or member access. It first looks for the identifier in the inner `T` objects, then in the direct parameters of the `T` object, then in the `context_obj`, and finally in the `TagLookupTable`.
---
--- @param T table The localized string object.
--- @param context_obj table The context object to use for resolving identifiers.
--- @param id string The identifier to evaluate.
--- @return any The value of the evaluated identifier.
---
local function evalIdentifier(T, context_obj, id)
local value
if type(T) == "table" then
local format_string_index = type(T[1]) == "number" and 2 or 1
-- 1. Recursively try the parameters of the inner T
local innerT = T[format_string_index]
if IsT(innerT) then
value = evalIdentifier(innerT, context_obj, id)
end
-- 2. Try direct parameters
value = value or T[id]
-- 3. Try context_obj
if not value and context_obj then
value = ResolveValue(context_obj, id)
end
if not value then
-- 4. Look into parameters passed in tables
for j = format_string_index + 1, #T do
local obj = T[j]
if context_obj ~= obj then
value = ResolveValue(obj, id)
if value then break end
end
end
end
else
-- 3. Try context_obj
if not value and context_obj then
value = ResolveValue(context_obj, id)
end
end
-- 5. apply TagLookupTable
if not value then -- carefully avoid changing 'false' to 'nil'
local lookup = TagLookupTable[id]
if lookup then
value = lookup
end
end
-- 6. call if func
if type(value) == "function" then
value = value(context_obj)
end
return value
end
---
--- Recursively evaluates a string of identifiers, resolving each identifier against the provided `context_obj`.
---
--- @param T table The table containing the identifiers to evaluate.
--- @param context_obj table The context object to use for resolving identifiers.
--- @param ids string The string of identifiers to evaluate.
--- @return table The final resolved context object.
---
function evalIdentifiers(T, context_obj, ids)
-- Implementation details...
end
local function evalIdentifiers(T, context_obj, ids)
local first = 1
while first do
local rest = ids:find(".", first, true)
context_obj = evalIdentifier(T, context_obj, ids:sub(first, (rest or 0) - 1))
first = rest and rest + 1
end
return context_obj
end
---
--- Evaluates a function call with the provided parameters.
---
--- @param T table The table containing the function to call.
--- @param context_obj table The context object to use for resolving identifiers in the parameters.
--- @param fn string The name of the function to call.
--- @param tag string The full tag string containing the function call and parameters.
--- @param param_start number The starting index of the parameters in the tag string.
--- @return any The result of the function call.
---
local function evalFunctionCall(T, context_obj, fn, tag, param_start)
-- Implementation details...
end
local evalFunctionCall
---
--- Evaluates the parameters in a tag string and returns them as a list of values.
---
--- @param T table The table containing the identifiers and functions to evaluate.
--- @param context_obj table The context object to use for resolving identifiers in the parameters.
--- @param tag string The full tag string containing the parameters.
--- @param start number The starting index of the parameters in the tag string.
--- @return any, any The evaluated parameters and the remaining part of the tag string.
---
local function evalParams(T, context_obj, tag, start)
local param, cont = tag:match("^%s*([%a_][%w_.]*)%s*[,)]()", start)
if param == "true" or param == "false" then -- bool
param = param == "true"
return param, evalParams(T, context_obj, tag, cont)
end
if param then -- identifiers? normal lookup in T params, members of T objs etc.
return evalIdentifiers(T, context_obj, param), evalParams(T, context_obj, tag, cont)
end
local param_start
param, param_start, cont = tag:match("^%s*([%a_][%w_]*)()%b()%s*[,)]()", start)
if param then -- function call? normal lookup in T params, members of T objs etc.
return evalFunctionCall(T, context_obj, param, tag, param_start + 1), evalParams(T, context_obj, tag, cont)
end
param, cont = tag:match("^%s*%'(.-)%'%s*[,)]()", start)
if param then -- literal string in ''s
return param, evalParams(T, context_obj, tag, cont)
end
param, cont = tag:match("^%s*(%-?%d+)%s*[,)]()", start)
if param then -- integer
param = tonumber(param)
return param, evalParams(T, context_obj, tag, cont)
end
end
---
--- Evaluates a function call with the provided parameters.
---
--- @param T table The table containing the function to call.
--- @param context_obj table The context object to use for resolving identifiers in the parameters.
--- @param fn string The name of the function to call.
--- @param tag string The full tag string containing the function call and parameters.
--- @param param_start number The starting index of the parameters in the tag string.
--- @return any The result of the function call.
---
local function evalFunctionCall(T, context_obj, fn, tag, param_start)
local f = TFormat[fn]
if f then
return f(context_obj, evalParams(T, context_obj, tag, param_start))
end
local f, obj = ResolveFunc(context_obj, fn)
if f then
return f(obj or context_obj, evalParams(T, context_obj, tag, param_start))
end
assert(f, "unknown TFormat or context function specified in tag " .. tag)
end
evalFunctionCall = function (T, context_obj, fn, tag, param_start)
local f = TFormat[fn]
if f then
return f(context_obj, evalParams(T, context_obj, tag, param_start))
end
local f, obj = ResolveFunc(context_obj, fn)
if f then
return f(obj or context_obj, evalParams(T, context_obj, tag, param_start))
end
assert(f, "unknown TFormat or context function specified in tag " .. tag)
end
---
--- Evaluates a tag in the localization system.
---
--- @param T table The table containing the localization data.
--- @param context_obj table The context object to use for resolving identifiers in the tag.
--- @param tag string The tag to evaluate.
--- @return any The result of evaluating the tag.
---
local function evalTag(T, context_obj, tag)
local func, param_start = tag:match("^(/?[%a_][%w_]*)()%b()$") -- find function and parameters start
if func then
return evalFunctionCall(T, context_obj, func, tag, param_start + 1) -- ATTN: Multiple return values possible (2nd value is true for preventing error checking on the resulting string)
end
return evalIdentifiers(T, context_obj, tag)
end
---
--- Concatenates a table of translated strings, optionally with a separator.
---
--- @param T table The table containing the strings to concatenate.
--- @param context_obj table The context object to use for translating the strings.
--- @param check boolean Whether to perform error checking on the translated strings.
--- @param tags_off boolean Whether to disable tag evaluation in the translated strings.
--- @return string The concatenated string.
---
local function evalConcat(T, context_obj, check, tags_off)
local pieces = {}
local t = T.table
if t then
for i = T.i, T.j do
table.insert(pieces, _InternalTranslate(t[i], context_obj, check, tags_off))
end
return oldTableConcat(pieces, T.sep and _InternalTranslate(T.sep, context_obj, check, tags_off))
end
for i = 1, #T do
table.insert(pieces, _InternalTranslate(T[i], context_obj, check, tags_off))
end
return oldTableConcat(pieces)
end
---
--- Appends a translation function call to the provided string buffer.
---
--- @param _pstr string The string buffer to append the translation to.
--- @param T table The localization data table.
--- @param context_obj table The context object to use for resolving identifiers in the tag.
--- @param fn string The name of the translation function to call.
--- @param tag string The full tag string, including the function name and parameters.
--- @param param_start number The starting index of the parameters in the tag string.
--- @param check boolean Whether to perform error checking on the translated string.
--- @return boolean|string False on success, or an error string on failure.
---
local function appendTranslateFunctionCall(_pstr, T, context_obj, fn, tag, param_start, check)
local append_f = TFormatPstr[fn]
if append_f then
local err = append_f(_pstr, context_obj, evalParams(T, context_obj, tag, param_start))
return err
end
local eval_f = TFormat[fn]
if eval_f then
local value, ignore_check = eval_f(context_obj, evalParams(T, context_obj, tag, param_start))
if value == nil then
return "not_a_tag"
end
if not value then
return "failed"
end
return AppendTTranslate(_pstr, value, context_obj, check ~= false and not ignore_check)
end
local eval_f, obj = ResolveFunc(context_obj, fn)
if eval_f then
local value, ignore_check = eval_f(obj or context_obj, evalParams(T, context_obj, tag, param_start))
if value == nil then
return "not_a_tag"
end
if not value then
return "failed"
end
return AppendTTranslate(_pstr, value, context_obj, check ~= false and not ignore_check)
end
assert(eval_f, "unknown TFormat or context function specified in tag " .. tag)
end
---
--- Appends a translation tag to the provided string buffer.
---
--- @param _pstr string The string buffer to append the translation to.
--- @param T table The localization data table.
--- @param context_obj table The context object to use for resolving identifiers in the tag.
--- @param tag string The full tag string, including the function name and parameters.
--- @param check boolean Whether to perform error checking on the translated string.
--- @return boolean|string False on success, or an error string on failure.
---
local function appendTranslateTag(_pstr, T, context_obj, tag, check)
local func, param_start = tag:match("^(/?[%a_][%w_]*)()%b()$") -- find function and parameters start
if func then
local err = appendTranslateFunctionCall(_pstr, T, context_obj, func, tag, param_start + 1, check) -- ATTN: Multiple return values possible (2nd value is true for preventing error checking on the resulting string)
return err
else
local value, ignore_check = evalIdentifiers(T, context_obj, tag)
if value == nil then
return "not_a_tag"
end
if not value then
return "failed"
end
local err = AppendTTranslate(_pstr, value, context_obj, check ~= false and not ignore_check)
return err
end
return false
end
---
--- Appends a translated string to the provided string buffer, handling any translation tags within the string.
---
--- @param _pstr string The string buffer to append the translation to.
--- @param T userdata|table|string|number The localization data to translate.
--- @param context_obj table The context object to use for resolving identifiers in any translation tags.
--- @param check boolean Whether to perform error checking on the translated string.
--- @param tags_off boolean Whether to skip processing any translation tags in the string.
--- @return boolean False on success, or an error string on failure.
---
local function appendTranslateT(_pstr, T, context_obj, check, tags_off)
local id = TGetID(T)
local str = (not Platform.debug or GetLanguage() ~= "English" or type(T) == "userdata") and TranslationTable[id]
or TDevModeGetEnglishText(T, "deep", "no_assert") or string.format("{#%d}", id)
if tags_off then
_pstr:append(str)
return false
end
local untagged, tag, first, last = str:nexttag(1)
context_obj = context_obj or type(T) == "table" and T[type(T[1]) == "number" and 3 or 2] or nil
while tag do
_pstr:append(untagged)
local success, err = procall(appendTranslateTag, _pstr, T, context_obj, tag, check)
if not success then
print("once", "evalTag", tag, "failed for", str)
untagged = ""
break
end
if err == "not_a_tag" then
_pstr:append_sub(str, first, last)
elseif err then
untagged = ""
break
end
untagged, tag, first, last = str:nexttag(last + 1)
end
_pstr:append(untagged)
return false
end
---
--- Appends a concatenated localized string to the provided string buffer, handling any translation tags within the strings.
---
--- @param _pstr string The string buffer to append the translation to.
--- @param T table The concatenated localization data to translate.
--- @param context_obj table The context object to use for resolving identifiers in any translation tags.
--- @param check boolean Whether to perform error checking on the translated strings.
--- @param tags_off boolean Whether to skip processing any translation tags in the strings.
--- @return boolean False on success, or an error string on failure.
---
local function appendTranslateConcat(_pstr, T, context_obj, check, tags_off)
local AppendTTranslate = AppendTTranslate
local t = T.table
if t then
local t_start = T.i
local t_end = T.j
if T.sep then
for i = t_start, t_end - 1 do
AppendTTranslate(_pstr, t[i], context_obj, check, tags_off)
AppendTTranslate(_pstr, T.sep, context_obj, check, tags_off)
end
AppendTTranslate(_pstr, t[t_end], context_obj, check, tags_off)
else
for i = t_start, t_end do
AppendTTranslate(_pstr, t[i], context_obj, check, tags_off)
end
end
return false
end
for i = 1, #T do
AppendTTranslate(_pstr, T[i], context_obj, check, tags_off)
end
return false
end
---
--- Appends a filtered user text string to the provided string buffer.
---
--- @param _pstr string The string buffer to append the user text to.
--- @param T table The user text to append.
--- @param check boolean Whether to perform error checking on the user text.
--- @return boolean False on success, or an error string on failure.
---
local function appendTranslateUserText(_pstr, T, check)
local text = GetFilteredText(T)
assert(not check or text, "Trying to use a UserText before AsyncFilterUserTexts or SetCustomFilteredUserText(s) call\n" .. TDevModeGetEnglishText(T, not "deep", "no_assert"))
_pstr:append(text or TDevModeGetEnglishText(T, not "deep", "no_assert"))
return false
end
---
--- Appends a localized string to the provided string buffer, handling any translation tags within the strings.
---
--- @param _pstr string The string buffer to append the translation to.
--- @param T table|string|number|userdata The localization data to translate.
--- @param context_obj table The context object to use for resolving identifiers in any translation tags.
--- @param check boolean Whether to perform error checking on the translated strings.
--- @param tags_off boolean Whether to skip processing any translation tags in the strings.
--- @return boolean False on success, or an error string on failure.
---
function AppendTTranslate(_pstr, T, context_obj, check, tags_off)
-- TODO: assert if it's too early to translate (see locutils for a too-early translation)?
if T == "" then
return false
end
local Ttype = type(T)
if Ttype == "userdata" then
local err = appendTranslateT(_pstr, T, context_obj, check)
if err then
return err
end
elseif Ttype == "string" then
assert(not Platform.debug or check == false or IsTagsAndPunctuation(T), string.format("Attempt to use plain text or numbers '%s' as a localized string", T))
_pstr:append(T)
elseif Ttype == "number" then
_pstr:append(LocaleInt(T))
elseif IsUserText(T) then
local err = appendTranslateUserText(_pstr, T, check)
if err then
return err
end
elseif Ttype == "table" and getmetatable(T) == TMeta then
local err = appendTranslateT(_pstr, T, context_obj, check, tags_off)
if err then
return err
end
elseif Ttype == "table" and getmetatable(T) == TConcatMeta then
local err = appendTranslateConcat(_pstr, T, context_obj, check, tags_off)
if err then
return err
end
else
assert(false, string.format("Attempt to translate invalid value '%s'", tostring(T)))
return true
end
return false
end
---
--- A boolean flag that controls whether localized string IDs should be prepended to the translated strings.
---
--- When this flag is true, the localized string ID will be prepended to the translated string, separated by a colon.
--- This can be useful for debugging and identifying the source of translated strings.
---
--- @type boolean
---
local g_TranslatePrependIDs
---
--- Toggles whether localized string IDs should be prepended to the translated strings.
---
--- When this flag is true, the localized string ID will be prepended to the translated string, separated by a colon.
--- This can be useful for debugging and identifying the source of translated strings.
---
function ToggleTranslatePrependIDs()
g_TranslatePrependIDs = not g_TranslatePrependIDs
Msg("TranslationChanged")
end
---
--- A temporary cache for the TTranslate function's pstr object.
--- This allows the TTranslate function to reuse the same pstr object instead of creating a new one every time.
---
--- @type pstr
---
local TTranslatePstrCache = pstr("", 256)
---
--- Translates the given value `T` using the provided context object and options.
---
--- @param T any The value to translate.
--- @param context_obj table The context object to use for translation.
--- @param check boolean Whether to check for translation errors.
--- @param tags_off boolean Whether to disable HTML tag translation.
--- @return string The translated string.
---
function TTranslate(T, context_obj, check, tags_off)
local _pstr = TTranslatePstrCache
if not _pstr then
_pstr = pstr("", 256)
else
TTranslatePstrCache = false
_pstr:clear()
end
local err = AppendTTranslate(_pstr, T, context_obj, check ~= false and not TIgnoreErrors, tags_off)
assert(not err, "translation error")
TTranslatePstrCache = _pstr -- return to the cache
if g_TranslatePrependIDs then
local id = TGetID(T)
if id then
return id .. ":" .. _pstr:str()
end
end
return _pstr:str()
end
_InternalTranslate = TTranslate
local ThousandsSeparator
---
--- Formats a number with a thousands separator.
---
--- @param x number The number to format.
--- @return string The formatted number string.
---
function LocaleInt(x)
ThousandsSeparator = ThousandsSeparator or TTranslate(T(433967674729, --[[thousands separator]] ","))
local ts = ThousandsSeparator
local r = ""
if x < 0 then
r = "-"
x = -x
end
if x < 1000 then
r = r .. tostring(x)
elseif x < 1000*1000 then
r = string.format("%s%d%s%03d", r, x/1000, ts, x%1000)
elseif x < 1000*1000*1000 then
r = string.format("%s%d%s%03d%s%03d", r, x/1000000, ts, (x/1000)%1000, ts, x%1000)
else
r = string.format("%s%d%s%03d%s%03d%s%03d", r, x/1000000000, ts, (x/1000000)%1000, ts, (x/1000)%1000, ts, x%1000)
end
return r
end
---
--- Called when the translation system has changed.
--- Clears the thousands separator cache and marks the PreGameButtons object as modified.
---
function OnMsg.TranslationChanged()
ThousandsSeparator = false
ObjModified("PreGameButtons")
end
---
--- Formats a date and time string in the user's locale.
---
--- @param os_time number The Unix timestamp to format.
--- @return string The formatted date and time string.
---
function LocaleDateTime(os_time)
return os.date(GetLanguage() == "Japanese" and "%Y.%m.%d %H:%M" or "%d %b %Y %H:%M", os_time)
end
-- Returns the order of month, day and year.
-- This handles transforming the output of the various systems into an array.
-- Don't call this on a hot path please :)
-- Example: YYYYMMDD -> { year, month, day }
-- M/d/YYYY -> { month, day, year }
-- dd mmm Y -> { day, month, year }
---
--- Returns the order of month, day and year based on the system date format.
---
--- @return table The order of month, day and year as an array of strings.
---
function GetDateTimeOrder()
local format = GetSystemDateFormat()
local lastC = false
local order = {}
for i = 1, #format do
local c = format:sub(i, i)
local isMonthChar = c == "m" or c == "M"
local isYearChar = c == "Y" or c == "y"
local isDayChar = c == "D" or c == "d"
local isValidChar = isMonthChar or isYearChar or isDayChar
if isValidChar then
if c ~= lastC then
if isMonthChar then
order[#order + 1] = "month"
elseif isYearChar then
order[#order + 1] = "year"
elseif isDayChar then
order[#order + 1] = "day"
end
end
lastC = c
end
end
return order
end
---
--- Converts a localization table or ID to a Lua code string.
---
--- @param T table|number The localization table or ID to convert.
--- @param context string The context of the localization.
--- @param pstr string An optional string to append the Lua code to.
--- @return string The Lua code representation of the localization.
---
function TToLuaCode(T, context, pstr)
if IsUserText(T) then
return UserTextToLuaCode(T, context, pstr)
end
assert(not THasArgs(T))
return IDTextToLuaCode(TGetID(T), TDevModeGetEnglishText(T, not "deep", "no assert"), context, pstr)
end
---
--- Converts a user text localization object to a Lua code string.
---
--- @param T table The user text localization object to convert.
--- @param context string The context of the localization.
--- @param pstr string An optional string to append the Lua code to.
--- @return string The Lua code representation of the user text localization.
---
function UserTextToLuaCode(T, context, pstr)
local lua_str = string.format("T%s", TableToLuaCode(T))
if pstr then
return pstr:appendf(lua_str)
end
return string.format(lua_str)
end
---
--- Converts a localization ID and text to a Lua code string.
---
--- @param id number The localization ID.
--- @param text string The localization text.
--- @param context string The context of the localization.
--- @param pstr string An optional string to append the Lua code to.
--- @return string The Lua code representation of the localization.
---
function IDTextToLuaCode(id, text, context, pstr)
-- ...
end
function IDTextToLuaCode(id, text, context, pstr)
local context_str = context and context ~= "" and string.format("--[[%s]] ", context) or ""
if id then
if text ~= "" then
if pstr then
return pstr:appendf("T(%d, %s%v)", id, context_str, text)
end
return string.format("T(%d, %s%s)", id, context_str, StringToLuaCode(text))
else
if pstr then
return pstr:append('""')
end
return '""'
end
else
if pstr then
return pstr:appendf("T(%s%v)", context_str, text)
end
return string.format("T(%s%s)", context_str, StringToLuaCode(text))
end
end
local csv_load_fields = { [1] = "id", [2] = "text", [5] = "translated", [3] = "translated_new", [7] = "gender" }
---
--- Loads translation tables from a CSV file.
---
--- @param filename string The path to the CSV file containing the translation data.
--- @return boolean Whether the translation tables were successfully loaded.
---
function LoadTranslationTableFile(filename)
local loaded = {}
LoadCSV(filename, loaded, csv_load_fields, "omit_captions")
return ProcessLoadedTables(loaded, GetLanguage(), TranslationTable, TranslationGenderTable)
end
---
--- Loads translation tables from a folder containing CSV files.
---
--- @param path string The path to the folder containing the CSV files.
--- @param language string The language to load the translation tables for.
--- @param out_table table The table to store the loaded translations.
--- @param out_gendertable table The table to store the gender information for the translations.
--- @return boolean Whether the translation tables were successfully loaded.
---
function LoadTranslationTablesFolder(path, language, out_table, out_gendertable)
local loaded = {}
local files = io.listfiles(path, "*.csv") or {}
table.sort(files)
for _, filename in ipairs(files) do
LoadCSV(filename, loaded, csv_load_fields, "omit_captions")
end
return ProcessLoadedTables(loaded, language, out_table, out_gendertable)
end
---
--- Processes the loaded translation tables, extracting the appropriate translation text and storing it in the output tables.
---
--- @param loaded table The table containing the loaded translation data.
--- @param language string The language to process the translations for.
--- @param out_table table The table to store the loaded translations.
--- @param out_gendertable table The table to store the gender information for the translations.
--- @return boolean Whether the translation tables were successfully loaded.
---
function ProcessLoadedTables(loaded, language, out_table, out_gendertable)
local order = { "translated_new", "translated", "text" }
if language == "English" then
order = { "translated_new", "text", "translated" }
end
for _, entry in ipairs(loaded) do
local translation
if entry[order[1]] and entry[order[1]] ~= "" then
translation = entry[order[1]]
elseif entry[order[2]] and entry[order[2]] ~= "" then
translation = entry[order[2]]
else
translation = entry[order[3]]
end
local id = tonumber(entry.id)
if id then
out_table[id] = translation
if out_gendertable then
out_gendertable[id] = entry.gender
end
end
end
return next(loaded) ~= nil
end
--- A table of languages that should always wrap text, regardless of the user's text wrapping settings.
---
--- This table is used to ensure that certain languages, such as Chinese, Japanese, and Korean, always wrap text properly, even if the user has disabled text wrapping in their settings.
---
--- @field Schinese boolean Whether Simplified Chinese should always wrap text.
--- @field Tchinese boolean Whether Traditional Chinese should always wrap text.
--- @field Japanese boolean Whether Japanese should always wrap text.
--- @field Koreana boolean Whether Korean should always wrap text.
local AlwaysWrapLanguages = {
Schinese = true,
Tchinese = true,
Japanese = true,
Koreana = true,
}
---
--- Loads the translation tables for the current language.
---
--- This function loads the translation tables for the current language, and stores them in the `TranslationTable` and `TranslationGenderTable` global variables. It first attempts to load the tables from the `CurrentLanguage` directory in the executable directory, and if that fails, it tries to load them from the `CurrentLanguage/` directory. If that also fails, and the game is not in debug or command-line mode, it asserts an error.
---
--- The function also sets the `config.TextWrapAnywhere` flag based on whether the current language is one of the languages that should always wrap text, as defined in the `AlwaysWrapLanguages` table.
---
--- Finally, the function sends a "TranslationChanged" message to notify other parts of the application that the translation tables have been updated.
---
function LoadTranslationTables()
TranslationTable = {}
collectgarbage("collect")
local path = GetExecDirectory() .. "CurrentLanguage"
if not LoadTranslationTablesFolder(path, GetLanguage(), TranslationTable, TranslationGenderTable) then
if not LoadTranslationTablesFolder("CurrentLanguage/", GetLanguage(), TranslationTable, TranslationGenderTable) and
not Platform.debug and not Platform.cmdline
then
assert(false, "Localization table not found in non-developer mode! (For testing you could copy the game.csv from LocalizationOut/English/CurrentLanguage to Bin/CurrentLanguage. Build the table with the LocExtract build command if it's not present.)")
end
end
config.TextWrapAnywhere = AlwaysWrapLanguages[GetLanguage()] or false
if not Loading then
Msg("TranslationChanged")
end
collectgarbage("collect")
end
-- used by build
g_BuildLocTables = false
g_BuildLocTablesSignal = false
---
--- Loads the localization tables for the specified project path.
---
--- This function loads the localization tables for all languages found in the `LocalizationOut` directory of the specified project path. It first checks if the tables have already been loaded, and if so, waits for the first thread to finish loading them. Otherwise, it loads the tables and stores them in the `g_BuildLocTables` global variable.
---
--- The function first lists all the language folders in the `LocalizationOut` directory, then for each language, it loads all the CSV files in the `CurrentLanguage` subdirectory. It parses the CSV files and stores the localization data in a table, with the language name as the key.
---
--- Once all the localization tables have been loaded, the function sets the `g_BuildLocTables` global variable and signals any waiting threads that the tables have been loaded.
---
--- @param project_path string The path to the project directory containing the localization files.
---
function LoadBuildLocTables(project_path)
if g_BuildLocTables then return end
if g_BuildLocTablesSignal then
WaitMsg(g_BuildLocTablesSignal) -- if several build threads try to load the table concurrently, all wait for the first to finish
return
end
g_BuildLocTablesSignal = {}
local loctables = {}
local err, languages = AsyncListFiles(project_path .. "/LocalizationOut", "*", "folders,relative")
if err then print("Error loading translation tables: ", err) return end
for _, language in ipairs(languages) do
local path = project_path .. "/LocalizationOut/" .. language .. "/CurrentLanguage"
local err, files = AsyncListFiles(path, "*.csv")
if not err then
local loaded = {}
local needed_fields = { [1] = "id", [2] = "text", [3] = "translated_new" }
table.sort(files)
for i = 1, #files do
LoadCSV(files[i], loaded, needed_fields, "omit_captions")
end
local order = { "translated_new" }
if language == "English" then
order = { "translated_new", "text" }
end
local lang_result = {}
for _, entry in ipairs(loaded) do
if entry[order[1]] and entry[order[1]] ~= "" then
lang_result[tonumber(entry.id)] = entry[order[1]]
elseif order[2] and entry[order[2]] and entry[order[2]] ~= "" then
lang_result[tonumber(entry.id)] = entry[order[2]]
end
end
loctables[language] = lang_result
end
end
g_BuildLocTables = loctables
Msg(g_BuildLocTablesSignal)
g_BuildLocTablesSignal = false
end
---
--- Returns the gender of the given translation table.
---
--- @param T table The translation table.
--- @return string|false The gender of the translation table, or `false` if no gender is defined.
---
function GetTGender(T)
return TranslationGenderTable[TGetID(T) or false] or false
end
-- gender can be:
-- * a string (M/F/N)
-- * a T and we use GetTGender(T)
-- * a table and we use gender.Gender
-- T can be:
-- * a simple T (no parameters) in which case we return the alternative gender variant of T
-- * a T with parameters in which case we MODIFY it in-place to reflect the requestsed gender
---
--- Returns a translation table with the gender variant specified.
---
--- @param T table The translation table.
--- @param gender string The gender variant to use. Can be "M", "F", or "N".
--- @return table The translation table with the specified gender variant.
---
function GetTByGender(T, gender)
if (T or "") == "" then return T end
if THasArgs(T) then -- in-place modification of a T with parameters - the assumption is that the function gets called with a newly created T table
assert(type(T) == "table" and getmetatable(T) == TMeta) -- we do not work with concatenated strings
assert(not T.__gender_updated) -- the same T should not be passed more than once to this function
dbg(rawset(T, "__gender_updated", true)) -- mark the T so we can recognise it if we get it again
T[1] = GenderChangedID(T[1], gender)
return T
else
local id = GenderChangedID(TGetID(T), gender)
return TranslationTable[id or false] and LocIDToLightUserdata(id) or T
end
end
---
--- Returns the gender-specific suffix for the given translation table ID.
---
--- @param T table The translation table.
--- @param id string The translation table ID.
--- @return string The gender-specific suffix for the given ID.
---
function IdGenderSuffix(T, id)
local gender = TranslationGenderTable[TGetID(T) or false] or "M"
if gender == "F" then
return id .. "_f"
elseif gender == "N" then
return id .. "_n"
else
return id .. "_m"
end
end
--global functions for controlling/getting windows ime state
local IME_languages = {"Koreana","Japanese","Schinese","Tchinese"}
---
--- Initializes the Windows IME (Input Method Editor) state for the current language.
---
--- This function is responsible for enabling or disabling the IME based on the current language, and controlling the visibility of the IME candidate window.
---
--- The IME is enabled for languages that have been properly implemented, and disabled for languages that have not. The IME candidate window is also disabled for the "Koreana" language, as the game's fonts do not support Hanja input.
---
--- Additionally, the function sets the `hr.HideIme` flag to `true`, which is used to hide the IME window when the user has no editable controls in focus, allowing them to control the game without the IME window interfering.
---
--- It is the responsibility of the Lua controls to enable/disable the IME as needed.
---
function InitWindowsImeState()
--since ime has different behaviour for each language, disable it for languages that have
--not been properly implemented.
local lang = GetLanguage()
config.EnableIme = Platform.pc and table.find(IME_languages,lang)
--print("config.EnableIme:", config.EnableIme)
config.EnableImeCandidateWindow = lang ~= "Koreana" --we don't support hanja in fonts so kill the candidate window to avoid hanja input.
--print("config.EnableImeCandidateWindow:", config.EnableImeCandidateWindow)
--we want ime hidden when the user has no editable controls on focus
--so that the user is able to control the game without the ime window poping up and eating input
--it is the responsibility of lua controls to enable/disable as needed.
hr.HideIme = true
end
---
--- Checks if the Input Method Editor (IME) is enabled.
---
--- @return boolean True if the IME is enabled, false otherwise.
---
function IsImeEnabled()
return config.EnableIme
end
---
--- Sets the position of the Windows Input Method Editor (IME) window.
---
--- This function is responsible for setting the position of the IME window relative to the upper left corner of the window. It assumes that the user will only edit one editable control at a time, and subsequent calls within the same frame are okay, but the final position will be processed at the end of the frame.
---
--- @param x number The x-coordinate of the IME window position.
--- @param y number The y-coordinate of the IME window position.
--- @param fontId number The font ID to use for the IME window.
---
function SetImePosition(x, y, fontId)
end
function SetImePosition(x, y, fontId) --relative to upper left corner of window, or so msdn claims.
if IsImeEnabled() then
--this assumes that the user will edit only one editable control @ a time.
--subsequent calls in the same frame are ok, but keep in mind hr. vars will get processed @ the end of the frame.
--this means only the last call per frame would actually get processed.
hr.WindowsImePositionX = x
hr.WindowsImePositionY = y
hr.WindowsImeFontId = fontId or -1
hr.WindowsImePosChanged = hr.WindowsImePosChanged + 1
end
end
--will temporariliy disable ime from showing up so the player can control the game
---
--- Hides the Windows Input Method Editor (IME) window.
---
--- This function is responsible for hiding the IME window so that the user can control the game without the IME window popping up and eating input. It is the responsibility of the Lua controls to enable/disable the IME as needed.
---
--- @return nil
---
function HideIme()
if IsImeEnabled() then
if not hr.HideIme then
hr.HideIme = true
end
end
end
--reverts changes done by HideIme()
---
--- Reverts the changes made by `HideIme()`, allowing the Windows Input Method Editor (IME) window to be shown again.
---
--- This function is responsible for restoring the ability for the IME window to be displayed, after it has been hidden by the `HideIme()` function. It is the responsibility of the Lua controls to enable/disable the IME as needed.
---
--- @return nil
---
function ShowIme()
if IsImeEnabled() then
if hr.HideIme then
hr.HideIme = false
end
end
end
---
--- Gets the width and height of the IME window based on the specified font ID.
---
--- This function is used to determine the size of the IME window, which is necessary for positioning the IME window correctly on the screen. It uses the `terminal.GetWindowsImeCompositionString()` function to retrieve the current composition string, and then measures the text using `UIL.MeasureText()` to get the width and height.
---
--- @param fontId number The font ID to use for measuring the IME window size.
--- @return number, number The width and height of the IME window.
---
function GetImeWindowWidthHeight(fontId)
local compStr = terminal.GetWindowsImeCompositionString()
if compStr then
return UIL.MeasureText(compStr, fontId) --another hack because ImmGetCompositionWindow returns empty rect.
end
return 0,0
end
---
--- A table of localization data for various languages, including their display names, PlayStation locale codes, and other locale codes.
---
--- This table contains information about the supported localization languages, including the display name, PlayStation locale code, locale code, Paradox locale code, and Epic Games locale code for each language.
---
--- @field value string The unique identifier for the language.
--- @field text string The display name for the language.
--- @field ps_locale string The PlayStation locale code for the language.
--- @field locale string The locale code for the language.
--- @field pdx_locale string The Paradox locale code for the language.
--- @field epic_locale string The Epic Games locale code for the language.
---
AllLanguages = {
{ value = "Brazilian", text = T(699854757080, "Brazilian Portuguese"), ps_locale = "pt-BR", locale = "pt-BR", pdx_locale = "pt", epic_locale = "pt-BR", },
{ value = "Bulgarian", text = T(385829073168, "Bulgarian"), ps_locale = "bg-BG", locale = "bg-BG", pdx_locale = "bg", epic_locale = false, },
{ value = "Czech", text = T(552240423015, "Czech"), ps_locale = "cs-CZ", locale = "cs-CZ", pdx_locale = "cs", epic_locale = false, },
{ value = "Danish", text = T(782416127227, "Danish"), ps_locale = "da-DK", locale = "da-DK", pdx_locale = "da", epic_locale = "da", },
{ value = "Dutch", text = T(675114896426, "Dutch"), ps_locale = "nl-NL", locale = "nl-NL", pdx_locale = "nl", epic_locale = "nl", },
{ value = "English", text = T(147611982706, "English"), ps_locale = "en-US", locale = "en-US", pdx_locale = "en", epic_locale = "en-US", },
{ value = "Finnish", text = T(283206621979, "Finnish"), ps_locale = "fi-FI", locale = "fi-FI", pdx_locale = "fi", epic_locale = "fi", },
{ value = "French", text = T(170273676234, "French"), ps_locale = "fr-FR", locale = "fr-FR", pdx_locale = "fr", epic_locale = "fr", },
{ value = "German", text = T(505552009073, "German"), ps_locale = "de-DE", locale = "de-DE", pdx_locale = "de", epic_locale = "de", },
{ value = "Hungarian", text = T(646055054297, "Hungarian"), ps_locale = "hu-HU", locale = "hu-HU", pdx_locale = "hu", epic_locale = false, },
{ value = "Indonesian", text = T(596539604344, "Indonesian"), ps_locale = "id-ID", locale = "id-ID", pdx_locale = "id", epic_locale = false, },
{ value = "Italian", text = T(330877865785, "Italian"), ps_locale = "it-IT", locale = "it-IT", pdx_locale = "it", epic_locale = "it", },
{ value = "Japanese", text = T(527962174587, "Japanese"), ps_locale = "ja-JP", locale = "ja-JP", pdx_locale = "ja", epic_locale = "ja", },
{ value = "Koreana", text = T(585811408758, "Korean"), ps_locale = "ko-KR", locale = "ko-KR", pdx_locale = "ko", epic_locale = "ko", },
{ value = "Norwegian", text = T(369233670775, "Norwegian"), ps_locale = "nb-NO", locale = "nb-NO", pdx_locale = "no", epic_locale = "no", },
{ value = "Polish", text = T(197791212449, "Polish"), ps_locale = "pl-PL", locale = "pl-PL", pdx_locale = "pl", epic_locale = "pl", },
{ value = "Portuguese", text = T(661132086100, "Portuguese"), ps_locale = "pt-PT", locale = "pt-PT", pdx_locale = "pt", epic_locale = false, },
{ value = "Romanian", text = T(375694388084, "Romanian"), ps_locale = "ro-RO", locale = "ro-RO", pdx_locale = "ro", epic_locale = false, },
{ value = "Russian", text = T(794451731349, "Russian"), ps_locale = "ru-RU", locale = "ru-RU", pdx_locale = "ru", epic_locale = "ru", },
{ value = "Schinese", text = T(465743231919, "Chinese (Simplified)"), ps_locale = "zh-Hans", locale = "zh-CN", pdx_locale = "zh", epic_locale = "zh-Hans", },
{ value = "Spanish", text = T(277226277909, "Spanish (Spain)"), ps_locale = "es-ES", locale = "es-ES", pdx_locale = "es", epic_locale = "es-ES", },
{ value = "Latam", text = T(342769994919, "Spanish (Latin America)"), ps_locale = "es-MX", locale = "es-MX", pdx_locale = "es", epic_locale = "es-MX", },
{ value = "Swedish", text = T(487752404194, "Swedish"), ps_locale = "sv-SE", locale = "sv-SE", pdx_locale = "sv", epic_locale = "sv", },
{ value = "Tchinese", text = T(508880261610, "Chinese (Traditional)"), ps_locale = "zh-Hant", locale = "zh-TW", pdx_locale = "zh", epic_locale = "zh-Hant", },
{ value = "Thai", text = T(681908731541, "Thai"), ps_locale = "th-TH", locale = "th-TH", pdx_locale = "th", epic_locale = "th", },
{ value = "Turkish", text = T(218295023775, "Turkish"), ps_locale = "tr-TR", locale = "tr-TR", pdx_locale = "tr", epic_locale = "tr", },
}
--- Indicates that the language names for Traditional Chinese and Simplified Chinese start with the "Tchinese" and "Schinese" family names, respectively.
LanguagesWithNamesStartWithFamily = {
["Tchinese"] = true,
["Schinese"] = true,
}
-- TODO(mitko): Move to PlayStationRules.lua when trophies building stop depending on DataInstances
-- Copied from:
-- https://ps4.siedev.net/resources/documents/Misc/current/Live_Item_Admin_Tool-Users_Guide/0003.html#__document_toc_00000006
-- https://ps4.siedev.net/resources/documents/Misc/current/Param_File_Editor-Users_Guide/0004.html#0_Ref368663318
--- Defines a mapping between PlayStation language codes and their corresponding language names.
-- The mapping is used to convert between PlayStation language codes and language names used in the game.
-- The table contains the language name, the corresponding PlayStation language code, and the PlayStation game code.
-- This mapping is used in various parts of the game to handle localization and language-specific functionality.
PlayStationLanguageCodes = {
-- hg pack, sfo, gp
"Japanese", "00", -- Japanese
"English", "01", -- English (United States)
"French", "02", -- French
"Spanish", "03", -- Spanish
"German", "04", -- German
"Italian", "05", -- Italian
"", "06", -- Dutch
"Portuguese", "07", -- Portuguese (Portugal)
"Russian", "08", -- Russian
"Koreana", "09", -- Korean
"Tchinese", "10", -- Chinese (traditional)
"Schinese", "11", -- Chinese (simplified)
"", "12", -- Finnish
"", "13", -- Swedish
"", "14", -- Danish
"", "15", -- Norwegian
"Polish", "16", -- Polish
"Brazilian", "17", -- Portuguese (Brazil)
"English", "18", -- English (United Kingdom)
"", "19", -- Turkish
"Latam", "20", -- Spanish (Latin America)
"French", "22", -- French (Canada)
"Czech", "23", -- Czech
"Hungarian", "24", -- Hungarian
"", "25", -- Greek
"Romanian", "26", -- Romanian
"Thai", "27", -- Thai
"", "28", -- Vietnamese
"Indonesian", "29", -- Indonesian
}
--- Converts a locale string to the corresponding PlayStation locale code.
---
--- @param locale string The locale string to convert.
--- @return string The PlayStation locale code corresponding to the input locale.
function LocaleToPlayStationLocale(locale)
return table.find_value(AllLanguages, "locale", locale).ps_locale
end
---
--- Checks if a localization language is available.
---
--- @param language string The language to check for availability.
--- @return boolean True if the localization language is available, false otherwise.
function IsLocalizationLanguageAvailable(language)
local folder_or_pack =
(config.UnpackedLocalization or config.UnpackedLocalization == nil and IsFSUnpacked())
and ("svnProject/LocalizationOut/" .. language .. "/CurrentLanguage/")
or ("Local/" .. language .. ".hpk")
return io.exists(folder_or_pack)
end
---
--- Initializes the localization options for the game.
---
--- This function is called on game startup and sets up the available localization options.
--- It first adds an "Auto" option, which automatically selects the system language.
--- Then, it checks if the game is running on a desktop platform and if the `OptionsData` table exists.
--- If so, it iterates through the `AllLanguages` table and adds any available localization languages to the options list.
--- Finally, it sets the `OptionsData.Options.Language` table to the resulting list of localization options.
---
--- @return nil
function OnMsg.Autorun()
local result = {
{ value = "Auto", text = T(388818321440, "Auto"), iso_639_1 = "en" }
}
if Platform.desktop and rawget(_G, "OptionsData") then
for _, language in ipairs(AllLanguages) do
if IsLocalizationLanguageAvailable(language.value) then
result[#result+1] = language
end
end
OptionsData.Options.Language = result
end
end
local list_separator = T(651365107459, --[[list separator]] ", ")
---
--- Concatenates a list of values into a single string, using the provided separator.
---
--- If no separator is provided, the default list separator from the localization system is used.
---
--- @param list table The list of values to concatenate.
--- @param separator string (optional) The separator to use between the list items.
--- @return string The concatenated string.
function TList(list, separator)
return table.concat(list, separator or _InternalTranslate(list_separator))
end
-- given "abra keyword:cad abra", "keyword" -> returns "abra abra", "cad"
---
--- Extracts a value from a string based on a given needle pattern.
---
--- If the needle pattern is found in the haystack string, this function will extract the value that follows the needle pattern and return the haystack string with the extracted value removed.
---
--- @param haystack string The string to search and extract from.
--- @param needle string The pattern to search for in the haystack.
--- @return string, string The haystack string with the extracted value removed, and the extracted value.
function match_and_remove(haystack, needle)
if not haystack then return end
local extracted = string.match(haystack, needle .. "(%g*)")
if extracted then
local st, nd = string.find(haystack, needle .. extracted, 1, "plain")
return string.sub(haystack, 1, st-1) .. string.sub(haystack, nd+1), extracted
else
return haystack
end
end |