import { describe, it, expect, beforeEach } from 'vitest' import { formatItemTooltip, formatPropertyCode, getTooltipLocale, setTooltipLocale, translateItemName, } from '../src/game/item-tooltip.ts' import { translateSkillName, translateRareName, translateMagicName, formatPropertyCodeI18n, ITEM_NAMES_ZH, SKILL_NAMES_ZH, CANONICAL_SKILL_TABS_ZH, RUNE_INFO_ZH, } from '../src/game/tooltip-i18n.ts' import type { Item } from '../src/game/items.ts' describe('Issue #122: Equipment & Affix Chinese Localization (EN / ZH / Bilingual)', () => { beforeEach(() => { // Ensure default is always English setTooltipLocale('en') }) describe('1. Locale State & Switching', () => { it('defaults to en (1.13c English original)', () => { expect(getTooltipLocale()).toBe('en') }) it('can switch between en, zh, and bilingual', () => { setTooltipLocale('zh') expect(getTooltipLocale()).toBe('zh') setTooltipLocale('bilingual') expect(getTooltipLocale()).toBe('bilingual') setTooltipLocale('en') expect(getTooltipLocale()).toBe('en') }) }) describe('2. Item & Skill Name Dictionaries', () => { it('translates base equipment names correctly', () => { expect(translateItemName('Cap')).toBe('帽子') expect(translateItemName('Shako')).toBe('军帽') expect(translateItemName('Archon Plate')).toBe('执政官铠甲') expect(translateItemName('Colossus Blade')).toBe('巨神之刃') expect(translateItemName('Ring')).toBe('戒指') expect(translateItemName('Amulet')).toBe('项链') }) it('translates unique item names correctly', () => { expect(translateItemName('The Stone of Jordan')).toBe('乔丹之石') expect(translateItemName('Harlequin Crest')).toBe('谐角之冠') expect(translateItemName('Stormshield')).toBe('暴风之盾') expect(translateItemName('The Oculus')).toBe('眼球') }) it('translates set item names and set names correctly', () => { expect(translateItemName("Tal Rasha's Wrappings")).toBe('塔·拉夏的外袍') expect(translateItemName("Tal Rasha's Guardianship")).toBe('塔·拉夏的守护') expect(translateItemName("Immortal King's Soul Cage")).toBe('不朽之王的灵魂牢笼') expect(translateItemName("Griswold's Legacy")).toBe('格瑞斯华尔德的传奇') }) it('translates superior and low quality prefixes', () => { expect(translateItemName('Superior Cap')).toBe('超强的帽子') expect(translateItemName('Cracked Ring Mail')).toBe('劣质的锁环甲') expect(translateItemName('Crude Short Sword')).toBe('粗糙的短剑') expect(translateItemName('Low Quality Breast Plate')).toBe('低品质的胸甲') }) it('translates classic and LoD skills correctly', () => { expect(translateSkillName('Teleport')).toBe('传送') expect(translateSkillName('Fire Ball')).toBe('火球') expect(translateSkillName('Blizzard')).toBe('暴风雪') expect(translateSkillName('Whirlwind')).toBe('旋风') expect(translateSkillName('Blessed Hammer')).toBe('祝福之锤') // Expansion Druid & Assassin skills expect(translateSkillName('Hurricane')).toBe('飓风') expect(translateSkillName('Phoenix Strike')).toBe('凤凰击') expect(translateSkillName('Poison Creeper')).toBe('剧毒蔓藤') }) it('translates rare and magic names correctly with proper Chinese word order', () => { expect(translateRareName('Beast Edge')).toBe('野兽之刃') expect(translateRareName('Shadow Brow')).toBe('阴影之额头') // Prefix only expect(translateMagicName('Sturdy Leather Armor', 'Leather Armor')).toBe('结实之皮甲') // Suffix only: modifier before noun expect(translateMagicName('War Spear of Swiftness', 'War Spear')).toBe('疾速之巨战长矛') // Both prefix & suffix: modifiers before noun expect(translateMagicName('Savage Short Sword of the Giant', 'Short Sword')).toBe('野蛮之巨人之短剑') // 100% Affix Coverage test for previously missing items expect(translateMagicName("Harpoonist's Ceremonial Javelin", 'Ceremonial Javelin')).toBe('标枪之祭典标枪') expect(translateMagicName('Trump Edge Bow', 'Edge Bow')).toBe('胜者之锋锐之弓') expect(translateMagicName('Gritty Broad Axe', 'Broad Axe')).toBe('坚韧之阔斧') }) it('translates item names to normalized Simplified Chinese', () => { expect(translateItemName('Flawless Ruby')).toBe('无瑕疵的红宝石') expect(translateItemName('Flawless Emerald')).toBe('无瑕疵的绿宝石') expect(translateItemName('The Humongous')).toBe('巨大无比') expect(translateItemName('Steelclash')).toBe('作响的金属') }) }) describe('3. Property Code Formatting in Chinese vs English', () => { it('formats skills in Chinese', () => { expect(formatPropertyCodeI18n('allskills', 2, undefined, undefined, undefined, 'zh')).toBe('+2 所有技能') expect(formatPropertyCodeI18n('ama', 1, undefined, undefined, undefined, 'zh')).toBe('+1 亚马逊技能等级') expect(formatPropertyCodeI18n('skill', 3, undefined, undefined, 'Teleport', 'zh')).toBe('+3 传送 (限法师使用)') expect(formatPropertyCodeI18n('oskill', 1, undefined, undefined, 'Teleport', 'zh')).toBe('+1 传送') }) it('formats attributes & life/mana in Chinese', () => { expect(formatPropertyCodeI18n('str', 20, undefined, undefined, undefined, 'zh')).toBe('+20 力量') expect(formatPropertyCodeI18n('dex', 15, undefined, undefined, undefined, 'zh')).toBe('+15 敏捷') expect(formatPropertyCodeI18n('all-stats', 10, undefined, undefined, undefined, 'zh')).toBe('+10 所有属性') expect(formatPropertyCodeI18n('hp', 50, undefined, undefined, undefined, 'zh')).toBe('+50 生命') expect(formatPropertyCodeI18n('mana', 40, undefined, undefined, undefined, 'zh')).toBe('+40 法力') }) it('formats combat speeds and resistances in Chinese', () => { expect(formatPropertyCodeI18n('fcr', 20, undefined, undefined, undefined, 'zh')).toBe('+20% 快速施法速度') expect(formatPropertyCodeI18n('fhr', 30, undefined, undefined, undefined, 'zh')).toBe('+30% 快速打击恢复') expect(formatPropertyCodeI18n('ias', 20, undefined, undefined, undefined, 'zh')).toBe('+20% 增加攻击速度') expect(formatPropertyCodeI18n('res-all', 15, undefined, undefined, undefined, 'zh')).toBe('所有抗性 +15') expect(formatPropertyCodeI18n('res-fire', 30, undefined, undefined, undefined, 'zh')).toBe('抗火 +30%') }) it('formats per-level scaling and repair rate in Chinese', () => { expect(formatPropertyCodeI18n('hp/lvl', 12, undefined, undefined, undefined, 'zh')).toBe('+1.5 生命 (依角色等级提升)') expect(formatPropertyCodeI18n('rep-dur', 0, 0, undefined, 33, 'zh')).toBe('每 3 秒恢复 1 点耐久度') }) it('handles charged spell properties with MPQ negative encoding', () => { // In 1.13c MPQ, charges and level are stored as negative numbers e.g. min: -20, max: -3 expect(formatPropertyCodeI18n('charged', -20, -20, -3, 'Enchant', 'zh')).toBe('等级 3 强化 (20/20 次充能)') expect(formatPropertyCodeI18n('charged', -20, -20, -3, 'Enchant', 'en')).toBe('Level 3 Enchant (20/20 Charges)') }) it('handles asterisk-prefixed properties like *hp from UniqueItems.txt', () => { expect(formatPropertyCodeI18n('*hp', -10, -10, -10, undefined, 'zh')).toBe('-10 生命') expect(formatPropertyCodeI18n('*hp', -10, -10, -10, undefined, 'en')).toBe('-10 to Life') }) }) describe('4. formatItemTooltip: EN, ZH, and Bilingual Modes', () => { const sampleHarlequin = { id: 'item-shako-unique', name: 'Harlequin Crest', rarity: 'unique', level: 87, base: { id: 'uap', name: 'Shako', kind: 'armor', defense: 141, durability: 12, reqstr: 50, levelreq: 62, }, stats: {}, uniqueItemDef: { index: 'Harlequin Crest', level: 69, levelreq: 62, code: 'uap', rarity: 1, props: [ { code: 'allskills', min: 2, max: 2 }, { code: 'all-stats', min: 2, max: 2 }, { code: 'hp/lvl', min: 12, max: 12 }, { code: 'mana/lvl', min: 12, max: 12 }, { code: 'dmag', min: 10, max: 10 }, { code: 'mag%', min: 50, max: 50 }, ], }, } as unknown as Item it('formats in pure 1.13c English by default (locale === "en")', () => { const tt = formatItemTooltip(sampleHarlequin, 'en') expect(tt.title).toBe('Harlequin Crest') expect(tt.subTitle).toBe('Shako') expect(tt.quality).toBe('Unique') expect(tt.lines.some(l => l.text === '+2 to All Skills')).toBe(true) expect(tt.lines.some(l => l.text === '+2 to All Attributes')).toBe(true) expect(tt.lines.some(l => l.text === '50% Better Chance of Getting Magic Items')).toBe(true) }) it('formats in Simplified Chinese when locale === "zh"', () => { const tt = formatItemTooltip(sampleHarlequin, 'zh') expect(tt.title).toBe('谐角之冠') expect(tt.subTitle).toBe('军帽') expect(tt.quality).toBe('暗金') expect(tt.lines.some(l => l.text === '+2 所有技能')).toBe(true) expect(tt.lines.some(l => l.text === '+2 所有属性')).toBe(true) expect(tt.lines.some(l => l.text === '50% 获得魔法装备几率')).toBe(true) }) it('formats in Bilingual mode when locale === "bilingual"', () => { const tt = formatItemTooltip(sampleHarlequin, 'bilingual') expect(tt.title).toBe('Harlequin Crest (谐角之冠)') expect(tt.subTitle).toBe('Shako (军帽)') expect(tt.lines.some(l => l.text === '+2 所有技能')).toBe(true) expect(tt.lines.some(l => l.text === '+2 所有属性')).toBe(true) }) it('formats Runes correctly across all 3 modes', () => { const sampleRune = { id: 'item-rune-01', name: 'El Rune', code: 'r01', rarity: 'normal', base: { id: 'r01', name: 'El Rune', kind: 'misc' }, stats: {}, } as unknown as Item const ttEn = formatItemTooltip(sampleRune, 'en') expect(ttEn.title).toBe('El Rune') expect(ttEn.subTitle).toBe('Rune (1)') expect(ttEn.lines[0]?.text).toBe('Can be Inserted into Socketed Items') const ttZh = formatItemTooltip(sampleRune, 'zh') expect(ttZh.title).toBe('艾尔符文') expect(ttZh.subTitle).toBe('符文 (1)') expect(ttZh.lines[0]?.text).toBe('可镶嵌进有凹槽的装备') const ttBi = formatItemTooltip(sampleRune, 'bilingual') expect(ttBi.title).toBe('El Rune (艾尔)') expect(ttBi.subTitle).toBe('Rune (1)') expect(ttBi.lines[0]?.text).toBe('可镶嵌进有凹槽的装备') }) }) })