first commit

This commit is contained in:
jakciehan
2026-04-10 22:59:39 +08:00
commit cc2e7b9bb0
89 changed files with 23631 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
/**
* I18n.js
* Internationalization module for Tank Adventure.
* Auto-detects language from WeChat system info.
* Supports {variable} placeholder interpolation.
*/
const zhLang = require('./zh');
const enLang = require('./en');
// ============================================================
// Language Detection
// ============================================================
let _currentLang = 'en'; // default fallback
try {
const sysInfo = wx.getSystemInfoSync();
const lang = (sysInfo.language || '').toLowerCase();
// zh_CN, zh_TW, zh_HK, etc.
if (lang.startsWith('zh')) {
_currentLang = 'zh';
}
} catch (e) {
// Fallback to English if wx API is unavailable
_currentLang = 'en';
}
const _langPacks = {
zh: zhLang,
en: enLang,
};
// ============================================================
// Translation Function
// ============================================================
/**
* Get translated text by key.
* Supports {variable} placeholder interpolation.
*
* @param {string} key - The translation key, e.g. 'menu.title'
* @param {Object} [params] - Optional parameters for interpolation
* @returns {string} The translated text
*
* @example
* t('menu.title') // => '坦克探险' or 'Tank Adventure'
* t('pvp.hp', { count: 3 }) // => '生命 x3' or 'HP x3'
*/
function t(key, params) {
// Try current language first
let text = _langPacks[_currentLang] && _langPacks[_currentLang][key];
// Fallback to English
if (text === undefined && _currentLang !== 'en') {
text = _langPacks.en[key];
}
// Fallback to key itself
if (text === undefined) {
return key;
}
// Interpolate {variable} placeholders
if (params) {
text = text.replace(/\{(\w+)\}/g, (match, name) => {
return params[name] !== undefined ? String(params[name]) : match;
});
}
return text;
}
/**
* Get the current language code.
* @returns {string} 'zh' or 'en'
*/
function getLang() {
return _currentLang;
}
/**
* Set the language manually (for testing or future settings).
* @param {string} lang - 'zh' or 'en'
*/
function setLang(lang) {
if (_langPacks[lang]) {
_currentLang = lang;
}
}
module.exports = { t, getLang, setLang };
+272
View File
@@ -0,0 +1,272 @@
/**
* en.js
* English language pack for Tank Adventure.
*/
module.exports = {
// ============================================================
// Common
// ============================================================
'common.back': '← Back',
'common.joinBtn': 'Join',
'common.cannotConnect': 'Cannot connect to server',
'common.connectFailed': 'Connection failed',
'common.disconnected': 'Disconnected from server',
'common.paused': 'PAUSED',
'common.tapContinue': 'Tap to continue',
'common.kicked': 'You have been kicked from the team',
// ============================================================
// Menu Scene
// ============================================================
'menu.title': 'Tank Adventure',
'menu.subtitle': 'TANK WAR',
'menu.classic': 'Classic',
'menu.endless': 'Endless',
'menu.pvp': 'PVP',
'menu.team3v3': '3v3 Battle',
'menu.shop': 'Shop',
'menu.ranking': 'Ranking',
'menu.settings': 'Settings',
// ============================================================
// Room Scene (PVP)
// ============================================================
'room.title': 'PVP Battle',
'room.idleHint': 'Create a room or join with a code',
'room.create': 'Create Room',
'room.join': 'Join Room',
'room.connecting': 'Connecting{dots}',
'room.roomCode': 'Room Code:',
'room.waiting': 'Waiting for opponent{dots}',
'room.shareHint': 'Share the room code with your friend',
'room.inputCode': 'Enter Room Code:',
'room.opponentFound': 'Opponent found!',
'room.starting': 'Game starting...',
'room.tapBack': 'Tap anywhere to go back',
// ============================================================
// Team Room Scene (3v3)
// ============================================================
'teamRoom.title': '3v3 Team Battle',
'teamRoom.chooseMode': 'Choose how to play',
'teamRoom.createTeam': '🎮 Create Team',
'teamRoom.soloMatch': '⚡ Quick Match',
'teamRoom.teamId': 'Team: {id}',
'teamRoom.leader': 'Leader',
'teamRoom.ready': '✓ Ready',
'teamRoom.notReady': 'Not Ready',
'teamRoom.emptySlot': 'Empty',
'teamRoom.invite': '📨 Invite',
'teamRoom.startMatch': '🔍 Start Match',
'teamRoom.disband': 'Disband',
'teamRoom.readyBtn': '✓ Ready',
'teamRoom.cancelReady': 'Cancel Ready',
'teamRoom.leaveTeam': 'Leave Team',
'teamRoom.matching': 'Matching{dots}',
'teamRoom.waitTime': 'Waited {seconds}s',
'teamRoom.cancelMatch': 'Cancel Match',
'teamRoom.matchFound': 'Match found!',
'teamRoom.enterBattle': 'Entering battle...',
'teamRoom.tapBack': 'Tap anywhere to go back',
'teamRoom.shareTitle': 'Tank 3v3, join the battle!',
'teamRoom.joining': 'Joining room',
// ============================================================
// PVP Game Scene
// ============================================================
'pvp.playerLabel': 'P{slot} (You)',
'pvp.hp': 'HP x{count}',
'pvp.kills': 'Kills: {count}',
'pvp.killDeath': 'K:{kills} D:{deaths}',
'pvp.respawn': 'Respawning in {seconds}s',
'pvp.youWin': 'YOU WIN!',
'pvp.draw': 'DRAW',
'pvp.youLose': 'YOU LOSE',
'pvp.baseHpSummary': 'P1: {hp1} HP | P2: {hp2} HP',
// ============================================================
// Team Game Scene (3v3)
// ============================================================
'team.teamA': 'Team A',
'team.teamB': 'Team B',
'team.myTeam': 'You: {team} Team',
'team.killDeath': 'K:{kills} D:{deaths}',
'team.respawn': 'Respawning in {seconds}s',
'team.victory': 'VICTORY!',
'team.defeat': 'DEFEAT',
'team.baseHpSummary': 'Team A: {hpA} HP | Team B: {hpB} HP',
'team.disconnectTitle': '⚠ Connection Lost',
'team.reconnecting': 'Reconnecting{dots} ({attempts}/{max})',
'team.reconnectHint': 'Please wait, your tank will be controlled by AI',
// ============================================================
// PVP Result Scene
// ============================================================
'pvpResult.title': 'MATCH RESULT',
'pvpResult.victory': '🏆 VICTORY!',
'pvpResult.draw': '⚔️ DRAW',
'pvpResult.defeat': '😵 DEFEAT',
'pvpResult.kills': 'Kills',
'pvpResult.deaths': 'Deaths',
'pvpResult.lives': 'Lives',
'pvpResult.baseDmg': 'Base DMG',
'pvpResult.p1BaseHp': 'P1: {hp} HP',
'pvpResult.p2BaseHp': 'P2: {hp} HP',
'pvpResult.baseDestroyed': 'Base Destroyed',
'pvpResult.disconnectedReason': 'Disconnected',
'pvpResult.duration': 'Match duration: {time}',
'pvpResult.timeRemaining': 'Time remaining: {time}',
'pvpResult.rematch': 'Rematch',
'pvpResult.backMenu': 'Back to Menu',
// ============================================================
// Team Result Scene (3v3)
// ============================================================
'teamResult.title': '3v3 MATCH RESULT',
'teamResult.victory': '🏆 VICTORY!',
'teamResult.defeat': '😵 DEFEAT',
'teamResult.teamAHp': 'Team A: {hp} HP',
'teamResult.teamBHp': 'Team B: {hp} HP',
'teamResult.baseDestroyed': 'Base Destroyed',
'teamResult.disconnectedReason': 'Disconnected',
'teamResult.teamAHeader': 'Team A',
'teamResult.teamBHeader': 'Team B',
'teamResult.myTeamSuffix': ' (You)',
'teamResult.player': 'Player',
'teamResult.k': 'K',
'teamResult.d': 'D',
'teamResult.a': 'A',
'teamResult.dmg': 'DMG',
'teamResult.bot': '🤖 Bot',
'teamResult.duration': 'Match duration: {time}',
'teamResult.mvp': '⭐ MVP: {name} ({kills} kills)',
'teamResult.rankUp': '📈 Rank +{points}',
'teamResult.mvpBonus': '(MVP bonus +5)',
'teamResult.rankDown': '📉 Rank -{points}',
'teamResult.rematch': 'Rematch',
'teamResult.rematchWaiting': 'Waiting({ready}/{total})',
'teamResult.backMenu': 'Back to Menu',
// ============================================================
// Game Scene (Classic/Endless)
// ============================================================
'game.level': 'Level {level}',
'game.hp': 'HP x{count}',
'game.fireLevel': 'LV{level}',
'game.enemies': 'Enemies: {count}',
'game.score': '{score}pts',
'game.gameOver': 'GAME OVER',
'game.stageClear': 'STAGE CLEAR!',
// ============================================================
// Result Scene
// ============================================================
'result.victory': '🎉 STAGE CLEAR!',
'result.defeat': '😵 GAME OVER',
'result.level': 'Level {level}',
'result.killStats': 'Kill Statistics',
'result.tankNormal': 'Normal',
'result.tankFast': 'Fast',
'result.tankArmor': 'Armor',
'result.tankBoss': 'BOSS',
'result.totalLabel': 'Total',
'result.rowKills': 'Kills',
'result.rowScore': 'Score',
'result.totalScore': 'Total: {score}',
'result.time': 'Time: {minutes}m{seconds}s',
'result.baseAlive': 'Base: ✅ Intact',
'result.baseDestroyed': 'Base: ❌ Destroyed',
'result.newRecord': '🎊 New Record!',
'result.doubled': '2x!',
'result.share': '📤 Share Challenge',
'result.adDouble': '🎬 Watch Ad for 2x Score',
'result.nextLevel': 'Next Level →',
'result.retry': 'Retry',
'result.backMenu': 'Back to Menu',
// ============================================================
// Ranking Scene
// ============================================================
'ranking.title': '🏆 Ranking',
'ranking.personalRecord': '— Personal Records —',
'ranking.classicHigh': 'Classic Mode High Score',
'ranking.endlessHigh': 'Endless Mode High Score',
'ranking.highestLevel': 'Highest Level Cleared',
'ranking.levelSuffix': 'Lv',
'ranking.scoreSuffix': 'pts',
'ranking.friendHint': 'Friend ranking requires WeChat Open Data Domain',
// ============================================================
// Settings Scene
// ============================================================
'settings.title': 'Settings',
'settings.sound': 'Sound',
'settings.music': 'Music',
'settings.vibration': 'Vibration',
// ============================================================
// Shop Scene (Simplified)
// ============================================================
'shop.title': 'Shop',
'shop.goldBalance': 'Gold Balance',
'shop.adFree': 'Remove Ads',
'shop.adFreeDesc': 'Permanently remove interstitial ads',
'shop.adFreeOwned': 'Owned',
'shop.goldPack': 'Gold Pack',
'shop.goldPackDesc': '1000 Gold',
'shop.newcomerPack': 'Newcomer Pack',
'shop.newcomerPackDesc': '500 Gold',
'shop.newcomerExpired': 'Expired',
'shop.buy': 'Buy',
'shop.purchased': 'Purchased',
// ============================================================
// Ad System
// ============================================================
'ad.reviveTitle': 'Revive Chance',
'ad.reviveDesc': 'Choose how to revive and continue',
'ad.watchAd': '📺 Watch Ad (Free)',
'ad.goldRevive': '🪙 Gold Revive (200)',
'ad.giveUp': 'Give Up',
'ad.doubleReward': '🎬 Watch Ad for 2x Reward',
'ad.unavailable': 'Ad temporarily unavailable',
'ad.dailyLimitReached': 'Daily ad recovery limit reached',
// ============================================================
// Currency (Simplified - Gold only)
// ============================================================
'currency.gold': 'Gold',
'currency.insufficient': 'Insufficient Gold',
'currency.full': 'Gold is full',
// ============================================================
// IAP Products (Simplified)
// ============================================================
'iap.adFree': 'Remove Ads (¥18 Permanent)',
'iap.goldPack': 'Gold Pack (¥6)',
'iap.newcomerPack': 'Newcomer Pack (¥1)',
// ============================================================
// Buff System
// ============================================================
'buff.title': 'Pre-Game Buffs',
'buff.shield': '🛡️ Shield',
'buff.shieldDesc': 'Start with a shield layer',
'buff.doubleFire': '🔥 Double Fire',
'buff.doubleFireDesc': '2x bullet power for 10s',
'buff.skip': 'Skip →',
'buff.start': 'Start Game',
'buff.purchased': 'Purchased',
'buff.goldInsufficient': 'Insufficient Gold',
// ============================================================
// Daily Gold
// ============================================================
'dailyGold.btn': '🪙 Get Gold',
'dailyGold.remaining': '{remaining}/3',
'dailyGold.exhausted': 'Come back tomorrow',
'dailyGold.reward': '+100 Gold!',
};
+272
View File
@@ -0,0 +1,272 @@
/**
* zh.js
* Chinese language pack for Tank Adventure.
*/
module.exports = {
// ============================================================
// Common
// ============================================================
'common.back': '← 返回',
'common.joinBtn': '加入',
'common.cannotConnect': '无法连接服务器',
'common.connectFailed': '连接失败',
'common.disconnected': '与服务器断开连接',
'common.paused': '暂停',
'common.tapContinue': '点击继续',
'common.kicked': '你已被踢出队伍',
// ============================================================
// Menu Scene
// ============================================================
'menu.title': '坦克探险',
'menu.subtitle': '经典坦克对战',
'menu.classic': '经典模式',
'menu.endless': '无尽模式',
'menu.pvp': '双人对战',
'menu.team3v3': '3v3 对战',
'menu.shop': '商店',
'menu.ranking': '排行榜',
'menu.settings': '设置',
// ============================================================
// Room Scene (PVP)
// ============================================================
'room.title': '双人对战',
'room.idleHint': '创建房间或输入房间号加入',
'room.create': '创建房间',
'room.join': '加入房间',
'room.connecting': '连接中{dots}',
'room.roomCode': '房间号:',
'room.waiting': '等待对手加入{dots}',
'room.shareHint': '将房间号分享给好友',
'room.inputCode': '输入房间号:',
'room.opponentFound': '对手已找到!',
'room.starting': '即将开始...',
'room.tapBack': '点击任意位置返回',
// ============================================================
// Team Room Scene (3v3)
// ============================================================
'teamRoom.title': '3v3 团队对战',
'teamRoom.chooseMode': '选择游戏方式',
'teamRoom.createTeam': '🎮 组队开黑',
'teamRoom.soloMatch': '⚡ 快速匹配',
'teamRoom.teamId': '队伍:{id}',
'teamRoom.leader': '队长',
'teamRoom.ready': '✓ 已准备',
'teamRoom.notReady': '未准备',
'teamRoom.emptySlot': '空位',
'teamRoom.invite': '📨 邀请好友',
'teamRoom.startMatch': '🔍 开始匹配',
'teamRoom.disband': '解散队伍',
'teamRoom.readyBtn': '✓ 准备',
'teamRoom.cancelReady': '取消准备',
'teamRoom.leaveTeam': '退出队伍',
'teamRoom.matching': '匹配中{dots}',
'teamRoom.waitTime': '已等待 {seconds} 秒',
'teamRoom.cancelMatch': '取消匹配',
'teamRoom.matchFound': '对手已找到!',
'teamRoom.enterBattle': '即将进入战斗...',
'teamRoom.tapBack': '点击任意位置返回',
'teamRoom.shareTitle': '坦克3v3,速来开黑!',
'teamRoom.joining': '正在加入房间',
// ============================================================
// PVP Game Scene
// ============================================================
'pvp.playerLabel': 'P{slot} (我方)',
'pvp.hp': '生命 x{count}',
'pvp.kills': '击杀:{count}',
'pvp.killDeath': '杀:{kills} 亡:{deaths}',
'pvp.respawn': '{seconds}秒后重生',
'pvp.youWin': '你赢了!',
'pvp.draw': '平局',
'pvp.youLose': '你输了',
'pvp.baseHpSummary': 'P1{hp1} 生命 | P2{hp2} 生命',
// ============================================================
// Team Game Scene (3v3)
// ============================================================
'team.teamA': 'A队',
'team.teamB': 'B队',
'team.myTeam': '我方:{team}队',
'team.killDeath': '杀:{kills} 亡:{deaths}',
'team.respawn': '{seconds}秒后重生',
'team.victory': '胜利!',
'team.defeat': '失败',
'team.baseHpSummary': 'A队:{hpA} 生命 | B队:{hpB} 生命',
'team.disconnectTitle': '⚠ 连接断开',
'team.reconnecting': '重连中{dots} ({attempts}/{max})',
'team.reconnectHint': '请稍候,您的坦克将由AI代管',
// ============================================================
// PVP Result Scene
// ============================================================
'pvpResult.title': '对战结果',
'pvpResult.victory': '🏆 胜利!',
'pvpResult.draw': '⚔️ 平局',
'pvpResult.defeat': '😵 失败',
'pvpResult.kills': '击杀',
'pvpResult.deaths': '死亡',
'pvpResult.lives': '生命',
'pvpResult.baseDmg': '阵地伤害',
'pvpResult.p1BaseHp': 'P1{hp} 生命',
'pvpResult.p2BaseHp': 'P2{hp} 生命',
'pvpResult.baseDestroyed': '基地被摧毁',
'pvpResult.disconnectedReason': '断线',
'pvpResult.duration': '对战时长:{time}',
'pvpResult.timeRemaining': '剩余时间:{time}',
'pvpResult.rematch': '再来一局',
'pvpResult.backMenu': '返回菜单',
// ============================================================
// Team Result Scene (3v3)
// ============================================================
'teamResult.title': '3v3 对战结果',
'teamResult.victory': '🏆 胜利!',
'teamResult.defeat': '😵 失败',
'teamResult.teamAHp': 'A队:{hp} 生命',
'teamResult.teamBHp': 'B队:{hp} 生命',
'teamResult.baseDestroyed': '基地被摧毁',
'teamResult.disconnectedReason': '断线',
'teamResult.teamAHeader': 'A队',
'teamResult.teamBHeader': 'B队',
'teamResult.myTeamSuffix': ' (我方)',
'teamResult.player': '玩家',
'teamResult.k': '杀',
'teamResult.d': '亡',
'teamResult.a': '助',
'teamResult.dmg': '伤害',
'teamResult.bot': '🤖 机器人',
'teamResult.duration': '对战时长:{time}',
'teamResult.mvp': '⭐ MVP{name}{kills} 击杀)',
'teamResult.rankUp': '📈 积分 +{points}',
'teamResult.mvpBonus': 'MVP加成 +5',
'teamResult.rankDown': '📉 积分 -{points}',
'teamResult.rematch': '再来一局',
'teamResult.rematchWaiting': '等待中({ready}/{total})',
'teamResult.backMenu': '返回菜单',
// ============================================================
// Game Scene (Classic/Endless)
// ============================================================
'game.level': '第 {level} 关',
'game.hp': '生命 x{count}',
'game.fireLevel': 'LV{level}',
'game.enemies': '敌人: {count}',
'game.score': '{score}分',
'game.gameOver': '游戏结束',
'game.stageClear': '关卡通过!',
// ============================================================
// Result Scene
// ============================================================
'result.victory': '🎉 关卡通过!',
'result.defeat': '😵 游戏结束',
'result.level': '第 {level} 关',
'result.killStats': '击杀统计',
'result.tankNormal': '普通',
'result.tankFast': '快速',
'result.tankArmor': '重甲',
'result.tankBoss': 'BOSS',
'result.totalLabel': '汇总',
'result.rowKills': '击杀',
'result.rowScore': '得分',
'result.totalScore': '总分: {score}',
'result.time': '用时: {minutes}分{seconds}秒',
'result.baseAlive': '基地: ✅ 完好',
'result.baseDestroyed': '基地: ❌ 被毁',
'result.newRecord': '🎊 新纪录!',
'result.doubled': '双倍!',
'result.share': '📤 分享挑战书',
'result.adDouble': '🎬 看广告双倍得分',
'result.nextLevel': '下一关 →',
'result.retry': '重新开始',
'result.backMenu': '返回主菜单',
// ============================================================
// Ranking Scene
// ============================================================
'ranking.title': '🏆 排行榜',
'ranking.personalRecord': '— 个人记录 —',
'ranking.classicHigh': '经典模式最高分',
'ranking.endlessHigh': '无尽模式最高分',
'ranking.highestLevel': '最高通关关卡',
'ranking.levelSuffix': '关',
'ranking.scoreSuffix': '分',
'ranking.friendHint': '好友排行榜需要微信开放数据域支持',
// ============================================================
// Settings Scene
// ============================================================
'settings.title': '设置',
'settings.sound': '音效',
'settings.music': '音乐',
'settings.vibration': '振动',
// ============================================================
// Shop Scene (Simplified)
// ============================================================
'shop.title': '商店',
'shop.goldBalance': '金币余额',
'shop.adFree': '去广告特权',
'shop.adFreeDesc': '永久移除插屏广告',
'shop.adFreeOwned': '已拥有',
'shop.goldPack': '金币包',
'shop.goldPackDesc': '1000 金币',
'shop.newcomerPack': '新手礼包',
'shop.newcomerPackDesc': '500 金币',
'shop.newcomerExpired': '已过期',
'shop.buy': '购买',
'shop.purchased': '已购买',
// ============================================================
// Ad System
// ============================================================
'ad.reviveTitle': '复活机会',
'ad.reviveDesc': '选择复活方式继续游戏',
'ad.watchAd': '📺 观看广告(免费)',
'ad.goldRevive': '🪙 金币复活(200',
'ad.giveUp': '放弃',
'ad.doubleReward': '🎬 看广告双倍奖励',
'ad.unavailable': '广告暂时不可用',
'ad.dailyLimitReached': '今日广告恢复次数已用完',
// ============================================================
// Currency (Simplified - Gold only)
// ============================================================
'currency.gold': '金币',
'currency.insufficient': '金币不足',
'currency.full': '金币已满',
// ============================================================
// IAP Products (Simplified)
// ============================================================
'iap.adFree': '去广告特权(¥18 永久)',
'iap.goldPack': '金币包(¥6',
'iap.newcomerPack': '新手礼包(¥1',
// ============================================================
// Buff System
// ============================================================
'buff.title': '局前增益',
'buff.shield': '🛡️ 护盾',
'buff.shieldDesc': '开局自带一层护盾',
'buff.doubleFire': '🔥 双倍火力',
'buff.doubleFireDesc': '开局10秒子弹威力翻倍',
'buff.skip': '跳过 →',
'buff.start': '开始游戏',
'buff.purchased': '已购买',
'buff.goldInsufficient': '金币不足',
// ============================================================
// Daily Gold
// ============================================================
'dailyGold.btn': '🪙 领金币',
'dailyGold.remaining': '{remaining}/3',
'dailyGold.exhausted': '明日再来',
'dailyGold.reward': '+100 金币!',
};