Files
diablo2d/js/core/i18n.js
T
deepseek fc1fa2d51e Diablo2D — Shadows of Tristram: complete browser ARPG
- Isometric canvas renderer (depth-sorted, FOV/fog, additive lighting)
- 3 classes x 20 skills, 4 acts x 4 floors + boss lairs, torment I-X
- Diablo-style loot: rarities, affix tiers, 14 legendaries, vendor, stash
- Rogue camp with 6 NPCs: Charsi/Akara/Kashya/Cain/Gheed/storage
- NPC quest chain (accept -> hunt -> turn in) with rewards & gating
- Procedural WebAudio SFX + generative music, EN/VI localization
- Saves, settings, waypoints, hardcore mode, PWA manifest
- 93-assertion headless suite + browser E2E via CDP
2026-08-23 06:59:36 +00:00

304 lines
19 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================
* Diablo2D — i18n.js : English / Vietnamese localization
* ============================================================ */
'use strict';
window.D2 = window.D2 || {};
(function (D2) {
const dict = {
en: {
/* generic */
ok: 'OK', cancel: 'Cancel', close: 'Close', yes: 'Yes', no: 'No',
back: 'Back', confirm: 'Confirm', none: 'None', level_short: 'Lv',
/* title */
'title.newgame': 'New Game', 'title.continue': 'Continue', 'title.settings': 'Settings',
'title.help': 'How to Play', 'title.credits': 'Credits',
'title.tagline': 'Shadows of Tristram',
'title.subtitle': 'A dark action-RPG for your browser',
'title.no_save': 'No saved hero found.',
'title.confirm_overwrite': 'Starting a new game will overwrite your saved hero. Continue?',
'title.saved_hero': 'Saved Hero',
'title.delete_save': 'Delete Save',
'title.confirm_delete': 'Permanently delete the saved hero?',
/* classes */
'class.crusader.name': 'Crusader',
'class.crusader.desc': 'A holy warrior clad in heavy armor. Wades into melee with sweeping strikes and unbreakable defenses.',
'class.ranger.name': 'Shadow Huntress',
'class.ranger.desc': 'A swift rogue who strikes from the shadows with bow and blade, bleeding her prey from afar.',
'class.sorceress.name': 'Sorceress',
'class.sorceress.desc': 'Master of fire, frost and storm. Fragile in body, devastating in arcane power.',
'class.select': 'Choose Your Fate',
'class.play': 'Begin Descent',
/* stats */
'stat.strength': 'Strength', 'stat.dexterity': 'Dexterity', 'stat.vitality': 'Vitality', 'stat.energy': 'Energy',
'stat.damage': 'Damage', 'stat.armor': 'Armor', 'stat.crit': 'Crit Chance', 'stat.critdmg': 'Crit Damage',
'stat.speed': 'Move Speed', 'stat.magicfind': 'Magic Find', 'stat.lifelench': 'Life per Kill',
'stat.fire': 'Fire Resist', 'stat.cold': 'Cold Resist', 'stat.lightning': 'Lightning Resist', 'stat.poison': 'Poison Resist',
'stat.points': 'Attribute Points', 'stat.skillpoints': 'Skill Points',
/* ui panels */
'ui.inventory': 'Inventory', 'ui.character': 'Character', 'ui.skills': 'Skills',
'ui.stash': 'Stash', 'ui.vendor': 'Blacksmith', 'ui.healer': 'Healer',
'ui.questlog': 'Quest Log', 'ui.map': 'World Map', 'ui.help': 'How to Play',
'ui.equip.weapon': 'Weapon', 'ui.equip.offhand': 'Off-hand', 'ui.equip.head': 'Head',
'ui.equip.chest': 'Body', 'ui.equip.hands': 'Hands', 'ui.equip.feet': 'Feet',
'ui.equip.amulet': 'Amulet', 'ui.equip.ring1': 'Ring', 'ui.equip.ring2': 'Ring',
'ui.gold': 'Gold', 'ui.sell': 'Sell', 'ui.buy': 'Buy', 'ui.repair_none': '',
'ui.heal_full': 'Full Restore', 'ui.price': 'Price',
'ui.empty_slot': 'Empty', 'ui.drop_confirm': 'This item will be destroyed. Drop it?',
/* hotbar */
'hb.health_potion': 'Health Potion', 'hb.mana_potion': 'Mana Potion',
'hb.inventory': 'Inventory (I)', 'hb.character': 'Character (C)', 'hb.skills': 'Skills (T)',
'hb.quests': 'Quest Log (J)', 'hb.map': 'Map (M)', 'hb.help': 'Help (F1)',
/* messages */
'msg.levelup': 'Level Up! Press C to assign points.',
'msg.skillpoint': 'New skill learned! Press T to view skills.',
'msg.not_enough_mana': 'Not enough mana!',
'msg.skill_cooldown': 'Skill is not ready.',
'msg.inventory_full': 'Inventory is full!',
'msg.potion_none': 'No potions left!',
'msg.boss_spawn': 'A great evil awakens...',
'msg.boss_slain': 'The guardian has fallen!',
'msg.act_clear': 'Act complete! The waypoint stirs...',
'msg.saved': 'Game saved.',
'msg.hardcore_death': 'Your hardcore hero has perished. The record burns.',
'msg.town_portal': 'Returned to town.',
'msg.elite_slain': 'Champion slain!',
'msg.shrine': 'You touch the shrine...',
/* quests */
'quest.main_prefix': 'Act {0}: ',
'quest.descend': 'Descend to depth {0}',
'quest.kill_boss': 'Slay {0}',
'quest.done': 'Complete',
/* death / victory */
'death.title': 'YOU HAVE DIED',
'death.subtitle': 'Even heroes fall... but darkness always offers another chance.',
'death.respawn': 'Awaken in Town', 'death.title_screen': 'Return to Title',
'victory.title': 'THE LORD OF TERROR IS VANQUISHED',
'victory.subtitle': 'Tristram breathes again — yet the depths always call louder.',
'victory.continue': 'Continue in Torment {0}', 'victory.title_screen': 'Return to Title',
'victory.stats': 'Run Statistics',
/* settings */
'set.master': 'Master Volume', 'set.music': 'Music', 'set.sfx': 'Sound Effects',
'set.lang': 'Language', 'set.shake': 'Screen Shake', 'set.dmgnum': 'Damage Numbers',
'set.labels': 'Item Labels', 'set.hardcore': 'Hardcore Mode (new game)',
'set.export': 'Export Save Code', 'set.import': 'Import Save Code', 'set.wipe': 'Erase All Data',
'set.copy_ok': 'Copied!', 'set.import_bad': 'Invalid save code.', 'set.import_ok': 'Save imported!',
'set.confirm_wipe': 'Erase ALL saved data? This cannot be undone.',
/* help */
'help.move': 'Left click — move / attack. Hold to keep moving toward cursor.',
'help.combat': 'Right click & keys 14 — cast skills. Q / E — potions.',
'help.items': 'Hold Alt to reveal ground item labels. Click an item to pick up.',
'help.panels': 'I inventory · C character · T skills · J quests · M map · Esc pause.',
'help.tip1': 'Champions glow with an aura — they hit harder but drop better loot.',
'help.tip2': 'Shrines grant powerful temporary blessings.',
'help.tip3': 'Resistances matter more than armor against elemental bosses.',
'help.keys_title': 'Controls',
/* town npcs */
'npc.charsi.name': 'Charsi',
'npc.akara.name': 'Akara',
'npc.kashya.name': 'Kashya',
'npc.cain.name': 'Deckard Cain',
'npc.gheed.name': 'Gheed',
'npc.stash.name': 'Camp Storage',
'msg.quest_accepted': 'Quest accepted: {0}',
'msg.quest_reward': 'Quest complete! +{0} gold',
'quest.seek_hint': 'Visit camp — new tasks await.',
'ui.rare_item': 'a Rare item',
'npc.charsi.name': 'Charsi',
'npc.akara.name': 'Akara',
'npc.kashya.name': 'Kashya',
'npc.cain.name': 'Deckard Cain',
'npc.gheed.name': 'Gheed',
'npc.stash.name': 'Kho trại',
'msg.quest_accepted': 'Đã nhận nhiệm vụ: {0}',
'msg.quest_reward': 'Nhiệm vụ hoàn thành! +{0} vàng',
'quest.seek_hint': 'Hãy ghé trại — có việc mới đang chờ.',
'ui.rare_item': 'một món Hiếm',
'npc.charsi.greet': 'Steel keeps you alive down there. Browse my wares.',
'npc.akara.greet': 'The Light mend you. What do you need, child?',
'npc.kashya.greet': 'The camp needs a blade like yours. We have work.',
'npc.cain.greet': 'Stay awhile and listen…',
'npc.gheed.greet': 'Psst. Finest goods in the camp — provenance uncertain.',
'npc.stash.greet': 'Your vault awaits.',
'npc.waypoint.greet': 'Where does fate pull you, hero?',
'ui.accept': 'Accept quest',
'ui.turn_in': 'Turn in',
'ui.reward': 'Reward',
'ui.later': 'Not now',
'ui.lore': 'Ask for lore',
'ui.buy_tome': 'Buy Skill Tome (+1 skill pt)',
'ui.respec': 'Respec all points',
'ui.gamble': 'Gamble for goods',
'ui.gamble_hint': 'Rarity unknown until bought…',
'ui.quest_done_return': 'Return to {0}',
'ui.tome_bought': 'Knowledge burns behind your eyes… (+1 skill point)',
'ui.respec_done': 'Your training is undone. Points refunded.',
'ui.gamble_win': 'Fortune favors you… this time.',
'quest.cull': 'Cull the Fallen',
'quest.cull.desc': 'Slay {0} denizens of the Cathedral depths. Kashya counts every kill.',
'quest.boss': 'The {0}',
'quest.boss.desc': 'Descend to the lair and destroy {0}. The camp will not rest until it is done.',
'quest.torment': 'Echoes of Terror',
'quest.torment.desc': 'Malgor is dead, yet his echo festers. Open torment {0} and cull it anew.',
'town.enter': 'Tristram Outpost — Sanctuary',
/* misc */
'floor.depth': 'Depth {0}',
'act.names.0': 'The Ravaged Cathedral',
'act.names.1': 'The Weeping Catacombs',
'act.names.2': 'The Fungal Depths',
'act.names.3': "Hell's Threshold",
torment: 'Torment {0}',
'loading.tips.0': 'Elites drop enchanted loot. Hunt the glowing ones.',
'loading.tips.1': 'Potions refill each time you visit town.',
'loading.tips.2': 'Fire melts flesh; cold freezes bone. Bring both.',
'loading.tips.3': 'Barrels and urns hide coins... and sometimes worse.',
'loading.tips.4': 'Magic Find increases rare and legendary drops.',
'credits.line1': 'Design, code, art & audio — procedurally generated, no external assets.',
'credits.line2': 'Inspired by the classics that defined the genre.',
},
vi: {
ok: 'Đồng ý', cancel: 'Hủy', close: 'Đóng', yes: 'Có', no: 'Không',
back: 'Quay lại', confirm: 'Xác nhận', none: 'Không', level_short: 'Cấp',
'title.newgame': 'Chơi mới', 'title.continue': 'Tiếp tục', 'title.settings': 'Cài đặt',
'title.help': 'Hướng dẫn chơi', 'title.credits': 'Thông tin',
'title.tagline': 'Bóng Đêm Tristram',
'title.subtitle': 'Action-RPG tối giản chạy ngay trên trình duyệt',
'title.no_save': 'Không tìm thấy bản lưu nào.',
'title.confirm_overwrite': 'Bắt đầu game mới sẽ xóa hero đã lưu. Tiếp tục?',
'title.saved_hero': 'Hero đã lưu',
'title.delete_save': 'Xóa bản lưu',
'title.confirm_delete': 'Xóa vĩnh viễn hero đã lưu?',
'class.crusader.name': 'Thập Tự Quân',
'class.crusader.desc': 'Chiến binh thánh thiện trong bộ giáp nặng, quét sạch kẻ thù bằng chém rộng và phòng thủ thép.',
'class.ranger.name': 'Nữ Thợ Săn Bóng Đêm',
'class.ranger.desc': 'Nữ sát thủ nhanh nhẹn, hạ gục con mồi từ xa bằng cung tên độc và bóng tối.',
'class.sorceress.name': 'Pháp Sư',
'class.sorceress.desc': 'Bậc thầy hỏa, băng và lôi. Thân yếu nhưng sức mạnh huyền thuật tàn khốc.',
'class.select': 'Chọn Số Phận Của Bạn',
'class.play': 'Bắt Đầu Hạ Sơn',
'stat.strength': 'Sức mạnh', 'stat.dexterity': 'Nhuệ', 'stat.vitality': 'Sinh lực', 'stat.energy': 'Năng lượng',
'stat.damage': 'Sát thương', 'stat.armor': 'Giáp', 'stat.crit': 'Tỷ lệ chí mạng', 'stat.critdmg': 'ST chí mạng',
'stat.speed': 'Tốc độ chạy', 'stat.magicfind': 'May mắn tìm đồ', 'stat.lifelench': 'Hồi máu mỗi lần hạ',
'stat.fire': 'Kháng Hỏa', 'stat.cold': 'Kháng Băng', 'stat.lightning': 'Kháng Lôi', 'stat.poison': 'Kháng Độc',
'stat.points': 'Điểm thuộc tính', 'stat.skillpoints': 'Điểm kỹ năng',
'ui.inventory': 'Túi đồ', 'ui.character': 'Nhân vật', 'ui.skills': 'Kỹ năng',
'ui.stash': 'Kho đồ', 'ui.vendor': 'Thợ Rèn', 'ui.healer': 'Pháp Sư Trị Liệu',
'ui.questlog': 'Nhật nhiệm vụ', 'ui.map': 'Bản đồ thế giới', 'ui.help': 'Hướng dẫn',
'ui.equip.weapon': 'Vũ khí', 'ui.equip.offhand': 'Tay trái', 'ui.equip.head': 'Đầu',
'ui.equip.chest': 'Thân', 'ui.equip.hands': 'Tay', 'ui.equip.feet': 'Chân',
'ui.equip.amulet': 'Mặt dây', 'ui.equip.ring1': 'Nhẫn', 'ui.equip.ring2': 'Nhẫn',
'ui.gold': 'Vàng', 'ui.sell': 'Bán', 'ui.buy': 'Mua',
'ui.heal_full': 'Hồi phục hoàn toàn', 'ui.price': 'Giá',
'ui.empty_slot': 'Trống', 'ui.drop_confirm': 'Vật phẩm sẽ bị phá hủy. Vứt bỏ?',
'hb.health_potion': 'Bình máu', 'hb.mana_potion': 'Bình mana',
'hb.inventory': 'Túi đồ (I)', 'hb.character': 'Nhân vật (C)', 'hb.skills': 'Kỹ năng (T)',
'hb.quests': 'Nhiệm vụ (J)', 'hb.map': 'Bản đồ (M)', 'hb.help': 'Trợ giúp (F1)',
'msg.levelup': 'Lên cấp! Nhấn C để cộng điểm.',
'msg.skillpoint': 'Kỹ năng mới! Nhấn T để xem kỹ năng.',
'msg.not_enough_mana': 'Không đủ mana!',
'msg.skill_cooldown': 'Kỹ năng chưa hồi xong.',
'msg.inventory_full': 'Túi đồ đầy!',
'msg.potion_none': 'Hết bình hồi máu!',
'msg.boss_spawn': 'Một ma lực khổng lồ thức dậy...',
'msg.boss_slain': 'Ác thần đã bị hạ!',
'msg.act_clear': 'Hoàn thành chương! Điểm dịch chuyển rung lên...',
'msg.saved': 'Đã lưu game.',
'msg.hardcore_death': 'Hero Hardcore của bạn đã chết. Bản lưu bị thiêu rụi.',
'msg.town_portal': 'Đã về trấn.',
'msg.elite_slain': 'Đã hạ tinh anh!',
'msg.shrine': 'Bạn chạm vào đền thờ...',
'quest.main_prefix': 'Chương {0}: ',
'quest.descend': 'Hạ xuống độ sâu {0}',
'quest.kill_boss': 'Hạ gục {0}',
'quest.done': 'Hoàn thành',
'death.title': 'BẠN ĐÃ CHẾT',
'death.subtitle': 'Anh hùng cũng có thể ngã... nhưng bóng tối luôn cho thêm một cơ hội.',
'death.respawn': 'Thức dậy ở trấn', 'death.title_screen': 'Về màn hình chính',
'victory.title': 'CHÚA TỂ KINH HOÀNG ĐÃ BỊ TIÊU DIỆT',
'victory.subtitle': 'Tristram lại được thở — nhưng vực sâu luôn gọi to hơn.',
'victory.continue': 'Tiếp tục ở Torment {0}', 'victory.title_screen': 'Về màn hình chính',
'victory.stats': 'Thống kê chuyến chiến',
'set.master': 'Âm lượng tổng', 'set.music': 'Nhạc nền', 'set.sfx': 'Hiệu ứng âm thanh',
'set.lang': 'Ngôn ngữ', 'set.shake': 'Rung màn hình', 'set.dmgnum': 'Số sát thương',
'set.labels': 'Nhãn đồ trên đất', 'set.hardcore': 'Chế độ Hardcore (chơi mới)',
'set.export': 'Xuất mã lưu', 'set.import': 'Nhập mã lưu', 'set.wipe': 'Xóa toàn bộ dữ liệu',
'set.copy_ok': 'Đã sao chép!', 'set.import_bad': 'Mã lưu không hợp lệ.', 'set.import_ok': 'Đã nhập bản lưu!',
'set.confirm_wipe': 'Xóa TOÀN BỘ dữ liệu đã lưu? Không thể hoàn tác.',
'help.move': 'Chuột trái — di chuyển / tấn công. Giữ để liên tục đi theo con trỏ.',
'help.combat': 'Chuột phải & phím 14 — dùng kỹ năng. Q / E — bình máu, bình mana.',
'help.items': 'Giữ Alt để hiện nhãn đồ trên đất. Nhấp vào món để nhặt.',
'help.panels': 'I túi đồ · C nhân vật · T kỹ năng · J nhiệm vụ · M bản đồ · Esc tạm dừng.',
'help.tip1': 'Tinh anh phát sáng quanh thân — mạnh hơn nhưng rớt đồ tốt hơn.',
'help.tip2': 'Đền thờ ban phước tạm thời rất mạnh.',
'help.tip3': 'Kháng nguyên tố quan trọng hơn giáp khi đấu boss hệ elemental.',
'help.keys_title': 'Điều khiển',
'npc.charsi.greet': 'Thép giữ bạn sống dưới đó. Xem hàng của ta đi.',
'npc.akara.greet': 'Ánh Sáng chữa lành ngươi. Con cần gì nào?',
'npc.kashya.greet': 'Trại cần một lưỡi kiếm như ngươi. Ta có việc đây.',
'npc.cain.greet': 'Ở lại nghe ta kể chuyện này…',
'npc.gheed.greet': 'Này. Hàng tốt nhất trại — nguồn gốc thì… khỏi hỏi.',
'npc.stash.greet': 'Kho của bạn đang chờ.',
'npc.waypoint.greet': 'Định mệnh đang kéo bạn đến đâu, anh hùng?',
'ui.accept': 'Nhận nhiệm vụ',
'ui.turn_in': 'Trả nhiệm vụ',
'ui.reward': 'Thưởng',
'ui.later': 'Để sau',
'ui.lore': 'Hỏi chuyện xưa',
'ui.buy_tome': 'Mua Sách Kỹ năng (+1 điểm)',
'ui.respec': 'Phân bổ lại toàn bộ điểm',
'ui.gamble': 'Đánh bạc đổi hàng',
'ui.gamble_hint': 'Đến khi mua mới biết hiếm hay thường…',
'ui.quest_done_return': 'Quay về gặp {0}',
'ui.tome_bought': 'Tri thức bừng cháy trong đầu con… (+1 điểm kỹ năng)',
'ui.respec_done': 'Toàn bộ điểm đã hoàn trả.',
'ui.gamble_win': 'Thần may mắn đứng về phía ngươi… lần này.',
'quest.cull': 'Thanh tẩy Kẻ ngã ngã',
'quest.cull.desc': 'Hạ {0} sinh vật trong hang động. Kashya đếm từng mạng.',
'quest.boss': '{0}',
'quest.boss.desc': 'Xuống tới hang ổ và tiêu diệt {0}. Trại sẽ không yên khi nó còn sống.',
'quest.torment': 'Ám ảnh Khủng khiếp',
'quest.torment.desc': 'Malgor chết rồi nhưng bóng ma nó còn. Mở torment {0} và thanh tẩy lại.',
'town.enter': 'Trấn Tristram — Nơi Tôn Nghiêm',
'floor.depth': 'Độ sâu {0}',
'act.names.0': 'Nhà thờ hoang tàn',
'act.names.1': 'Hầm mộ than khóc',
'act.names.2': 'Vực nấm sâu thẳm',
'act.names.3': 'Ngưỡng cửa Địa Ngục',
torment: 'Torment {0}',
'loading.tips.0': 'Tinh anh rớt đồ phù phép. Săn những con phát sáng.',
'loading.tips.1': 'Bình hồi máu được nạp lại mỗi khi về trấn.',
'loading.tips.2': 'Lửa thiêu thịt; băng đóng băng xương. Mang cả hai.',
'loading.tips.3': 'Thùng rượu và lư đồng giấu vàng... đôi khi còn giấu cả thứ khác.',
'loading.tips.4': 'May mắn tìm đồ tăng tỷ lệ rớt đồ hiếm và huyền thoại.',
'credits.line1': 'Thiết kế, lập trình, hình ảnh & âm nhạc — sinh ra thủ tục, không dùng tài nguyên ngoài.',
'credits.line2': 'Lấy cảm hứng từ những tựa game kinh điển định hình thể loại.',
},
};
let lang = 'en';
function t(key, ...vars) {
let s = (dict[lang] && dict[lang][key]) ?? dict.en[key] ?? key;
vars.forEach((v, i) => { s = s.replaceAll('{' + i + '}', String(v)); });
return s;
}
function setLang(l) { lang = (dict[l] ? l : 'en'); if (typeof document !== 'undefined') document.documentElement.lang = lang; }
function getLang() { return lang; }
function languages() { return Object.keys(dict); }
/* apply data-i18n attributes inside a root element */
function applyDom(root = document) {
root.querySelectorAll('[data-i18n]').forEach(el => {
el.textContent = t(el.getAttribute('data-i18n'));
});
root.querySelectorAll('[data-i18n-title]').forEach(el => {
el.title = t(el.getAttribute('data-i18n-title'));
});
}
D2.i18n = { t, setLang, getLang, languages, applyDom, dict };
})(window.D2);