Add full language UI translations and modal wrapping

This commit is contained in:
2026-02-08 19:00:25 +01:00
parent 27515639aa
commit 62ad664ec0
3 changed files with 695 additions and 21 deletions

View File

@@ -22,8 +22,37 @@ const installDismissed = ref(false);
const isCoarsePointer = ref(false); const isCoarsePointer = ref(false);
const isStandalone = ref(false); const isStandalone = ref(false);
const themePreference = ref('system'); const themePreference = ref('system');
const isLangOpen = ref(false);
const langMenuRef = ref(null);
let displayModeMedia = null; let displayModeMedia = null;
let prefersColorSchemeMedia = null; let prefersColorSchemeMedia = null;
const languageFlags = {
en: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="16" fill="#b22234"/><rect y="2" width="24" height="2" fill="#fff"/><rect y="6" width="24" height="2" fill="#fff"/><rect y="10" width="24" height="2" fill="#fff"/><rect y="14" width="24" height="2" fill="#fff"/><rect width="10" height="8" fill="#3c3b6e"/></svg>',
zh: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="16" fill="#de2910"/><circle cx="6" cy="5" r="2" fill="#ffde00"/></svg>',
hi: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="16" fill="#ffffff"/><rect width="24" height="5.33" fill="#ff9933"/><rect y="10.67" width="24" height="5.33" fill="#128807"/><circle cx="12" cy="8" r="2" fill="#000080"/></svg>',
es: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="16" fill="#aa151b"/><rect y="4" width="24" height="8" fill="#f1bf00"/></svg>',
fr: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="8" height="16" fill="#0055a4"/><rect x="8" width="8" height="16" fill="#ffffff"/><rect x="16" width="8" height="16" fill="#ef4135"/></svg>',
ar: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="16" fill="#006c35"/><rect y="11" width="24" height="3" fill="#ffffff"/></svg>',
bn: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="16" fill="#006a4e"/><circle cx="11" cy="8" r="4" fill="#f42a41"/></svg>',
ru: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="16" fill="#ffffff"/><rect y="5.33" width="24" height="5.33" fill="#0039a6"/><rect y="10.67" width="24" height="5.33" fill="#d52b1e"/></svg>',
pt: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="9" height="16" fill="#006600"/><rect x="9" width="15" height="16" fill="#ff0000"/><circle cx="9.5" cy="8" r="2.5" fill="#ffcc00"/></svg>',
ur: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="16" fill="#01411c"/><rect width="6" height="16" fill="#ffffff"/><circle cx="15" cy="8" r="3" fill="#ffffff"/></svg>',
pl: '<svg viewBox="0 0 24 16" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="16" fill="#ffffff"/><rect y="8" width="24" height="8" fill="#dc143c"/></svg>'
};
const languages = computed(() => [
{ code: 'en', label: t('language.en') },
{ code: 'zh', label: t('language.zh') },
{ code: 'hi', label: t('language.hi') },
{ code: 'es', label: t('language.es') },
{ code: 'fr', label: t('language.fr') },
{ code: 'ar', label: t('language.ar') },
{ code: 'bn', label: t('language.bn') },
{ code: 'ru', label: t('language.ru') },
{ code: 'pt', label: t('language.pt') },
{ code: 'ur', label: t('language.ur') },
{ code: 'pl', label: t('language.pl') }
]);
const installLabel = computed(() => { const installLabel = computed(() => {
return isCoarsePointer.value ? t('pwa.installMobile') : t('pwa.installDesktop'); return isCoarsePointer.value ? t('pwa.installMobile') : t('pwa.installDesktop');
@@ -85,6 +114,18 @@ const handleSystemThemeChange = () => {
} }
}; };
const selectLanguage = (value) => {
setLocale(value);
isLangOpen.value = false;
};
const handleOutsideClick = (event) => {
if (!langMenuRef.value) return;
if (!langMenuRef.value.contains(event.target)) {
isLangOpen.value = false;
}
};
onMounted(() => { onMounted(() => {
if (!store.loadState()) { if (!store.loadState()) {
store.initGame(); // Inicjalizacja domyślnej gry jeśli brak zapisu store.initGame(); // Inicjalizacja domyślnej gry jeśli brak zapisu
@@ -111,6 +152,7 @@ onMounted(() => {
} else if (displayModeMedia?.addListener) { } else if (displayModeMedia?.addListener) {
displayModeMedia.addListener(updateStandalone); displayModeMedia.addListener(updateStandalone);
} }
document.addEventListener('click', handleOutsideClick);
} }
}); });
@@ -128,6 +170,7 @@ onUnmounted(() => {
} else if (displayModeMedia?.removeListener) { } else if (displayModeMedia?.removeListener) {
displayModeMedia.removeListener(updateStandalone); displayModeMedia.removeListener(updateStandalone);
} }
document.removeEventListener('click', handleOutsideClick);
}); });
</script> </script>
@@ -138,9 +181,29 @@ onUnmounted(() => {
<header class="game-header"> <header class="game-header">
<h1>{{ t('app.title') }}</h1> <h1>{{ t('app.title') }}</h1>
<div class="header-toggles"> <div class="header-toggles">
<div class="lang-toggle"> <div class="lang-dropdown" ref="langMenuRef">
<button class="lang-btn" :class="{ active: locale === 'pl' }" @click="setLocale('pl')">PL</button> <button
<button class="lang-btn" :class="{ active: locale === 'en' }" @click="setLocale('en')">EN</button> class="lang-trigger"
type="button"
:aria-label="t('language.label')"
:aria-expanded="isLangOpen"
@click="isLangOpen = !isLangOpen"
>
<span class="lang-flag" v-html="languageFlags[locale]"></span>
</button>
<div v-if="isLangOpen" class="lang-menu">
<button
v-for="lang in languages"
:key="lang.code"
class="lang-option"
:class="{ active: locale === lang.code }"
type="button"
@click="selectLanguage(lang.code)"
>
<span class="lang-flag" v-html="languageFlags[lang.code]"></span>
<span class="lang-name">{{ lang.label }}</span>
</button>
</div>
</div> </div>
<div class="theme-toggle"> <div class="theme-toggle">
<span class="theme-label">{{ t('theme.label') }}</span> <span class="theme-label">{{ t('theme.label') }}</span>
@@ -242,35 +305,87 @@ h1 {
margin-top: 12px; margin-top: 12px;
} }
.lang-toggle { .lang-dropdown {
position: relative;
display: inline-flex; display: inline-flex;
gap: 8px; align-items: center;
padding: 6px 10px; }
border-radius: 999px;
.lang-trigger {
background: var(--toggle-bg); background: var(--toggle-bg);
border: 1px solid var(--toggle-border); border: 1px solid var(--toggle-border);
box-shadow: var(--toggle-shadow); box-shadow: var(--toggle-shadow);
}
.lang-btn {
background: transparent;
border: 1px solid var(--toggle-btn-border);
color: var(--text-strong);
padding: 4px 10px;
border-radius: 999px; border-radius: 999px;
font-size: 0.8rem; width: 38px;
letter-spacing: 1px; height: 38px;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.lang-btn.active { .lang-trigger:hover {
border-color: var(--toggle-hover-border);
}
.lang-menu {
position: absolute;
top: calc(100% + 8px);
right: 0;
min-width: 170px;
background: var(--toggle-bg);
border: 1px solid var(--toggle-border);
box-shadow: var(--toggle-shadow);
border-radius: 16px;
padding: 8px;
display: flex;
flex-direction: column;
gap: 6px;
z-index: 10;
opacity: 1;
}
.lang-option {
background: transparent;
border: 1px solid var(--toggle-btn-border);
color: var(--text-strong);
padding: 6px 10px;
border-radius: 12px;
font-size: 0.85rem;
letter-spacing: 0.5px;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
transition: all 0.2s ease;
text-align: left;
}
.lang-option:hover {
border-color: var(--toggle-hover-border);
}
.lang-option.active {
border-color: var(--primary-accent); border-color: var(--primary-accent);
box-shadow: var(--toggle-active-shadow); box-shadow: var(--toggle-active-shadow);
} }
.lang-btn:hover { .lang-flag {
border-color: var(--toggle-hover-border); width: 24px;
height: 16px;
display: inline-flex;
}
.lang-flag svg {
width: 100%;
height: 100%;
display: block;
border-radius: 2px;
}
.lang-name {
font-weight: 500;
} }
.theme-toggle { .theme-toggle {

View File

@@ -337,6 +337,7 @@ onUnmounted(() => {
animation: slideUp 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275); animation: slideUp 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative; position: relative;
z-index: 1001; z-index: 1001;
overflow-wrap: anywhere;
} }
h2 { h2 {
@@ -344,12 +345,14 @@ h2 {
color: var(--primary-accent); color: var(--primary-accent);
margin: 0 0 10px 0; margin: 0 0 10px 0;
text-shadow: 0 0 20px var(--primary-accent); text-shadow: 0 0 20px var(--primary-accent);
overflow-wrap: anywhere;
} }
p { p {
color: var(--text-secondary); color: var(--text-secondary);
font-size: 1.2rem; font-size: 1.2rem;
margin-bottom: 30px; margin-bottom: 30px;
overflow-wrap: anywhere;
} }
.stats { .stats {
@@ -380,6 +383,7 @@ p {
letter-spacing: 1px; letter-spacing: 1px;
text-transform: uppercase; text-transform: uppercase;
color: var(--text-muted); color: var(--text-muted);
overflow-wrap: anywhere;
} }
.share-buttons { .share-buttons {
@@ -409,6 +413,13 @@ p {
align-self: center; align-self: center;
padding: 8px 18px; padding: 8px 18px;
font-size: 0.85rem; font-size: 0.85rem;
max-width: 100%;
white-space: normal;
}
.actions .btn-neon {
max-width: 100%;
white-space: normal;
} }
@keyframes fadeIn { @keyframes fadeIn {

View File

@@ -1,10 +1,12 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
const supportedLocales = ['pl', 'en', 'zh', 'hi', 'es', 'fr', 'ar', 'bn', 'ru', 'pt', 'ur'];
const detectLocale = () => { const detectLocale = () => {
if (typeof navigator === 'undefined') return 'en'; if (typeof navigator === 'undefined') return 'en';
const browserLocale = (navigator.languages && navigator.languages[0]) || navigator.language || 'en'; const browserLocale = (navigator.languages && navigator.languages[0]) || navigator.language || 'en';
const short = browserLocale.toLowerCase().split('-')[0]; const short = browserLocale.toLowerCase().split('-')[0];
return short === 'pl' ? 'pl' : 'en'; return supportedLocales.includes(short) ? short : 'en';
}; };
const messages = { const messages = {
@@ -49,6 +51,18 @@ const messages = {
'pwa.installTitle': 'Zainstaluj aplikację i graj offline', 'pwa.installTitle': 'Zainstaluj aplikację i graj offline',
'pwa.installMobile': 'Dodaj do ekranu głównego', 'pwa.installMobile': 'Dodaj do ekranu głównego',
'pwa.installDesktop': 'Zainstaluj na komputerze', 'pwa.installDesktop': 'Zainstaluj na komputerze',
'language.label': 'Wybór języka',
'language.pl': 'Polski',
'language.en': 'Angielski',
'language.zh': 'Mandaryński',
'language.hi': 'Hindi',
'language.es': 'Hiszpański',
'language.fr': 'Francuski',
'language.ar': 'Arabski',
'language.bn': 'Bengalski',
'language.ru': 'Rosyjski',
'language.pt': 'Portugalski',
'language.ur': 'Urdu',
'theme.label': 'Motyw', 'theme.label': 'Motyw',
'theme.system': 'System', 'theme.system': 'System',
'theme.light': 'Jasny', 'theme.light': 'Jasny',
@@ -95,10 +109,544 @@ const messages = {
'pwa.installTitle': 'Install the app and play offline', 'pwa.installTitle': 'Install the app and play offline',
'pwa.installMobile': 'Add to home screen', 'pwa.installMobile': 'Add to home screen',
'pwa.installDesktop': 'Install on desktop', 'pwa.installDesktop': 'Install on desktop',
'language.label': 'Language selection',
'language.pl': 'Polish',
'language.en': 'English',
'language.zh': 'Mandarin',
'language.hi': 'Hindi',
'language.es': 'Spanish',
'language.fr': 'French',
'language.ar': 'Arabic',
'language.bn': 'Bengali',
'language.ru': 'Russian',
'language.pt': 'Portuguese',
'language.ur': 'Urdu',
'theme.label': 'Theme', 'theme.label': 'Theme',
'theme.system': 'System', 'theme.system': 'System',
'theme.light': 'Light', 'theme.light': 'Light',
'theme.dark': 'Dark' 'theme.dark': 'Dark'
},
zh: {
'app.title': 'Nonograms',
'level.easy': '简单 5X5',
'level.medium': '中等 10X10',
'level.hard': '困难 15X15',
'level.custom': '自定义',
'level.guide': '指南 ❓',
'actions.reset': '重置',
'actions.random': '新随机',
'actions.undo': '撤销',
'status.time': '时间',
'status.moves': '步数',
'status.progress': '进度',
'fixed.time': '时间:',
'fixed.progress': '进度:',
'fixed.hide': '隐藏',
'fixed.show': '显示',
'guide.play': '开始',
'guide.pause': '暂停',
'guide.step': '步骤',
'guide.speed': '速度',
'guide.waiting': '等待...',
'guide.solved': '已解!',
'custom.title': '自定义游戏',
'custom.prompt': '输入网格大小 (5 - 80):',
'custom.cancel': '取消',
'custom.start': '开始',
'custom.sizeError': '尺寸必须在 5 到 80 之间!',
'win.title': '恭喜!',
'win.message': '你解开了谜题!',
'win.time': '时间:',
'win.playAgain': '再玩一次',
'win.shareTitle': '分享你的结果',
'win.shareText': '我在 {time} 内解开了 {size}x{size} 的数织!',
'win.shareX': 'X',
'win.shareFacebook': 'Facebook',
'win.shareWhatsapp': 'WhatsApp',
'win.shareDownload': '下载截图',
'pwa.installTitle': '安装应用并离线游玩',
'pwa.installMobile': '添加到主屏幕',
'pwa.installDesktop': '安装到桌面',
'language.label': '语言选择',
'language.pl': '波兰语',
'language.en': '英语',
'language.zh': '中文',
'language.hi': '印地语',
'language.es': '西班牙语',
'language.fr': '法语',
'language.ar': '阿拉伯语',
'language.bn': '孟加拉语',
'language.ru': '俄语',
'language.pt': '葡萄牙语',
'language.ur': '乌尔都语',
'theme.label': '主题',
'theme.system': '系统',
'theme.light': '浅色',
'theme.dark': '深色'
},
hi: {
'app.title': 'Nonograms',
'level.easy': 'आसान 5X5',
'level.medium': 'मध्यम 10X10',
'level.hard': 'कठिन 15X15',
'level.custom': 'कस्टम',
'level.guide': 'गाइड ❓',
'actions.reset': 'रीसेट',
'actions.random': 'नई रैंडम',
'actions.undo': 'वापस',
'status.time': 'समय',
'status.moves': 'चालें',
'status.progress': 'प्रगति',
'fixed.time': 'समय:',
'fixed.progress': 'प्रगति:',
'fixed.hide': 'छिपाएं',
'fixed.show': 'दिखाएं',
'guide.play': 'शुरू',
'guide.pause': 'रोकें',
'guide.step': 'कदम',
'guide.speed': 'गति',
'guide.waiting': 'प्रतीक्षा...',
'guide.solved': 'हल हो गया!',
'custom.title': 'कस्टम गेम',
'custom.prompt': 'ग्रिड आकार दर्ज करें (5 - 80):',
'custom.cancel': 'रद्द करें',
'custom.start': 'शुरू',
'custom.sizeError': 'आकार 5 और 80 के बीच होना चाहिए!',
'win.title': 'बधाई!',
'win.message': 'आपने पहेली हल कर ली!',
'win.time': 'समय:',
'win.playAgain': 'फिर से खेलें',
'win.shareTitle': 'अपना परिणाम साझा करें',
'win.shareText': 'मैंने {time} में {size}x{size} नॉनोग्राम हल किया!',
'win.shareX': 'X',
'win.shareFacebook': 'Facebook',
'win.shareWhatsapp': 'WhatsApp',
'win.shareDownload': 'स्क्रीनशॉट डाउनलोड करें',
'pwa.installTitle': 'ऐप इंस्टॉल करें और ऑफलाइन खेलें',
'pwa.installMobile': 'होम स्क्रीन पर जोड़ें',
'pwa.installDesktop': 'डेस्कटॉप पर इंस्टॉल करें',
'language.label': 'भाषा चयन',
'language.pl': 'पोलिश',
'language.en': 'अंग्रेज़ी',
'language.zh': 'चीनी',
'language.hi': 'हिंदी',
'language.es': 'स्पेनिश',
'language.fr': 'फ़्रेंच',
'language.ar': 'अरबी',
'language.bn': 'बंगाली',
'language.ru': 'रूसी',
'language.pt': 'पुर्तगाली',
'language.ur': 'उर्दू',
'theme.label': 'थीम',
'theme.system': 'सिस्टम',
'theme.light': 'हल्का',
'theme.dark': 'गहरा'
},
es: {
'app.title': 'Nonograms',
'level.easy': 'FÁCIL 5X5',
'level.medium': 'MEDIO 10X10',
'level.hard': 'DIFÍCIL 15X15',
'level.custom': 'PERSONALIZADO',
'level.guide': 'GUÍA ❓',
'actions.reset': 'REINICIAR',
'actions.random': 'NUEVO ALEATORIO',
'actions.undo': 'DESHACER',
'status.time': 'TIEMPO',
'status.moves': 'MOVIMIENTOS',
'status.progress': 'PROGRESO',
'fixed.time': 'Tiempo:',
'fixed.progress': 'Progreso:',
'fixed.hide': 'Ocultar',
'fixed.show': 'Mostrar',
'guide.play': 'INICIAR',
'guide.pause': 'PAUSA',
'guide.step': 'PASO',
'guide.speed': 'VELOCIDAD',
'guide.waiting': 'Esperando...',
'guide.solved': '¡Resuelto!',
'custom.title': 'JUEGO PERSONALIZADO',
'custom.prompt': 'Introduce el tamaño de la cuadrícula (5 - 80):',
'custom.cancel': 'Cancelar',
'custom.start': 'Empezar',
'custom.sizeError': '¡El tamaño debe estar entre 5 y 80!',
'win.title': '¡FELICIDADES!',
'win.message': '¡Has resuelto el rompecabezas!',
'win.time': 'Tiempo:',
'win.playAgain': 'Jugar de nuevo',
'win.shareTitle': 'Comparte tu resultado',
'win.shareText': '¡Resolví un nonograma de {size}x{size} en {time}!',
'win.shareX': 'X',
'win.shareFacebook': 'Facebook',
'win.shareWhatsapp': 'WhatsApp',
'win.shareDownload': 'Descargar captura',
'pwa.installTitle': 'Instala la app y juega sin conexión',
'pwa.installMobile': 'Agregar a la pantalla de inicio',
'pwa.installDesktop': 'Instalar en el escritorio',
'language.label': 'Selección de idioma',
'language.pl': 'Polaco',
'language.en': 'Inglés',
'language.zh': 'Chino',
'language.hi': 'Hindi',
'language.es': 'Español',
'language.fr': 'Francés',
'language.ar': 'Árabe',
'language.bn': 'Bengalí',
'language.ru': 'Ruso',
'language.pt': 'Portugués',
'language.ur': 'Urdu',
'theme.label': 'Tema',
'theme.system': 'Sistema',
'theme.light': 'Claro',
'theme.dark': 'Oscuro'
},
fr: {
'app.title': 'Nonograms',
'level.easy': 'FACILE 5X5',
'level.medium': 'MOYEN 10X10',
'level.hard': 'DIFFICILE 15X15',
'level.custom': 'PERSONNALISÉ',
'level.guide': 'GUIDE ❓',
'actions.reset': 'RÉINITIALISER',
'actions.random': 'NOUVEAU ALÉATOIRE',
'actions.undo': 'ANNULER',
'status.time': 'TEMPS',
'status.moves': 'COUPS',
'status.progress': 'PROGRÈS',
'fixed.time': 'Temps:',
'fixed.progress': 'Progrès:',
'fixed.hide': 'Masquer',
'fixed.show': 'Afficher',
'guide.play': 'LANCER',
'guide.pause': 'PAUSE',
'guide.step': 'ÉTAPE',
'guide.speed': 'VITESSE',
'guide.waiting': 'En attente...',
'guide.solved': 'Résolu !',
'custom.title': 'JEU PERSONNALISÉ',
'custom.prompt': 'Entrez la taille de la grille (5 - 80) :',
'custom.cancel': 'Annuler',
'custom.start': 'Démarrer',
'custom.sizeError': 'La taille doit être entre 5 et 80 !',
'win.title': 'FÉLICITATIONS !',
'win.message': 'Vous avez résolu le puzzle !',
'win.time': 'Temps:',
'win.playAgain': 'Rejouer',
'win.shareTitle': 'Partagez votre résultat',
'win.shareText': 'Jai résolu un nonogramme {size}x{size} en {time} !',
'win.shareX': 'X',
'win.shareFacebook': 'Facebook',
'win.shareWhatsapp': 'WhatsApp',
'win.shareDownload': 'Télécharger la capture',
'pwa.installTitle': 'Installez lapp et jouez hors ligne',
'pwa.installMobile': 'Ajouter à lécran daccueil',
'pwa.installDesktop': 'Installer sur le bureau',
'language.label': 'Choix de la langue',
'language.pl': 'Polonais',
'language.en': 'Anglais',
'language.zh': 'Chinois',
'language.hi': 'Hindi',
'language.es': 'Espagnol',
'language.fr': 'Français',
'language.ar': 'Arabe',
'language.bn': 'Bengali',
'language.ru': 'Russe',
'language.pt': 'Portugais',
'language.ur': 'Ourdou',
'theme.label': 'Thème',
'theme.system': 'Système',
'theme.light': 'Clair',
'theme.dark': 'Sombre'
},
ar: {
'app.title': 'Nonograms',
'level.easy': 'سهل 5X5',
'level.medium': 'متوسط 10X10',
'level.hard': 'صعب 15X15',
'level.custom': 'مخصص',
'level.guide': 'دليل ❓',
'actions.reset': 'إعادة ضبط',
'actions.random': 'عشوائي جديد',
'actions.undo': 'تراجع',
'status.time': 'الوقت',
'status.moves': 'الحركات',
'status.progress': 'التقدم',
'fixed.time': 'الوقت:',
'fixed.progress': 'التقدم:',
'fixed.hide': 'إخفاء',
'fixed.show': 'إظهار',
'guide.play': 'ابدأ',
'guide.pause': 'إيقاف مؤقت',
'guide.step': 'خطوة',
'guide.speed': 'السرعة',
'guide.waiting': 'جارٍ الانتظار...',
'guide.solved': 'تم الحل!',
'custom.title': 'لعبة مخصصة',
'custom.prompt': 'أدخل حجم الشبكة (5 - 80):',
'custom.cancel': 'إلغاء',
'custom.start': 'ابدأ',
'custom.sizeError': 'يجب أن يكون الحجم بين 5 و80!',
'win.title': 'تهانينا!',
'win.message': 'لقد حللت اللغز!',
'win.time': 'الوقت:',
'win.playAgain': 'العب مرة أخرى',
'win.shareTitle': 'شارك نتيجتك',
'win.shareText': 'حللت نونوغرام {size}x{size} في {time}!',
'win.shareX': 'X',
'win.shareFacebook': 'Facebook',
'win.shareWhatsapp': 'WhatsApp',
'win.shareDownload': 'تحميل لقطة الشاشة',
'pwa.installTitle': 'ثبّت التطبيق والعب دون اتصال',
'pwa.installMobile': 'أضف إلى الشاشة الرئيسية',
'pwa.installDesktop': 'التثبيت على سطح المكتب',
'language.label': 'اختيار اللغة',
'language.pl': 'البولندية',
'language.en': 'الإنجليزية',
'language.zh': 'الصينية',
'language.hi': 'الهندية',
'language.es': 'الإسبانية',
'language.fr': 'الفرنسية',
'language.ar': 'العربية',
'language.bn': 'البنغالية',
'language.ru': 'الروسية',
'language.pt': 'البرتغالية',
'language.ur': 'الأردية',
'theme.label': 'السمة',
'theme.system': 'النظام',
'theme.light': 'فاتح',
'theme.dark': 'داكن'
},
bn: {
'app.title': 'Nonograms',
'level.easy': 'সহজ 5X5',
'level.medium': 'মাঝারি 10X10',
'level.hard': 'কঠিন 15X15',
'level.custom': 'কাস্টম',
'level.guide': 'গাইড ❓',
'actions.reset': 'রিসেট',
'actions.random': 'নতুন র‍্যান্ডম',
'actions.undo': 'পূর্বাবস্থায়',
'status.time': 'সময়',
'status.moves': 'চাল',
'status.progress': 'অগ্রগতি',
'fixed.time': 'সময়:',
'fixed.progress': 'অগ্রগতি:',
'fixed.hide': 'লুকান',
'fixed.show': 'দেখান',
'guide.play': 'শুরু',
'guide.pause': 'বিরতি',
'guide.step': 'ধাপ',
'guide.speed': 'গতি',
'guide.waiting': 'অপেক্ষা...',
'guide.solved': 'সমাধান হয়েছে!',
'custom.title': 'কাস্টম গেম',
'custom.prompt': 'গ্রিডের আকার দিন (5 - 80):',
'custom.cancel': 'বাতিল',
'custom.start': 'শুরু',
'custom.sizeError': 'আকার 5 থেকে 80 এর মধ্যে হতে হবে!',
'win.title': 'অভিনন্দন!',
'win.message': 'আপনি ধাঁধা সমাধান করেছেন!',
'win.time': 'সময়:',
'win.playAgain': 'আবার খেলুন',
'win.shareTitle': 'আপনার ফলাফল শেয়ার করুন',
'win.shareText': 'আমি {time} সময়ে {size}x{size} ননোগ্রাম সমাধান করেছি!',
'win.shareX': 'X',
'win.shareFacebook': 'Facebook',
'win.shareWhatsapp': 'WhatsApp',
'win.shareDownload': 'স্ক্রিনশট ডাউনলোড করুন',
'pwa.installTitle': 'অ্যাপটি ইনস্টল করে অফলাইনে খেলুন',
'pwa.installMobile': 'হোম স্ক্রিনে যোগ করুন',
'pwa.installDesktop': 'ডেস্কটপে ইনস্টল করুন',
'language.label': 'ভাষা নির্বাচন',
'language.pl': 'পোলিশ',
'language.en': 'ইংরেজি',
'language.zh': 'চীনা',
'language.hi': 'হিন্দি',
'language.es': 'স্প্যানিশ',
'language.fr': 'ফরাসি',
'language.ar': 'আরবি',
'language.bn': 'বাংলা',
'language.ru': 'রুশ',
'language.pt': 'পর্তুগিজ',
'language.ur': 'উর্দু',
'theme.label': 'থিম',
'theme.system': 'সিস্টেম',
'theme.light': 'হালকা',
'theme.dark': 'গাঢ়'
},
ru: {
'app.title': 'Nonograms',
'level.easy': 'ЛЕГКО 5X5',
'level.medium': 'СРЕДНЕ 10X10',
'level.hard': 'ТЯЖЕЛО 15X15',
'level.custom': 'СВОЯ',
'level.guide': 'ПОДСКАЗКА ❓',
'actions.reset': 'СБРОС',
'actions.random': 'НОВАЯ СЛУЧАЙНАЯ',
'actions.undo': 'ОТМЕНА',
'status.time': 'ВРЕМЯ',
'status.moves': 'ХОДЫ',
'status.progress': 'ПРОГРЕСС',
'fixed.time': 'Время:',
'fixed.progress': 'Прогресс:',
'fixed.hide': 'Скрыть',
'fixed.show': 'Показать',
'guide.play': 'СТАРТ',
'guide.pause': 'ПАУЗА',
'guide.step': 'ШАГ',
'guide.speed': 'СКОРОСТЬ',
'guide.waiting': 'Ожидание...',
'guide.solved': 'Решено!',
'custom.title': 'СВОЯ ИГРА',
'custom.prompt': 'Введите размер сетки (5 - 80):',
'custom.cancel': 'Отмена',
'custom.start': 'Старт',
'custom.sizeError': 'Размер должен быть от 5 до 80!',
'win.title': 'ПОЗДРАВЛЯЕМ!',
'win.message': 'Вы решили головоломку!',
'win.time': 'Время:',
'win.playAgain': 'Сыграть снова',
'win.shareTitle': 'Поделитесь результатом',
'win.shareText': 'Я решил(а) нонограмму {size}x{size} за {time}!',
'win.shareX': 'X',
'win.shareFacebook': 'Facebook',
'win.shareWhatsapp': 'WhatsApp',
'win.shareDownload': 'Скачать снимок',
'pwa.installTitle': 'Установите приложение и играйте офлайн',
'pwa.installMobile': 'Добавить на главный экран',
'pwa.installDesktop': 'Установить на компьютер',
'language.label': 'Выбор языка',
'language.pl': 'Польский',
'language.en': 'Английский',
'language.zh': 'Китайский',
'language.hi': 'Хинди',
'language.es': 'Испанский',
'language.fr': 'Французский',
'language.ar': 'Арабский',
'language.bn': 'Бенгальский',
'language.ru': 'Русский',
'language.pt': 'Португальский',
'language.ur': 'Урду',
'theme.label': 'Тема',
'theme.system': 'Система',
'theme.light': 'Светлая',
'theme.dark': 'Тёмная'
},
pt: {
'app.title': 'Nonograms',
'level.easy': 'FÁCIL 5X5',
'level.medium': 'MÉDIO 10X10',
'level.hard': 'DIFÍCIL 15X15',
'level.custom': 'PERSONALIZADO',
'level.guide': 'GUIA ❓',
'actions.reset': 'REINICIAR',
'actions.random': 'NOVO ALEATÓRIO',
'actions.undo': 'DESFAZER',
'status.time': 'TEMPO',
'status.moves': 'MOVIMENTOS',
'status.progress': 'PROGRESSO',
'fixed.time': 'Tempo:',
'fixed.progress': 'Progresso:',
'fixed.hide': 'Ocultar',
'fixed.show': 'Mostrar',
'guide.play': 'INICIAR',
'guide.pause': 'PAUSAR',
'guide.step': 'PASSO',
'guide.speed': 'VELOCIDADE',
'guide.waiting': 'Aguardando...',
'guide.solved': 'Resolvido!',
'custom.title': 'JOGO PERSONALIZADO',
'custom.prompt': 'Digite o tamanho da grade (5 - 80):',
'custom.cancel': 'Cancelar',
'custom.start': 'Iniciar',
'custom.sizeError': 'O tamanho deve estar entre 5 e 80!',
'win.title': 'PARABÉNS!',
'win.message': 'Você resolveu o puzzle!',
'win.time': 'Tempo:',
'win.playAgain': 'Jogar novamente',
'win.shareTitle': 'Compartilhe seu resultado',
'win.shareText': 'Resolvi um nonograma {size}x{size} em {time}!',
'win.shareX': 'X',
'win.shareFacebook': 'Facebook',
'win.shareWhatsapp': 'WhatsApp',
'win.shareDownload': 'Baixar captura',
'pwa.installTitle': 'Instale o app e jogue offline',
'pwa.installMobile': 'Adicionar à tela inicial',
'pwa.installDesktop': 'Instalar no desktop',
'language.label': 'Seleção de idioma',
'language.pl': 'Polonês',
'language.en': 'Inglês',
'language.zh': 'Chinês',
'language.hi': 'Hindi',
'language.es': 'Espanhol',
'language.fr': 'Francês',
'language.ar': 'Árabe',
'language.bn': 'Bengali',
'language.ru': 'Russo',
'language.pt': 'Português',
'language.ur': 'Urdu',
'theme.label': 'Tema',
'theme.system': 'Sistema',
'theme.light': 'Claro',
'theme.dark': 'Escuro'
},
ur: {
'app.title': 'Nonograms',
'level.easy': 'آسان 5X5',
'level.medium': 'درمیانہ 10X10',
'level.hard': 'مشکل 15X15',
'level.custom': 'حسب ضرورت',
'level.guide': 'رہنمائی ❓',
'actions.reset': 'ری سیٹ',
'actions.random': 'نیا رینڈم',
'actions.undo': 'واپس',
'status.time': 'وقت',
'status.moves': 'چالیں',
'status.progress': 'پیش رفت',
'fixed.time': 'وقت:',
'fixed.progress': 'پیش رفت:',
'fixed.hide': 'چھپائیں',
'fixed.show': 'دکھائیں',
'guide.play': 'شروع',
'guide.pause': 'توقف',
'guide.step': 'قدم',
'guide.speed': 'رفتار',
'guide.waiting': 'انتظار...',
'guide.solved': 'حل ہوگیا!',
'custom.title': 'حسب ضرورت کھیل',
'custom.prompt': 'گرڈ کا سائز درج کریں (5 - 80):',
'custom.cancel': 'منسوخ',
'custom.start': 'شروع',
'custom.sizeError': 'سائز 5 اور 80 کے درمیان ہونا چاہیے!',
'win.title': 'مبارک ہو!',
'win.message': 'آپ نے پہیلی حل کر لی!',
'win.time': 'وقت:',
'win.playAgain': 'دوبارہ کھیلیں',
'win.shareTitle': 'اپنا نتیجہ شیئر کریں',
'win.shareText': 'میں نے {time} میں {size}x{size} نونوگرام حل کیا!',
'win.shareX': 'X',
'win.shareFacebook': 'Facebook',
'win.shareWhatsapp': 'WhatsApp',
'win.shareDownload': 'اسکرین شاٹ ڈاؤن لوڈ کریں',
'pwa.installTitle': 'ایپ انسٹال کریں اور آف لائن کھیلیں',
'pwa.installMobile': 'ہوم اسکرین پر شامل کریں',
'pwa.installDesktop': 'ڈیسک ٹاپ پر انسٹال کریں',
'language.label': 'زبان کا انتخاب',
'language.pl': 'پولش',
'language.en': 'انگریزی',
'language.zh': 'چینی',
'language.hi': 'ہندی',
'language.es': 'ہسپانوی',
'language.fr': 'فرانسیسی',
'language.ar': 'عربی',
'language.bn': 'بنگالی',
'language.ru': 'روسی',
'language.pt': 'پرتگالی',
'language.ur': 'اردو',
'theme.label': 'تھیم',
'theme.system': 'سسٹم',
'theme.light': 'ہلکا',
'theme.dark': 'گہرا'
} }
}; };
@@ -118,7 +666,7 @@ const t = (key, params) => {
}; };
const setLocale = (value) => { const setLocale = (value) => {
locale.value = messages[value] ? value : 'en'; locale.value = supportedLocales.includes(value) ? value : 'en';
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
document.documentElement.lang = locale.value; document.documentElement.lang = locale.value;
document.title = t('app.title'); document.title = t('app.title');