Working localisation (big spaghetti)

This commit is contained in:
MaciejkaG 2024-03-27 15:51:33 +01:00
parent 2e3a46b7e1
commit 9d0c60419e
17 changed files with 415 additions and 236 deletions

View File

@ -6,16 +6,16 @@ import { createServer } from 'node:http';
import { Server } from 'socket.io';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { v4 as uuidv4, validate } from 'uuid';
import { v4 as uuidv4 } from 'uuid';
import session from "express-session";
import { engine } from 'express-handlebars';
import { createClient } from 'redis';
import * as bships from './utils/battleships.js';
import { MailAuth } from './utils/auth.js';
import { Lang } from './utils/localisation.js';
import { rateLimit } from 'express-rate-limit';
import { RedisStore as LimiterRedisStore } from 'rate-limit-redis';
import SessionRedisStore from 'connect-redis';
import { I18n } from 'i18n';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@ -37,11 +37,6 @@ const redis = createClient();
redis.on('error', err => console.log('Redis Client Error', err));
await redis.connect();
const i18n = new I18n({
locales: ['en', 'pl'],
directory: path.join(__dirname, 'lang')
});
const limiter = rateLimit({
windowMs: 40 * 1000,
limit: 500,
@ -93,26 +88,47 @@ io.engine.use(sessionMiddleware);
app.get('/', async (req, res) => {
let login = loginState(req);
const locale = new Lang(req.acceptsLanguages());
if (login != 2) {
res.redirect('/login');
} else if (req.session.nickname == null) {
auth.getNickname(req.session.userId).then(nickname => {
if (nickname != null) {
req.session.langs = req.acceptsLanguages();
req.session.nickname = nickname;
res.render('index');
res.render('index', {
helpers: {
t: (key) => { return locale.t(key) }
}
});
} else {
res.redirect('/nickname');
}
});
} else {
res.render('index');
req.session.langs = req.acceptsLanguages();
res.render('index', {
helpers: {
t: (key) => { return locale.t(key) }
}
});
}
});
app.get('/login', (req, res) => {
let login = loginState(req);
const locale = new Lang(req.acceptsLanguages());
if (!login) {
res.render('login');
res.render('login', {
helpers: {
t: (key) => { return locale.t(key) }
}
});
} else if (login == 1) {
res.redirect('/auth');
} else {
@ -122,10 +138,17 @@ app.get('/login', (req, res) => {
app.get('/auth', (req, res) => {
let login = loginState(req);
const locale = new Lang(req.acceptsLanguages());
if (!login) { // Niezalogowany
res.redirect('/login');
} else if (login == 1) { // W trakcie autoryzacji
res.render('auth');
res.render('auth', {
helpers: {
t: (key) => { return locale.t(key) }
}
});
} else { // Zalogowany
res.redirect('/auth');
}
@ -133,15 +156,23 @@ app.get('/auth', (req, res) => {
app.get('/nickname', (req, res) => {
let login = loginState(req);
const locale = new Lang(req.acceptsLanguages());
if (!login) { // Niezalogowany
res.redirect('/login');
} else {
res.render('setup');
res.render('setup', {
helpers: {
t: (key) => { return locale.t(key) }
}
});
}
});
app.post('/api/login', (req, res) => {
let login = loginState(req);
if (login == 2) {
res.redirect('/');
} else if (login == 0 && req.body.email != null && validateEmail(req.body.email)) {
@ -164,19 +195,25 @@ app.post('/api/login', (req, res) => {
res.sendStatus(500);
}
}).catch((err) => {
const locale = new Lang(req.acceptsLanguages());
res.render("error", {
helpers: {
error: "Wystąpił nieznany błąd logowania",
fallback: "/login"
fallback: "/login",
t: (key) => { return locale.t(key) }
}
});
throw err;
});
} else {
const locale = new Lang(req.acceptsLanguages());
res.render("error", {
helpers: {
error: "Niepoprawny adres e-mail",
fallback: "/login"
fallback: "/login",
}
});
}
@ -192,18 +229,24 @@ app.post('/api/auth', async (req, res) => {
req.session.loggedIn = 2;
res.redirect('/');
} else {
const locale = new Lang(req.acceptsLanguages());
res.render("error", {
helpers: {
error: "Niepoprawny kod logowania",
fallback: "/auth"
fallback: "/auth",
t: (key) => { return locale.t(key) }
}
});
}
} else {
const locale = new Lang(req.acceptsLanguages());
res.render("error", {
helpers: {
error: "Niepoprawny kod logowania",
fallback: "/login"
fallback: "/login",
t: (key) => { return locale.t(key) }
}
});
}
@ -222,6 +265,9 @@ app.post('/api/nickname', (req, res) => {
});
app.get('/game', async (req, res) => {
const locale = new Lang(req.acceptsLanguages());
const game = await redis.json.get(`game:${req.query.id}`);
if (req.session.nickname == null) {
res.redirect('/setup');
@ -229,11 +275,16 @@ app.get('/game', async (req, res) => {
res.render("error", {
helpers: {
error: "Nie znaleziono wskazanej gry",
fallback: "/"
fallback: "/",
t: (key) => { return locale.t(key) }
}
});
} else {
res.render('board');
res.render('board', {
helpers: {
t: (key) => { return locale.t(key) }
}
});
}
});
@ -428,9 +479,13 @@ io.on('connection', async (socket) => {
let shipAvailable = bships.getShipsAvailable(playerShips)[type] > 0;
if (!canPlace) {
socket.emit("toast", "Nie możesz postawić tak statku");
const locale = new Lang(session.langs);
socket.emit("toast", locale.t("board.You cannot place a ship like this"));
} else if (!shipAvailable) {
socket.emit("toast", "Nie masz już statków tego typu");
const locale = new Lang(session.langs);
socket.emit("toast", locale.t("board.You have ran out of ships of that type"));
} else {
await GInfo.placeShip(socket, { type: type, posX: posX, posY: posY, rot: rot, hits: Array.from(new Array(type+1), () => false) });
socket.emit("placed ship", { type: type, posX: posX, posY: posY, rot: rot });
@ -491,7 +546,9 @@ io.on('connection', async (socket) => {
return;
}
} else if (hit.status === -1) {
socket.emit("toast", "Już strzeliłeś w to miejsce");
const locale = new Lang(session.langs);
socket.emit("toast", locale.t("You have already shot at this field"));
return;
}

View File

@ -1,13 +1,14 @@
{
"errors": {
"Reonnecting...": "Reconnecting...",
"Reconnecting": "Reconnecting...",
"Reconnected": "Reconnected",
"Reconnection error occured": "Reconnection error occured",
"Reconection failed": "Reconnection failed",
"Reconnection failed": "Reconnection failed",
"Disconnected": "Disconnected",
"Try to refresh the page if this error reoccurs": "Try to refresh the page if this error reoccurs",
"Connection error": "Connection error"
},
"menu": {
"navbar": {
"Main menu": "Main menu",
@ -17,7 +18,7 @@
"Select game mode": "Select game mode",
"PvP": "PvP",
"Play against another player": "Play against another player",
"Vs. AI": "Vs. AI",
"Vs AI": "Vs. AI",
"Play against the computer": "Play against the computer"
},
"PvP": {
@ -38,14 +39,14 @@
"Room code": "Room code",
"Join": "Join"
},
"PvP/Prepairing": {
"PvP/Loading": {
"PvP / Loading": "PvP / Loading",
"Wait...": "Wait...",
"Wait": "Wait...",
"You will be redirected soon": "You will be redirected soon",
"Opponent:": "Opponent"
},
"Profile": {
"Loading...": "Loading...",
"Loading": "Loading...",
"Player since:": "Player since:",
"Victory": "Victory",
"Defeat": "Defeat",
@ -53,13 +54,62 @@
"matches played this month": "matches played this month",
"total matches played": "total matches played",
"winrate": "winrate",
"No matches played": "No matches played"
},
"General": {
"Unknown error occured": "Unknown error occured",
"Status:": "Status:"
}
},
"login": {
"Login": "Login",
"E-mail address": "E-mail address",
"Proceed": "Proceed"
},
"auth": {
"Authorisation": "Authorisation",
"Enter the code sent to your e-mail box": "Enter the code sent to your e-mail box",
"Code": "Code",
"Login": "Login"
},
"setup": {
"Profile setup": "Profile setup",
"Your nickname will be visible to other players": "Your nickname will be visible to other players",
"Nickname": "Nickname",
"Confirm": "Confirm"
},
"error": {
"Error": "Erorr",
"Return": "Return"
},
"board": {
"Connecting": "Connecting...",
"Waiting for the server": "Waiting for the server...",
"Back to menu": "Back to menu",
"Selected ship": "Selected ship",
"Single-masted": "Single-masted",
"Two-masted": "Two-masted",
"Three-masted": "Three-masted",
"Four-masted": "Four-masted",
"Available:": "Available:",
"Controls": "Controls",
"Change ship": "Change ship",
"Rotate ship": "Rotate ship",
"Change boards": "Change boards",
"Preparation phase": "Preparation phase",
"Your turn": "Your turn",
"Opponents turn": "Opponent's turn",
"You cannot place a ship like this": "You cannot place a ship like this",
"You have ran out of ships of that type": "You have ran out of ships of that type",
"You have already shot at this field": "You have already shot at this field"
}
}

View File

@ -0,0 +1,117 @@
{
"errors": {
"Reconnecting": "Ponowne łączenie...",
"Reconnected": "Połączono ponownie",
"Reconnection error occured": "Wystąpił problem podczas ponownego łączenia",
"Reconnection failed": "Ponowne łączenie nieudane",
"Disconnected": "Rozłączono",
"Try to refresh the page if this error reoccurs": "Spróbuj odświeżyć stronę jeżeli błąd będzie się powtarzał",
"Connection error": "Błąd połączenia"
},
"menu": {
"navbar": {
"Main menu": "Menu główne",
"Profile": "Profil"
},
"index": {
"Select game mode": "Wybierz tryb gry",
"PvP": "PvP",
"Play against another player": "Graj przeciwko innemu graczowi",
"Vs AI": "Vs. AI",
"Play against the computer": "Graj przeciwko komputerowi"
},
"PvP": {
"PvP": "PvP",
"Create": "Stwórz",
"Create your own room": "Stwórz własny pokój",
"Join": "Dołącz",
"Join a room by a code": "Dołącz do pokoju poprzez kod"
},
"PvP/Create": {
"PvP / Create": "PvP / Stwórz",
"Room code:": "Kod pokoju:",
"Waiting for an opponent": "Oczekiwanie na przeciwnika",
"Leave the room": "Opuść pokój"
},
"PvP/Join": {
"PvP / Join": "PvP / Dołącz",
"Room code": "Kod pokoju",
"Join": "Dołącz"
},
"PvP/Loading": {
"PvP / Loading": "PvP / Wczytywanie",
"Wait": "Czekaj...",
"You will be redirected soon": "Wkrótce zostaniesz przekierowany(-a)",
"Opponent:": "Przeciwnik"
},
"Profile": {
"Loading": "Wczytywanie...",
"Player since:": "Gracz od:",
"Victory": "Zwycięstwo",
"Defeat": "Porażka",
"Click to view match statistics": "Kliknij by wyświetlić statystyki meczu",
"matches played this month": "meczy zagranych w tym miesiącu",
"total matches played": "meczy zagranych łącznie",
"winrate": "winrate",
"No matches played": "Nie zagrano meczy"
},
"General": {
"Unknown error occured": "Wystąpił nieznany błąd",
"Status:": "Status:"
}
},
"login": {
"Login": "Zaloguj się",
"E-mail address": "Adres e-mail",
"Proceed": "Kontynuuj"
},
"auth": {
"Authorisation": "Autoryzacja",
"Enter the code sent to your e-mail box": "Podaj kod wysłany do twojej skrzynki e-mail",
"Code": "Kod",
"Login": "Zaloguj się"
},
"setup": {
"Profile setup": "Konfiguracja profilu",
"Your nickname will be visible to other players": "Twoja nazwa użytkownika będzie widoczna dla innych graczy",
"Nickname": "Nazwa użytkownika",
"Confirm": "Potwierdź"
},
"error": {
"Error": "Błąd",
"Return": "Wróć"
},
"board": {
"Connecting": "Łączenie...",
"Waiting for the server": "Oczekiwanie na serwer...",
"Back to menu": "Powrót do menu",
"Selected ship": "Wybrany statek",
"Single-masted": "Jednomasztowiec",
"Two-masted": "Dwumasztowiec",
"Three-masted": "Trójmasztowiec",
"Four-masted": "Czteromasztowiec",
"Available:": "Dostępne:",
"Controls": "Sterowanie",
"Change ship": "Zmień statek",
"Rotate ship": "Obróć statek",
"Change boards": "Zmień plansze",
"Preparation phase": "Faza przygotowań",
"Your turn": "Twoja tura",
"Opponents turn": "Tura przeciwnika",
"You cannot place a ship like this": "Nie możesz tak postawić statku",
"You have ran out of ships of that type": "Skończyły ci się statki tego typu",
"You have already shot at this field": "Już strzelałeś w to pole"
}
}

106
package-lock.json generated
View File

@ -16,7 +16,6 @@
"express-rate-limit": "^7.2.0",
"express-session": "^1.18.0",
"geoip-lite": "^1.4.10",
"i18n": "^0.15.1",
"mysql": "^2.18.1",
"nodemailer": "^6.9.12",
"rate-limit-redis": "^4.2.0",
@ -42,45 +41,6 @@
"node": ">=12"
}
},
"node_modules/@messageformat/core": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/@messageformat/core/-/core-3.3.0.tgz",
"integrity": "sha512-YcXd3remTDdeMxAlbvW6oV9d/01/DZ8DHUFwSttO3LMzIZj3iO0NRw+u1xlsNNORFI+u0EQzD52ZX3+Udi0T3g==",
"dependencies": {
"@messageformat/date-skeleton": "^1.0.0",
"@messageformat/number-skeleton": "^1.0.0",
"@messageformat/parser": "^5.1.0",
"@messageformat/runtime": "^3.0.1",
"make-plural": "^7.0.0",
"safe-identifier": "^0.4.1"
}
},
"node_modules/@messageformat/date-skeleton": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@messageformat/date-skeleton/-/date-skeleton-1.0.1.tgz",
"integrity": "sha512-jPXy8fg+WMPIgmGjxSlnGJn68h/2InfT0TNSkVx0IGXgp4ynnvYkbZ51dGWmGySEK+pBiYUttbQdu5XEqX5CRg=="
},
"node_modules/@messageformat/number-skeleton": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@messageformat/number-skeleton/-/number-skeleton-1.2.0.tgz",
"integrity": "sha512-xsgwcL7J7WhlHJ3RNbaVgssaIwcEyFkBqxHdcdaiJzwTZAWEOD8BuUFxnxV9k5S0qHN3v/KzUpq0IUpjH1seRg=="
},
"node_modules/@messageformat/parser": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@messageformat/parser/-/parser-5.1.0.tgz",
"integrity": "sha512-jKlkls3Gewgw6qMjKZ9SFfHUpdzEVdovKFtW1qRhJ3WI4FW5R/NnGDqr8SDGz+krWDO3ki94boMmQvGke1HwUQ==",
"dependencies": {
"moo": "^0.5.1"
}
},
"node_modules/@messageformat/runtime": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@messageformat/runtime/-/runtime-3.0.1.tgz",
"integrity": "sha512-6RU5ol2lDtO8bD9Yxe6CZkl0DArdv0qkuoZC+ZwowU+cdRlVE1157wjCmlA5Rsf1Xc/brACnsZa5PZpEDfTFFg==",
"dependencies": {
"make-plural": "^7.0.0"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@ -273,11 +233,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/boolean": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@ -727,17 +682,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/fast-printf": {
"version": "1.6.9",
"resolved": "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.9.tgz",
"integrity": "sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==",
"dependencies": {
"boolean": "^3.1.4"
},
"engines": {
"node": ">=10.0"
}
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@ -987,25 +931,6 @@
"node": ">= 0.8"
}
},
"node_modules/i18n": {
"version": "0.15.1",
"resolved": "https://registry.npmjs.org/i18n/-/i18n-0.15.1.tgz",
"integrity": "sha512-yue187t8MqUPMHdKjiZGrX+L+xcUsDClGO0Cz4loaKUOK9WrGw5pgan4bv130utOwX7fHE9w2iUeHFalVQWkXA==",
"dependencies": {
"@messageformat/core": "^3.0.0",
"debug": "^4.3.3",
"fast-printf": "^1.6.9",
"make-plural": "^7.0.0",
"math-interval-parser": "^2.0.1",
"mustache": "^4.2.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/mashpie"
}
},
"node_modules/iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -1113,19 +1038,6 @@
"node": "14 || >=16.14"
}
},
"node_modules/make-plural": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-7.3.0.tgz",
"integrity": "sha512-/K3BC0KIsO+WK2i94LkMPv3wslMrazrQhfi5We9fMbLlLjzoOSJWr7TAdupLlDWaJcWxwoNosBkhFDejiu5VDw=="
},
"node_modules/math-interval-parser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-2.0.1.tgz",
"integrity": "sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -1207,24 +1119,11 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/moo": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
"integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q=="
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mustache": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
"integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
"bin": {
"mustache": "bin/mustache"
}
},
"node_modules/mysql": {
"version": "2.18.1",
"resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz",
@ -1530,11 +1429,6 @@
}
]
},
"node_modules/safe-identifier": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz",
"integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w=="
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",

View File

@ -26,7 +26,6 @@
"express-rate-limit": "^7.2.0",
"express-session": "^1.18.0",
"geoip-lite": "^1.4.10",
"i18n": "^0.15.1",
"mysql": "^2.18.1",
"nodemailer": "^6.9.12",
"rate-limit-redis": "^4.2.0",

View File

@ -63,6 +63,7 @@ nav span:hover {
background-color: black;
border: solid 1px white;
border-radius: 15px;
padding: 0.5rem 1rem;
user-select: none;
cursor: pointer;
transition: all 0.3s;
@ -87,6 +88,7 @@ nav span:hover {
background-color: black;
border: solid 1px white;
border-radius: 15px;
padding: 0.5rem 1rem;
user-select: none;
cursor: pointer;
transition: all 0.3s;

View File

@ -156,16 +156,16 @@ function switchShips() {
setTimeout(() => {
switch (selectedShip) {
case 0:
$("#selectedShip").html("Jednomasztowiec");
$("#selectedShip").html(window.locale["Single-masted"]);
break;
case 1:
$("#selectedShip").html("Dwumasztowiec");
$("#selectedShip").html(window.locale["Two-masted"]);
break;
case 2:
$("#selectedShip").html("Trójmasztowiec");
$("#selectedShip").html(window.locale["Three-masted"]);
break;
case 3:
$("#selectedShip").html("Czteromasztowiec");
$("#selectedShip").html(window.locale["Four-masted"]);
break;
}

View File

@ -1,7 +1,7 @@
// Handling connection errors
socket.on("reconnecting", (number) => {
Toastify({
text: `Ponowne łączenie... ${number}`,
text: `${window.locale["Reconnecting"]} ${number}`,
duration: 5000,
newWindow: true,
gravity: "bottom",
@ -13,7 +13,7 @@ socket.on("reconnecting", (number) => {
socket.on("reconnect", () => {
Toastify({
text: "Połączono ponownie",
text: window.locale["Reconnected"],
duration: 5000,
newWindow: true,
gravity: "bottom",
@ -25,7 +25,7 @@ socket.on("reconnect", () => {
socket.on("reconnect_error", () => {
Toastify({
text: "Wystąpił problem w trakcie ponownego łączenia",
text: window.locale["Reconnection error occured"],
duration: 5000,
newWindow: true,
gravity: "bottom",
@ -37,7 +37,7 @@ socket.on("reconnect_error", () => {
socket.on("reconnect_failed", () => {
Toastify({
text: "Nie udało się połączyć ponownie",
text: window.locale["Reconnection failed"],
duration: 5000,
newWindow: true,
gravity: "bottom",
@ -49,7 +49,7 @@ socket.on("reconnect_failed", () => {
socket.on("disconnect", () => {
Toastify({
text: "Rozłączono z serwerem\nSpróbuj odświeżyć stronę jeżeli błąd będzie się powtarzał",
text: `${window.locale["Disconnected"]}\n${window.locale["Try to refresh the page if this error reoccurs"]}`,
duration: 5000,
newWindow: true,
gravity: "bottom",
@ -61,7 +61,7 @@ socket.on("disconnect", () => {
socket.on("error", () => {
Toastify({
text: "Błąd połączenia",
text: window.locale["Connection error"],
duration: 5000,
newWindow: true,
gravity: "bottom",

View File

@ -144,14 +144,12 @@ socket.on("ship sunk", (victimIdx, ship) => {
let l = !ship.type ? ship.type + 1 : ship.type + 2;
if (victimIdx === playerIdx) {
for (let i = 0; i < l; i++) {
console.log("ourship");
setTimeout(() => {
bsc.setField(ship.posX + multips[0] * i, ship.posY + multips[1] * i, "sunken");
}, i * 150);
}
} else {
for (let i = 0; i < l; i++) {
console.log("theirship");
setTimeout(() => {
bsc.setFieldEnemy(ship.posX + multips[0] * i, ship.posY + multips[1] * i, "sunken");
}, i * 150);
@ -174,8 +172,8 @@ var updateTimer = setInterval(() => {
$("#timer").removeClass("active");
}
const minutes = Math.floor(time / 60).toLocaleString('pl-PL', { minimumIntegerDigits: 2, useGrouping: false });
const seconds = (time - minutes * 60).toLocaleString('pl-PL', { minimumIntegerDigits: 2, useGrouping: false });
const minutes = Math.floor(time / 60).toLocaleString(undefined, { minimumIntegerDigits: 2, useGrouping: false });
const seconds = (time - minutes * 60).toLocaleString(undefined, { minimumIntegerDigits: 2, useGrouping: false });
$("#timer").html(`${minutes}:${seconds}`);
}
@ -200,7 +198,7 @@ socket.on("game finished", (winnerIdx, oppName) => {
});
socket.on('connect', () => {
$(".cover .title").html("Oczekiwanie na serwer...");
$(".cover .title").html(window.locale["Waiting for the server"]);
});
socket.on("players ready", () => {
@ -213,12 +211,12 @@ socket.on("player idx", (idx) => {
socket.on('turn update', (turnData) => {
if (turnData.phase === "preparation") {
$("#whosTurn").html("Faza przygotowań");
$("#whosTurn").html(window.locale["Preparation phase"]);
$(".boardSwitch").css("opacity", 0.3);
} else {
postPrep = true;
myTurn = turnData.turn === playerIdx;
turnData.turn === playerIdx ? $("#whosTurn").html("Twoja tura") : $("#whosTurn").html("Tura przeciwnika");
turnData.turn === playerIdx ? $("#whosTurn").html(window.locale["Your turn"]) : $("#whosTurn").html(window.locale["Opponents turn"]);
$(".boardSwitch").css("opacity", 1);
}
@ -230,8 +228,3 @@ socket.on('turn update', (turnData) => {
socket.on('player left', () => {
window.location.replace("/");
});
// Profile stats: SELECT ROUND((AVG(statistics.won)) * 100) AS winrate, COUNT(statistics.match_id) AS alltime_matches, COUNT(CASE WHEN (YEAR(matches.date) = YEAR(NOW()) AND MONTH(matches.date) = MONTH(NOW())) THEN matches.match_id END) AS monthly_matches FROM accounts NATURAL JOIN statistics NATURAL JOIN matches WHERE accounts.nickname = "MaciejkaG";
// Match history: SELECT statistics.match_id, accounts.nickname AS opponent, matches.match_type, statistics.won, matches.duration, matches.date FROM statistics JOIN matches ON matches.match_id = statistics.match_id JOIN accounts ON accounts.user_id = (CASE WHEN matches.host_id != statistics.user_id THEN matches.host_id ELSE matches.guest_id END) WHERE statistics.user_id = "c231c4f7-e179-11ee-920b-fa163e32c013";
// Profile: SELECT nickname, account_creation FROM accounts WHERE user_id = "c231c4f7-e179-11ee-920b-fa163e32c013";
// Badges: SELECT badge, achievement_date FROM badges WHERE user_id = "c231c4f7-e179-11ee-920b-fa163e32c013";

View File

@ -26,7 +26,7 @@ var nickname;
socket.emit("my profile", (profile) => {
// General profile data
let options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
$("#playerSince").html(new Date(profile.profile.account_creation).toLocaleDateString("pl-PL", options));
$("#playerSince").html(new Date(profile.profile.account_creation).toLocaleDateString(undefined, options));
$("#nickname").html(profile.profile.nickname);
// Profile stats
@ -45,16 +45,16 @@ socket.emit("my profile", (profile) => {
let date = new Date(match.date).toLocaleDateString(undefined, options);
const seconds = (match.duration - minutes * 60).toLocaleString(undefined, { minimumIntegerDigits: 2, useGrouping: false });
const minutes = Math.floor(match.duration / 60).toLocaleString(undefined, { minimumIntegerDigits: 2, useGrouping: false });
const seconds = (match.duration - minutes * 60).toLocaleString(undefined, { minimumIntegerDigits: 2, useGrouping: false });
const duration = `${minutes}:${seconds}`;
matchHistoryDOM += `<div class="match" data-matchid="${match.match_id}"><div><h1 class="dynamic${match.won === 1 ? "" : " danger"}">${match.won === 1 ? "Zwycięstwo" : "Porażka"}</h1><span> vs. ${match.match_type === "pvp" ? match.opponent : "AI"}</span></div><h2 class="statsButton">Kliknij by wyświetlić statystyki</h2><span>${date}</span><br><span>${duration}</span></div>`;
matchHistoryDOM += `<div class="match" data-matchid="${match.match_id}"><div><h1 class="dynamic${match.won === 1 ? "" : " danger"}">${match.won === 1 ? window.locale["Victory"] : window.locale["Defeat"]}</h1><span> vs. ${match.match_type === "pvp" ? match.opponent : "AI"}</span></div><h2 class="statsButton">${window.locale["Click to view match statistics"]}</h2><span>${date}</span><br><span>${duration}</span></div>`;
}
if (matchHistoryDOM === "") {
matchHistoryDOM = `<h2>${locale["No matches played"]}</h2>`;
matchHistoryDOM = `<h2>${window.locale["No matches played"]}</h2>`;
}
$(".matchList").html(matchHistoryDOM);
@ -84,7 +84,7 @@ $("#createGameButton").on("click", function () {
break;
default:
alert(`${locale["Unknown error occured"]}\n${locale["Status:"]} ${response.status}`);
alert(`${window.locale["Unknown error occured"]}\n${window.locale["Status:"]} ${response.status}`);
lockUI(false);
break;
}
@ -122,7 +122,7 @@ form.addEventListener('submit', (e) => {
// break;
default:
alert(`${locale["Unknown error occured"]}\n${locale["Status:"]} ${response.status}`);
alert(`${window.locale["Unknown error occured"]}\n${window.locale["Status:"]} ${response.status}`);
lockUI(false);
switchView("mainMenuView");
break;

41
utils/localisation.js Normal file
View File

@ -0,0 +1,41 @@
import path from 'node:path';
import fs from 'node:fs';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export class Lang {
constructor(langs) {
const languagesPath = path.join(__dirname, '../lang');
for (let i = 0; i < langs.length; i++) {
const lang = langs[i];
if (fs.readdirSync(languagesPath).includes(`${lang}.json`)) {
try {
this.allText = JSON.parse(fs.readFileSync(path.join(languagesPath, `${lang}.json`), 'utf8'));
return;
} catch (e) {
console.log(e);
}
}
}
}
t(key) {
if (this.allText == null) {
throw new Error(`Language class has been improperly configured. (Unknown localisation module error)`);
} else {
let keySplit = key.split(".");
try {
return keySplit.reduce((x, y) => x[y], this.allText);
} catch (e) {
if (e instanceof TypeError) {
return "LocKeyErr"
}
}
}
}
}

View File

@ -10,13 +10,13 @@
<div class="container" id="pvpJoinView">
<div>
<h1>Statki</h1>
<h2>Autoryzacja</h2>
<h2>{{ t 'auth.Authorisation' }}</h2>
<div class="modes">
<div>
<p>Podaj kod wysłany do Twojej skrzynki e-mail</p>
<p>{{ t 'auth.Enter the code sent to your e-mail box' }}</p>
<form action="/api/auth" method="post">
<input type="text" name="code" placeholder="Kod" style="font-size: 1rem;" minlength="8" maxlength="10" autocomplete="off">
<input type="submit" value="Zaloguj się">
<input type="text" name="code" placeholder="{{ t 'auth.Code' }}" style="font-size: 1rem;" minlength="8" maxlength="10" autocomplete="off">
<input type="submit" value="{{ t 'auth.Login' }}">
</form>
</div>
</div>

View File

@ -1,9 +1,9 @@
<div class="cover">
<h1 class="title">Łączenie...</h1>
<h1 class="title">{{ t 'board.Connecting' }}</h1>
<div class="dialog">
<h1 id="state"></h1>
<h3 id="opponent"></h3>
<button onclick="window.location.href = '/'">Powróć do menu</button>
<button onclick="window.location.href = '/'">{{ t 'board.Back to menu' }}</button>
</div>
</div>
@ -13,20 +13,20 @@
<div class="panelContainer">
<div class="shapes">
<div class="ownBoardInfo">
<h3>Wybrany statek</h3>
<h2 class="dynamic" id="selectedShip">Jednomasztowiec</h2>
<h3>Dostępne: <span class="dynamic danger" id="shipsLeft">1</span></h3>
<h3>{{ t 'board.Selected ship' }}</h3>
<h2 class="dynamic" id="selectedShip">{{ t 'board.Single-masted' }}</h2>
<h3>{{ t 'board.Available:' }} <span class="dynamic danger" id="shipsLeft">-</span></h3>
</div>
<span class="break"></span>
<div class="controls">
<h2>Sterowanie</h2>
<h3 class="controlsOwnBoard"><span class="important">S</span> Zmiana statku</h3>
<h3 class="controlsOwnBoard"><span class="important">R</span> Obrót statku</h3>
<h3 class="boardSwitch"><span class="important">B</span> Zamiana planszy</h3>
<h2>{{ t 'board.Controls' }}</h2>
<h3 class="controlsOwnBoard"><span class="important">S</span> {{ t 'board.Change ship' }}</h3>
<h3 class="controlsOwnBoard"><span class="important">R</span> {{ t 'board.Rotate ship' }}</h3>
<h3 class="boardSwitch"><span class="important">B</span> {{ t 'board.Change boards' }}</h3>
</div>
<div class="mobileControls">
<button class="controlsOwnBoard" onclick="switchShips()">Zmiana statku</button>
<button class="boardSwitch" onclick="switchBoards()">Zmiana planszy</button>
<button class="controlsOwnBoard" onclick="switchShips()">{{ t 'board.Change ship' }}</button>
<button class="boardSwitch" onclick="switchBoards()">{{ t 'board.Change boards' }}</button>
</div>
<span class="break"></span>
<h3><span class="dynamic" id="whosTurn"></span></h3>
@ -50,6 +50,29 @@
<button class="tippyBtn" style="background-color: var(--danger)" onclick="manualShoot([[FIELDPOS]])"><span class="material-symbols-outlined">ads_click</span></button>
</div>
<script>
window.locale = {
"Waiting for the server": "{{ t 'board.Waiting for the server' }}",
"Reconnecting": "{{ t 'errors.Reconnecting' }}",
"Reconnected": "{{ t 'errors.Reconnected' }}",
"Reconnection error occured": "{{ t 'errors.Reconnection error occured' }}",
"Reconnection failed": "{{ t 'errors.Reconnection failed' }}",
"Disconnected": "{{ t 'errors.Disconnected' }}",
"Try to refresh the page if this error reoccurs": "{{ t 'errors.Try to refresh the page if this error reoccurs' }}",
"Connection error": "{{ t 'errors.Connection error' }}",
"Single-masted": "{{ t 'board.Single-masted' }}",
"Two-masted": "{{ t 'board.Two-masted' }}",
"Three-masted": "{{ t 'board.Three-masted' }}",
"Four-masted": "{{ t 'board.Four-masted' }}",
"Preparation phase": "{{ t 'board.Preparation phase' }}",
"Your turn": "{{ t 'board.Your turn' }}",
"Opponents turn": "{{ t 'board.Opponents turn' }}",
};
</script>
<script src="/assets/js/battleships-lib.js"></script>
<script src="/assets/js/main.js"></script>
<script src="/assets/js/key-handling.js"></script>

View File

@ -28,11 +28,11 @@
<div class="container" id="pvpJoinView">
<div>
<h1>Statki</h1>
<h2>Błąd</h2>
<h2>{{ t 'error.Error' }}</h2>
<div class="modes">
<div>
<p>{{ error }}</p>
<button onclick="window.location.href = '{{ fallback }}'">Wróć</button>
<button onclick="window.location.href = '{{ fallback }}'">{{ t 'error.Return' }}</button>
</div>
</div>
</div>

View File

@ -1,111 +1,114 @@
<nav><span onclick="switchView('mainMenuView')">Menu główne</span> <span id="profileButton" onclick="switchView('profileView')">Profil</span></nav>
<nav><span onclick="switchView('mainMenuView')">{{ t 'menu.navbar.Main menu' }}</span> <span id="profileButton" onclick="switchView('profileView')">{{ t 'menu.navbar.Profile' }}</span></nav>
{{!-- <h1 class="header">Statki</h1> --}}
<div class="container" id="mainMenuView" data-title="Statki" data-path="/">
<div class="container" id="mainMenuView" data-path="/">
<div>
<h2>Wybierz tryb gry</h2>
<h2>{{ t 'menu.index.Select game mode' }}</h2>
<div class="modes">
<div id="pvpMenuButton">
<h2>PvP</h2>
<p>Graj przeciwko innemu graczowi</p>
<h2>{{ t 'menu.index.PvP' }}</h2>
<p>{{ t 'menu.index.Play against another player' }}</p>
</div>
<div>
<h2 id="ai">Vs. AI</h2>
<p>Graj przeciwko komputerowi</p>
<h2 id="ai">{{ t 'menu.index.Vs AI' }}</h2>
<p>{{ t 'menu.index.Play against the computer' }}</p>
</div>
</div>
</div>
</div>
<div class="container" id="pvpMenuView" data-title="Statki / PvP" data-path="/pvp">
<div class="container" id="pvpMenuView" data-path="/pvp">
<div>
<h2>PvP</h2>
<h2>{{ t 'menu.PvP.PvP' }}</h2>
<div class="modes">
<div id="createGameButton">
<h2>Stwórz</h2>
<p>Stwórz własny pokój</p>
<h2>{{ t 'menu.PvP.Create' }}</h2>
<p>{{ t 'menu.PvP.Create your own room' }}</p>
</div>
<div onclick="switchView('pvpJoinView')">
<h2>Dołącz</h2>
<p>Dołącz do czyjegoś pokoju poprzez kod</p>
<h2>{{ t 'menu.PvP.Join' }}</h2>
<p>{{ t 'menu.PvP.Join a room by a code' }}</p>
</div>
</div>
</div>
</div>
<div class="container" id="pvpCreateView" data-title="Statki / PvP / Stwórz" data-path="/pvp/create">
<div class="container" id="pvpCreateView" data-path="/pvp/create">
<div>
<h2>PvP / Stwórz</h2>
<h2>{{ t 'menu.PvP/Create.PvP / Create' }}</h2>
<div class="modes">
<div>
<h2>Kod pokoju:</h2>
<h2>{{ t 'menu.PvP/Create.Room code' }}</h2>
<input type="text" maxlength="6" readonly value="-" id="createGameCode">
<h3>Oczekiwanie na gracza...</h3>
<button id="leaveGameButton">Opuść pokój</button>
<h3>{{ t 'menu.PvP/Create.Waiting for an opponent' }}</h3>
<button id="leaveGameButton">{{ t 'menu.PvP/Create.Leave the room' }}</button>
</div>
</div>
</div>
</div>
<div class="container" id="pvpJoinView" data-title="Statki / PvP / Dołącz" data-path="/pvp/join">
<div class="container" id="pvpJoinView" data-path="/pvp/join">
<div>
<h2>PvP / Dołącz</h2>
<h2>{{ t 'menu.PvP/Join.PvP / Join' }}</h2>
<div class="modes">
<div>
<form action="/api/joinme" id="pvpJoinForm">
<input type="text" maxlength="6" id="pvpJoinCode" placeholder="Kod pokoju" autocomplete="off">
<input type="submit" value="Dołącz">
<input type="text" maxlength="6" id="pvpJoinCode" placeholder="{{ t 'menu.PvP/Join.Room code' }}" autocomplete="off">
<input type="submit" value="{{ t 'menu.PvP/Join.Join' }}">
</form>
</div>
</div>
</div>
</div>
<div class="container" id="preparingGame" data-title="Statki / PvP / Przygotowywanie" data-path="/pvp/prepairing">
<div class="container" id="preparingGame" data-path="/pvp/prepairing">
<div>
<h2>PvP / Wczytywanie</h2>
<h2>{{ t 'menu.PvP/Loading.PvP / Loading' }}</h2>
<div class="modes">
<div>
<h2>Czekaj...</h2>
<h3>Wkrótce nastąpi przekierowanie</h3>
<h3>Przeciwnik:</h3>
<h2>{{ t 'menu.PvP/Loading.Wait' }}</h2>
<h3>{{ t 'menu.PvP/Loading.You will be redirected soon' }}</h3>
<h3>{{ t 'menu.PvP/Loading.Opponent:' }}</h3>
<h4 id="oppNameField"></h4>
</div>
</div>
</div>
</div>
<div class="container" id="profileView" data-title="Statki / Profil" data-path="/profile">
<div class="container" id="profileView" data-path="/profile">
<div class="profile">
<h1 id="nickname">Wczytywanie...</h1>
<h1 id="nickname">{{ t 'menu.Profile.Loading' }}</h1>
<div>
<span>Gracz od: </span>
<span>{{ t 'menu.Profile.Player since:' }} </span>
<span id="playerSince">-</span>
</div>
</div>
<div class="stats">
<div class="matchList">
<div class="match">
<div><h1 class="dynamic danger">Porażka</h1><span> vs. Wczytywanie...</span></div>
<h2 class="statsButton">Kliknij by wyświetlić statystyki</h2>
</div>
<div class="match">
<div><h1 class="dynamic">Zwycięstwo</h1><span> vs. Wczytywanie...</span></div>
<h2 class="statsButton">Kliknij by wyświetlić statystyki</h2>
</div>
</div>
<div class="matchList"></div>
<div class="periodal">
<div class="stat"><h1 id="monthlyPlayed">-</h1><span>meczów zagranych w ostatnim miesiącu</span></div>
<div class="stat"><h1 id="totalPlayed">-</h1><span>meczów zagranych łącznie</span></div>
<div class="stat"><h1 id="winrate">-</h1><span>winrate</span></div>
<div class="stat"><h1 id="monthlyPlayed">-</h1><span>{{ t 'menu.Profile.matches played this month' }}</span></div>
<div class="stat"><h1 id="totalPlayed">-</h1><span>{{ t 'menu.Profile.total matches played' }}</span></div>
<div class="stat"><h1 id="winrate">-</h1><span>{{ t 'menu.Profile.winrate' }}</span></div>
</div>
</div>
</div>
<script>
locale = {
"Unknown error occured": "{{ __('Profile.') }}"
}
window.locale = {
"Unknown error occured": "{{ t 'menu.General.Unknown error occured' }}",
"Status:": "{{ t 'menu.General.Status:' }}",
"Victory": "{{ t 'menu.Profile.Victory' }}",
"Defeat": "{{ t 'menu.Profile.Defeat' }}",
"Click to view match statistics": "{{ t 'menu.Profile.Click to view match statistics' }}",
"Reconnecting": "{{ t 'errors.Reconnecting' }}",
"Reconnected": "{{ t 'errors.Reconnected' }}",
"Reconnection error occured": "{{ t 'errors.Reconnection error occured' }}",
"Reconnection failed": "{{ t 'errors.Reconnection failed' }}",
"Disconnected": "{{ t 'errors.Disconnected' }}",
"Try to refresh the page if this error reoccurs": "{{ t 'errors.Try to refresh the page if this error reoccurs' }}",
"Connection error": "{{ t 'errors.Connection error' }}",
};
</script>
<script src="/assets/js/spa_lib.js"></script>
<script src="/assets/js/socket.js"></script>

View File

@ -10,12 +10,12 @@
<div class="container" id="pvpJoinView">
<div>
<h1>Statki</h1>
<h2>Logowanie</h2>
<h2>{{ t 'login.Login' }}</h2>
<div class="modes">
<div>
<form action="/api/login" method="post">
<input type="email" name="email" placeholder="Adres e-mail" style="font-size: 1rem;">
<input type="submit" value="Przejdź dalej">
<input type="email" name="email" placeholder="{{ t 'login.E-mail address' }}" style="font-size: 1rem;">
<input type="submit" value="{{ t 'login.Proceed' }}">
</form>
</div>
</div>

View File

@ -10,13 +10,13 @@
<div class="container" id="pvpJoinView">
<div>
<h1>Statki</h1>
<h2>Konfiguracja profilu</h2>
<h2>{{ t 'setup.Profile setup' }}</h2>
<div class="modes">
<div>
<p>Twój nickname będzie widoczny dla innych graczy</p>
<p>{{ t 'setup.Your nickname will be visible to other players' }}</p>
<form action="/api/nickname" method="post">
<input type="text" name="nickname" placeholder="Nazwa użytkownika" style="font-size: 1rem;">
<input type="submit" value="Zatwierdź">
<input type="text" name="nickname" placeholder="{{ t 'setup.Nickname' }}" style="font-size: 1rem;">
<input type="submit" value="{{ t 'setup.Confirm' }}">
</form>
</div>
</div>