Skip to content

Commit 2f7e523

Browse files
addohmclaude
andcommitted
Add 国服 (Simplified-Chinese) item-paste translation
Lets players paste an item copied from the Simplified-Chinese (WeGame / 国服) PoE2 client and have PoB parse it, by translating the paste to the English item-paste text the parser already expects. No parser changes — a thin hook in the Item constructor runs the translation before sanitiseText(); it is a no-op when the text contains no CJK, so English pastes are completely unaffected. What it translates: item class, rarity, base type, unique name, properties (quality/spirit/sockets/item level + weapon & defence stats), requirements, explicit/implicit/rune/desecrated mod lines (via the client's StatDescriptions, with (min-max) range + (augmented) stripping), flask/charm charge & duration lines, granted skills, anointments (配置 X -> Allocates X, incl. passive-node names), and affix annotations ({ 前缀属性 "name" (等阶:N) } with the affix name translated so PoB fills prefix/suffix tiers). Data: src/Data/ChineseTranslation.lua is generated by the self-contained datamine toolchain in tools/cn-translate/ (Node + pathofexile-dat) from the WeGame client, joining en<->zh on the language-independent metadata Id. NOTE: regeneration requires the 国服 client (the only source of the SC strings), so it can't be reproduced from the international GGPK — see tools/cn-translate/README.md. Tested: spec/System/TestChineseItemParse_spec.lua round-trips a corpus of real 国服 pastes through the parser (base recognised; affix tiers populated; mods resolved). ~94.5% line coverage on the corpus (remainder is procedural rare names + unique flavour, which PoB ignores). Baseline item-parse specs unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 99bc6e1 commit 2f7e523

24 files changed

Lines changed: 41082 additions & 0 deletions
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
-- 国服 fork: verify a Simplified-Chinese (WeGame) item paste is auto-translated
2+
-- and parsed by PoB's real parser (the constructor hook calls translateChineseItem
3+
-- before sanitiseText). The translation tables are datamined (see tools/cn-translate).
4+
describe("TestChineseItemParse", function()
5+
-- The example 国服 paste from POB-FORK-HANDOFF.md (a rare Shrine Sceptre).
6+
local sceptreRaw = table.concat({
7+
"物品类别: 权杖",
8+
"稀有度: 稀有",
9+
"复仇 巨锤",
10+
"圣地权杖",
11+
"--------",
12+
"品质: +20% (augmented)",
13+
"精魂: 177 (augmented)",
14+
"--------",
15+
"需求: 等级 84, 45 力量, 114 智慧",
16+
"--------",
17+
"插槽: S",
18+
"--------",
19+
"物品等级: 84",
20+
"--------",
21+
"该装备精魂提高 15% (rune)",
22+
"--------",
23+
"获得技能: 等级 19 冰霜净化",
24+
"--------",
25+
'{ 前缀属性 "龙胆的" (等阶:4) — 魔力 }',
26+
"+98 (90-104) 魔力上限",
27+
'{ 前缀属性 "国王的" (等阶:1) }',
28+
"该装备精魂提高 62 (61-65)%",
29+
'{ 后缀属性 "哲学家之" (等阶:4) — 属性 }',
30+
"+21 (21-24) 智慧",
31+
'{ 后缀属性 "涅盘之" (等阶:1) — 魔力 }',
32+
"魔力再生率提高 67 (60-69)%",
33+
'{ 后缀属性 "校长之" (等阶:3) — 生命, 召唤生物 }',
34+
"召唤生物的生命上限提高 39 (36-40)%",
35+
"--------",
36+
"引路石掉落",
37+
}, "\n")
38+
39+
it("parses base type, class and rarity", function()
40+
local item = new("Item", sceptreRaw)
41+
assert.are.equals("RARE", item.rarity)
42+
assert.are.equals("Shrine Sceptre", item.baseName)
43+
assert.is_not_nil(item.base)
44+
end)
45+
46+
it("parses the explicit mods", function()
47+
local item = new("Item", sceptreRaw)
48+
local text = table.concat(item.explicitModLines and (function()
49+
local t = {}
50+
for _, m in ipairs(item.explicitModLines) do t[#t + 1] = m.line end
51+
return t
52+
end)() or {}, "\n")
53+
assert.is_true(#item.explicitModLines >= 5)
54+
assert.is_truthy(text:find("increased Mana Regeneration Rate", 1, true))
55+
assert.is_truthy(text:find("to maximum Mana", 1, true))
56+
assert.is_truthy(text:find("to Intelligence", 1, true))
57+
assert.is_truthy(text:find("increased maximum Life", 1, true))
58+
end)
59+
60+
it("parses item level and spirit", function()
61+
local item = new("Item", sceptreRaw)
62+
assert.are.equals(84, item.itemLevel)
63+
assert.are.equals(177, item.spiritValue)
64+
end)
65+
66+
it("routes the rune mod to runeModLines", function()
67+
local item = new("Item", sceptreRaw)
68+
local foundRune = false
69+
for _, m in ipairs(item.runeModLines or {}) do
70+
if m.line:find("increased Spirit", 1, true) then foundRune = true end
71+
end
72+
assert.is_true(foundRune)
73+
end)
74+
75+
it("leaves an English paste untouched", function()
76+
local en = "Rarity: Rare\nName\nShrine Sceptre\n--------\nItem Level: 84"
77+
local item = new("Item", en)
78+
assert.are.equals("Shrine Sceptre", item.baseName)
79+
assert.are.equals(84, item.itemLevel)
80+
end)
81+
82+
-- Corpus of real in-game 国服 pastes (tools/items.txt, &&-separated). Every item
83+
-- must have its base type recognised after translation; we also report mod counts.
84+
it("parses the real 国服 corpus", function()
85+
local f = io.open("../tools/items.txt", "rb")
86+
if not f then pending("tools/items.txt not present"); return end
87+
local corpus = f:read("*a"); f:close()
88+
local items, cur = {}, {}
89+
for line in (corpus .. "\n"):gmatch("([^\n]*)\n") do
90+
if line:gsub("%s", "") == "&&" then
91+
items[#items + 1] = table.concat(cur, "\n"); cur = {}
92+
else cur[#cur + 1] = line end
93+
end
94+
if #cur > 0 then items[#items + 1] = table.concat(cur, "\n") end
95+
96+
local failures = {}
97+
for i, raw in ipairs(items) do
98+
if raw:gsub("%s", "") ~= "" then
99+
local item = new("Item", raw)
100+
local nMods = #item.explicitModLines + #item.implicitModLines
101+
+ #item.runeModLines + #item.enchantModLines
102+
print(string.format(" item %2d: base=%-28s rarity=%-7s mods=%d prefix=%d suffix=%d",
103+
i, tostring(item.baseName), tostring(item.rarity), nMods, #item.prefixes, #item.suffixes))
104+
if not item.base then
105+
failures[#failures + 1] = string.format("item %d: base %q not recognised", i, tostring(item.baseName))
106+
end
107+
end
108+
end
109+
assert.are.equals(0, #failures, "\n" .. table.concat(failures, "\n"))
110+
end)
111+
end)

src/Classes/Item.lua

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ end
7070

7171
local ItemClass = newClass("Item", function(self, raw, rarity, highQuality)
7272
if raw then
73+
-- 国服 fork: auto-translate a Simplified-Chinese (WeGame) item paste to the
74+
-- English text the parser expects. Must run BEFORE sanitiseText(), which
75+
-- turns every CJK byte into '?'. No-op when the text has no CJK.
76+
if translateChineseItem then
77+
raw = translateChineseItem(raw)
78+
end
7379
self:ParseRaw(sanitiseText(raw), rarity, highQuality)
7480
end
7581
end)

0 commit comments

Comments
 (0)