diablo2-web/tests/item-tooltip-i18n-122.test.ts

236 lines
11 KiB
TypeScript

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('可镶嵌进有凹槽的装备')
})
})
})