Commit 927372c0 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: i18n keys, chess hardcoded colors/strings, backgammon i18n

Phase 7.1: add 30+ missing i18n keys for queue timeout, game states,
achievements, and chat. Replace hardcoded Arabic in chess game scene
with t() calls.

Phase 7.3: replace hardcoded hex colors in chess HUD and draw dialog
with CSS variables (--text-primary, --bg-surface, --success, --error).
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent f5079d8b
......@@ -95,7 +95,38 @@ const strings = {
'block.muted': 'تم الكتم',
'block.confirm_block': 'حظر هذا اللاعب؟ لن يستطيع مراسلتك أو تحديك.',
'block.empty': 'لا يوجد لاعبين محظورين',
'block.cannot_message': 'لا يمكن مراسلة هذا اللاعب'
'block.cannot_message': 'لا يمكن مراسلة هذا اللاعب',
'play.no_match_found': 'لم يتم العثور على خصم',
'play.retry_or_cancel': 'لم يتم العثور على خصم. هل تريد المتابعة؟',
'play.retry': 'إعادة',
'play.challenge_friend': 'تحدّي صديق',
'play.daily_reward': 'المكافأة اليومية',
'play.my_tournaments': 'بطولاتي',
'play.leaderboard': 'الترتيب',
'play.my_games': 'مبارياتي',
'play.puzzles': 'أحجيات',
'game.bot': 'بوت',
'game.loading_opponent': 'جاري التحميل...',
'game.opponent_disconnected': 'انقطع الاتصال',
'game.weak_connection': 'اتصال ضعيف',
'game.draw_declined': 'الخصم رفض التعادل',
'game.leave_confirm': 'المغادرة تحتسب كخسارة. هل أنت متأكد؟',
'game.leave_title': 'مغادرة المباراة؟',
'game.leave': 'مغادرة',
'game.stay': 'البقاء',
'chat.now': 'الآن',
'chat.minutes_ago': 'منذ {n} دقيقة',
'chat.hours_ago': 'منذ {n} ساعة',
'chat.days_ago': 'منذ {n} يوم',
'chat.mark_all_read': 'تعليم الكل كمقروء',
'achievements.title': 'الإنجازات',
'achievements.complete': 'مكتمل',
'achievements.none': 'لا توجد إنجازات في هذه الفئة',
'achievements.all': 'الكل',
'achievements.gameplay': 'اللعب',
'achievements.social': 'اجتماعي',
'achievements.progression': 'التقدم',
'achievements.collection': 'جمع'
},
en: {
'app.name': 'EL3AB',
......@@ -191,7 +222,38 @@ const strings = {
'block.muted': 'Muted',
'block.confirm_block': 'Block this player? They won\'t be able to message or challenge you.',
'block.empty': 'No blocked players',
'block.cannot_message': 'Cannot message this player'
'block.cannot_message': 'Cannot message this player',
'play.no_match_found': 'No opponent found',
'play.retry_or_cancel': 'No opponent found. Keep searching?',
'play.retry': 'Retry',
'play.challenge_friend': 'Challenge Friend',
'play.daily_reward': 'Daily Reward',
'play.my_tournaments': 'My Tournaments',
'play.leaderboard': 'Leaderboard',
'play.my_games': 'My Games',
'play.puzzles': 'Puzzles',
'game.bot': 'Bot',
'game.loading_opponent': 'Loading...',
'game.opponent_disconnected': 'Disconnected',
'game.weak_connection': 'Weak connection',
'game.draw_declined': 'Opponent declined draw',
'game.leave_confirm': 'Leaving counts as a loss. Are you sure?',
'game.leave_title': 'Leave match?',
'game.leave': 'Leave',
'game.stay': 'Stay',
'chat.now': 'Now',
'chat.minutes_ago': '{n}m ago',
'chat.hours_ago': '{n}h ago',
'chat.days_ago': '{n}d ago',
'chat.mark_all_read': 'Mark all read',
'achievements.title': 'Achievements',
'achievements.complete': 'Complete',
'achievements.none': 'No achievements in this category',
'achievements.all': 'All',
'achievements.gameplay': 'Gameplay',
'achievements.social': 'Social',
'achievements.progression': 'Progression',
'achievements.collection': 'Collection'
}
};
......
......@@ -5,6 +5,7 @@ import * as store from '../../../core/store.js';
import * as net from '../../../core/net.js';
import * as matchSession from '../../../core/match-session.js';
import * as modal from '../../../core/modal.js';
import { t } from '../../../core/i18n.js';
import { emoji } from '../../../core/theme.js';
import { createGame, rollDice, getValidMoves, applyMove, nextTurn, hasWon, getPipCount, WHITE, BLACK, serializeState } from '../logic/rules.js';
import { getBotMove } from '../logic/bot.js';
......@@ -685,8 +686,8 @@ function showBubble(text) {
async function onQuit() {
audio.play('click');
if (params.mode === 'live') {
const confirmed = await modal.confirm('Leaving will count as a loss. Are you sure?', {
title: 'Leave match?', confirmText: 'Leave', cancelText: 'Stay', danger: true
const confirmed = await modal.confirm(t('game.leave_confirm'), {
title: t('game.leave_title'), confirmText: t('game.leave'), cancelText: t('game.stay'), danger: true
});
if (!confirmed) return;
net.post('backgammon-match.php', { action: 'leave', match_id: params.matchId }).catch(() => {});
......
......@@ -103,15 +103,15 @@ export function mountGame(el, params) {
el.innerHTML = `
<div class="chess-layout" style="display:flex;flex-direction:column;height:100%;background:#0f0f1e;justify-content:center;">
<!-- Opponent Bar -->
<div class="chess-bar" style="display:flex;align-items:center;justify-content:space-between;padding:8px 14px;background:#0f0f1e;">
<div class="chess-bar" style="display:flex;align-items:center;justify-content:space-between;padding:8px 14px;background:var(--bg-surface);">
<div style="display:flex;align-items:center;gap:10px;">
<div id="opponent-avatar" style="width:36px;height:36px;border-radius:50%;background:#2a2a4a;display:flex;align-items:center;justify-content:center;overflow:hidden;border:2px solid ${mode === 'bot' ? '#64748b' : '#3B82F6'};">
<div id="opponent-avatar" style="width:36px;height:36px;border-radius:50%;background:var(--bg-elevated);display:flex;align-items:center;justify-content:center;overflow:hidden;border:2px solid ${mode === 'bot' ? 'var(--text-muted)' : 'var(--blue)'};">
${mode === 'bot' ? `<img src="https://stockfishapi.caprover.al-arcade.com/portraits/${botId || 'amina'}.png" style="width:100%;height:100%;object-fit:cover;" onerror="this.style.display='none';this.parentNode.textContent='🤖'">` : '<span style="font-size:16px;">👤</span>'}
</div>
<div>
<div style="font-size:13px;font-weight:600;color:#f8fafc;" id="opponent-name">${mode === 'bot' ? (botId || 'Bot') : 'جاري التحميل...'}</div>
<div style="font-size:13px;font-weight:600;color:var(--text-primary);" id="opponent-name">${mode === 'bot' ? (botId || t('game.bot')) : t('game.loading_opponent')}</div>
<div style="display:flex;gap:6px;align-items:center;">
<div id="opponent-level" style="font-size:10px;color:#64748b;">${mode === 'bot' ? 'بوت' : ''}</div>
<div id="opponent-level" style="font-size:10px;color:var(--text-muted);">${mode === 'bot' ? t('game.bot') : ''}</div>
<div id="opponent-captured" style="font-size:11px;color:#94a3b8;letter-spacing:1px;"></div>
</div>
</div>
......@@ -134,7 +134,7 @@ export function mountGame(el, params) {
${store.get('player.avatar_url') ? `<img src="${store.get('player.avatar_url')}" style="width:100%;height:100%;object-fit:cover;">` : `<span style="font-size:16px;">👤</span>`}
</div>
<div>
<div style="font-size:13px;font-weight:600;color:#f8fafc;">${store.get('player.display_name') || store.get('player.username') || 'أنت'}</div>
<div style="font-size:13px;font-weight:600;color:var(--text-primary);">${store.get('player.display_name') || store.get('player.username') || 'You'}</div>
<div style="display:flex;gap:6px;align-items:center;">
<span style="font-size:10px;color:#64748b;">Lv.${store.get('player.level') || 1}</span>
<div id="player-captured" style="font-size:11px;color:#94a3b8;letter-spacing:1px;"></div>
......@@ -215,11 +215,11 @@ export function mountGame(el, params) {
// Controls
el.querySelector('#btn-resign').addEventListener('click', async () => {
if (gameState.gameOver) return;
const confirmed = await modal.confirm('هل أنت متأكد من الاستسلام؟', {
title: 'استسلام',
const confirmed = await modal.confirm(t('game.leave_confirm'), {
title: t('game.resign'),
icon: '🏳️',
confirmText: 'نعم، استسلم',
cancelText: 'تراجع',
confirmText: t('game.resign'),
cancelText: t('common.cancel'),
danger: true
});
if (!confirmed) return;
......@@ -237,7 +237,7 @@ export function mountGame(el, params) {
endGame('draw', 'agreement');
} else {
const msg = document.createElement('div');
msg.textContent = 'الخصم رفض التعادل';
msg.textContent = t('game.draw_declined');
msg.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.85);color:#F87171;padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;z-index:30;pointer-events:none;';
el.querySelector('#board-container').appendChild(msg);
setTimeout(() => msg.remove(), 2000);
......@@ -252,7 +252,7 @@ export function mountGame(el, params) {
game_state: JSON.stringify({ draw_offer: userId, draw_offer_t: drawT })
});
const msg = document.createElement('div');
msg.textContent = 'تم إرسال عرض التعادل...';
msg.textContent = t('game.draw') + '...';
msg.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.85);color:#E4AC38;padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;z-index:30;pointer-events:none;';
el.querySelector('#board-container').appendChild(msg);
setTimeout(() => msg.remove(), 2500);
......@@ -558,7 +558,7 @@ function updateMoveList(el, m) {
if (matEl) {
const adv = getMaterialAdvantage(engine.fen(), gameState.playerColor);
matEl.textContent = formatAdvantage(adv);
matEl.style.color = adv > 0 ? '#34D399' : adv < 0 ? '#F87171' : '#64748b';
matEl.style.color = adv > 0 ? 'var(--success)' : adv < 0 ? 'var(--error)' : 'var(--text-muted)';
}
}
......@@ -829,12 +829,12 @@ function checkDrawOffer(el, rawGameState, myId) {
const dialog = document.createElement('div');
dialog.id = 'draw-offer-dialog';
dialog.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#1e1e3a;border:1px solid rgba(228,172,56,0.4);border-radius:12px;padding:16px 20px;z-index:50;box-shadow:0 8px 32px rgba(0,0,0,0.7);text-align:center;min-width:220px;';
dialog.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:var(--bg-card);border:1px solid rgba(228,172,56,0.4);border-radius:12px;padding:16px 20px;z-index:50;box-shadow:0 8px 32px rgba(0,0,0,0.7);text-align:center;min-width:220px;';
dialog.innerHTML = `
<div style="font-size:14px;font-weight:600;color:#f8fafc;margin-bottom:12px;">الخصم يعرض التعادل</div>
<div style="font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:12px;">${t('game.draw')}?</div>
<div style="display:flex;gap:10px;justify-content:center;">
<button id="draw-accept" style="flex:1;padding:10px;background:#34D399;color:#000;font-weight:700;border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:inherit;">قبول ½</button>
<button id="draw-reject" style="flex:1;padding:10px;background:#EF4444;color:#fff;font-weight:700;border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:inherit;">رفض</button>
<button id="draw-accept" style="flex:1;padding:10px;background:var(--success);color:#000;font-weight:700;border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:inherit;">${t('common.confirm')} ½</button>
<button id="draw-reject" style="flex:1;padding:10px;background:var(--error);color:#fff;font-weight:700;border:none;border-radius:8px;font-size:13px;cursor:pointer;font-family:inherit;">${t('common.cancel')}</button>
</div>
`;
......@@ -858,7 +858,7 @@ function checkDrawOffer(el, rawGameState, myId) {
game_state: JSON.stringify({ draw_offer: null, draw_offer_t: null, draw_declined: myId, draw_declined_t: Date.now() })
});
const msg = document.createElement('div');
msg.textContent = 'تم رفض التعادل';
msg.textContent = t('game.draw_declined');
msg.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.85);color:#F87171;padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;z-index:30;pointer-events:none;';
el.querySelector('#board-container').appendChild(msg);
setTimeout(() => msg.remove(), 2000);
......@@ -883,7 +883,7 @@ function checkDrawResponse(el, rawGameState, myId) {
if (gs.draw_declined && gs.draw_declined !== myId && (gs.draw_declined_t || 0) > lastDrawDeclineHandled) {
lastDrawDeclineHandled = gs.draw_declined_t;
const msg = document.createElement('div');
msg.textContent = 'الخصم رفض التعادل';
msg.textContent = t('game.draw_declined');
msg.style.cssText = 'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(0,0,0,0.85);color:#F87171;padding:8px 16px;border-radius:8px;font-size:13px;font-weight:600;z-index:30;pointer-events:none;';
el.querySelector('#board-container').appendChild(msg);
setTimeout(() => msg.remove(), 2000);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment