diff --git a/accounts/views/login.py b/accounts/views/login.py index fc02e4a..afcc03d 100644 --- a/accounts/views/login.py +++ b/accounts/views/login.py @@ -18,6 +18,6 @@ class LoginView(APIView): serializer.is_valid(raise_exception=True) user = serializer.get_user(data) if user is None: - return Response({'login': ['Invalid username or password.']}, status.HTTP_400_BAD_REQUEST) + return Response({'login': ['Invalid username or password.']}, status.HTTP_401_UNAUTHORIZED) login(request, user) return Response({'id': user.pk}, status=status.HTTP_200_OK) diff --git a/frontend/static/js/api/LanguageManager.js b/frontend/static/js/api/LanguageManager.js index 3e1c9b3..2c9ebb5 100644 --- a/frontend/static/js/api/LanguageManager.js +++ b/frontend/static/js/api/LanguageManager.js @@ -1,11 +1,17 @@ +import { reloadView } from '../index.js' + export default class LanguageManager { constructor() { this.availableLanguages = ['en', 'fr']; + this.dict = null; this.currentLang = 'en' this.chosenLang = localStorage.getItem('preferedLanguage') || this.currentLang; if (this.chosenLang !== this.currentLang && this.availableLanguages.includes(this.chosenLang)) { this.translatePage(); + this.currentLang = this.chosenLang; + } else { + this.loadDict(this.chosenLang); } } @@ -13,33 +19,48 @@ export default class LanguageManager { if (this.currentLang === this.chosenLang) return; - let dictUrl = `${location.origin}/static/js/lang/${this.chosenLang}.json`; - let translation = await fetch(dictUrl).then(response => { - if (response.status !== 200) - return null; - return response.json(); - }); - if (!translation) { - console.log(`No translation found for language ${this.chosenLang}`); + await this.loadDict(this.chosenLang); + if (!this.dict) return 1; - } + document.querySelectorAll('[data-i18n]').forEach(el => { let key = el.getAttribute('data-i18n'); - el.innerHTML = translation[key]; + el.innerHTML = this.dict[key]; }) + await reloadView(); - this.currentLang = this.chosenLang; return 0; } async changeLanguage(lang) { if (lang === this.currentLang || !this.availableLanguages.includes(lang)) - return; + return 1; this.chosenLang = lang; if (await this.translatePage() !== 0) - return; + return 1; + this.currentLang = this.chosenLang; localStorage.setItem('preferedLanguage', lang); + return 0; + } + + async loadDict(lang) { + let dictUrl = `${location.origin}/static/js/lang/${lang}.json`; + let response = await fetch(dictUrl); + + if (response.status !== 200) { + console.log(`No translation found for language ${lang}`); + return; + } + + this.dict = await response.json(); + } + + get(key, defaultTxt) { + if (!this.dict) + return defaultTxt; + + return this.dict[key] || defaultTxt; } } diff --git a/frontend/static/js/api/MyProfile.js b/frontend/static/js/api/MyProfile.js index 208531a..de3f46e 100644 --- a/frontend/static/js/api/MyProfile.js +++ b/frontend/static/js/api/MyProfile.js @@ -15,7 +15,7 @@ class MyProfile extends Profile /** * * @param {*} form_data - * @returns + * @returns {Promise} */ async change_avatar(form_data) { diff --git a/frontend/static/js/api/account.js b/frontend/static/js/api/account.js index 6abb89b..b8b7e83 100644 --- a/frontend/static/js/api/account.js +++ b/frontend/static/js/api/account.js @@ -16,7 +16,7 @@ class Account /** * @param {String} username * @param {String} password - * @returns + * @returns {?Promise} */ async create(username, password) { @@ -33,7 +33,7 @@ class Account /** * @param {String} password - * @returns + * @returns {?Promise} */ async delete(password) { @@ -52,7 +52,7 @@ class Account /** * Get account data (username) - * @returns + * @returns {?Promise} */ async get() { @@ -71,7 +71,7 @@ class Account * * @param {*} data * @param {Number} password - * @returns + * @returns {?Object} */ async update(data, password) { diff --git a/frontend/static/js/api/client.js b/frontend/static/js/api/client.js index 57d3ce4..dc0a843 100644 --- a/frontend/static/js/api/client.js +++ b/frontend/static/js/api/client.js @@ -78,7 +78,7 @@ class Client /** * The only right way to determine is the user is logged - * @returns {Boolean} + * @returns {Promise} */ async isAuthentificate() { @@ -91,7 +91,7 @@ class Client * Send a GET request to %uri% * @param {String} uri * @param {*} data - * @returns {Response} + * @returns {Promise} */ async _get(uri, data) { @@ -106,7 +106,7 @@ class Client * Send a POST request * @param {String} uri * @param {*} data - * @returns {Response} + * @returns {Promise} */ async _post(uri, data) { @@ -115,7 +115,8 @@ class Client headers: { "Content-Type": "application/json", "X-CSRFToken": getCookie("csrftoken"), - }, + 'Accept-Language': this.lang.currentLang, + }, body: JSON.stringify(data), }); return response; @@ -125,7 +126,7 @@ class Client * Send a DELETE request * @param {String} uri * @param {String} data - * @returns {Response} + * @returns {Promise} */ async _delete(uri, data) { @@ -144,7 +145,7 @@ class Client * Send a PATCH request with json * @param {String} uri * @param {*} data - * @returns {Response} + * @returns {Promise} */ async _patch_json(uri, data) { @@ -163,7 +164,7 @@ class Client * Send a PATCH request with file * @param {String} uri * @param {*} file - * @returns {Response} + * @returns {Promise} */ async _patch_file(uri, file) { @@ -179,7 +180,7 @@ class Client /** * Change logged state. Use It if you recv an 403 error - * @param {Boolean} state + * @param {Promise} state * @returns */ async _update_logged(state) @@ -211,7 +212,7 @@ class Client * Loggin the user * @param {String} username * @param {String} password - * @returns + * @returns {Promise} */ async login(username, password) { @@ -224,6 +225,7 @@ class Client /** * Logout the user + * @returns {Promise} */ async logout() { @@ -233,7 +235,7 @@ class Client /** * Determine if the user is logged. NEVER USE IT, USE isAuthentificated() - * @returns {Boolean} + * @returns {Promise} */ async _test_logged() { diff --git a/frontend/static/js/api/matchmaking.js b/frontend/static/js/api/matchmaking.js index 1178803..c63dd4d 100644 --- a/frontend/static/js/api/matchmaking.js +++ b/frontend/static/js/api/matchmaking.js @@ -19,7 +19,7 @@ class MatchMaking * @param {CallableFunction} receive_func * @param {CallableFunction} disconnect_func * @param {Number} mode The number of players in a game - * @returns {undefined} + * @returns {Promise} */ async start(receive_func, disconnect_func, mode) { @@ -50,6 +50,9 @@ class MatchMaking this.disconnect_func(event); } + /** + * @returns {Promise} + */ async stop() { if (this._socket) diff --git a/frontend/static/js/api/profile.js b/frontend/static/js/api/profile.js index d15d0a4..2b57a04 100644 --- a/frontend/static/js/api/profile.js +++ b/frontend/static/js/api/profile.js @@ -34,6 +34,10 @@ class Profile this.isFriend = false; } + /** + * + * @returns {Promise<*>} + */ async init() { let response; diff --git a/frontend/static/js/api/profiles.js b/frontend/static/js/api/profiles.js index 070769c..7c0d924 100644 --- a/frontend/static/js/api/profiles.js +++ b/frontend/static/js/api/profiles.js @@ -15,7 +15,7 @@ class Profiles /** * - * @returns {[Profile]} + * @returns {Promise<[Profile]>} */ async all() { @@ -53,7 +53,7 @@ class Profiles /** * Block a user * @param {Number} user_id - * @returns + * @returns {Promise} */ async block(user_id) { @@ -70,7 +70,7 @@ class Profiles /** * Unblock a user * @param {Number} user_id - * @returns + * @returns {Promise} */ async deblock(user_id) { diff --git a/frontend/static/js/api/tournament/tournament.js b/frontend/static/js/api/tournament/tournament.js index b0ac785..46a9502 100644 --- a/frontend/static/js/api/tournament/tournament.js +++ b/frontend/static/js/api/tournament/tournament.js @@ -73,6 +73,10 @@ class Tourmanent this.connected = false; } + /** + * + * @returns {Promise} + */ async init() { let response = await this.client._get(`/api/tournaments/${id}`); @@ -108,6 +112,12 @@ class Tourmanent this._socket.send(JSON.stringify({participate: ""})); } + /** + * Join the tournament Websocket + * @param {CallableFunction} receive_func + * @param {CallableFunction} disconnect_func + * @returns {?} + */ async join(receive_func, disconnect_func) { if (!await this.client.isAuthentificate()) diff --git a/frontend/static/js/api/tournament/tournaments.js b/frontend/static/js/api/tournament/tournaments.js index 2ae420a..9f00564 100644 --- a/frontend/static/js/api/tournament/tournaments.js +++ b/frontend/static/js/api/tournament/tournaments.js @@ -17,7 +17,7 @@ class Tourmanents /** * * @param {Number} id - * @returns + * @returns {?Promise} */ async getTournament(id) { @@ -47,6 +47,7 @@ class Tourmanents /** * @param {String} state must be "finished", or "started", or "waiting". Any other return all elements + * @returns {?Promise<[Tourmanent]>} */ async search(state) { @@ -76,6 +77,10 @@ class Tourmanents return tournaments; } + /** + * Get all tournaments + * @returns {?Promise<[Tourmanent]>} + */ async all() { return await this.search(""); diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index 7af76b6..0c505cc 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -19,10 +19,10 @@ import TournamentPageView from "./views/tournament/TournamentPageView.js"; import TournamentsView from "./views/tournament/TournamentsListView.js"; import TournamentCreateView from "./views/tournament/TournamentCreateView.js"; -let client = new Client(location.protocol + "//" + location.host) +let client = new Client(location.origin); -let lastView = undefined -let lastPageUrlBeforeLogin = undefined +let lastView = undefined; +let lastPageUrlBeforeLogin = undefined; const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$"); @@ -48,9 +48,11 @@ const navigateTo = async (uri) => { } }; +const reloadView = async _ => renderView(lastView); + async function renderView(view) { - let content = await view.getHtml(); + let content = await view?.getHtml(); if (content == null) return 1; @@ -108,14 +110,16 @@ const router = async(uri) => { if (lastView !== undefined) await lastView.leavePage(); + if (uri !== '/login' && uri !== '/register' && uri !== '/logout') + lastPageUrlBeforeLogin = uri; + else + lastPageUrlBeforeLogin = undefined; const view = new match.route.view(getParams(match), lastPageUrlBeforeLogin); if (view instanceof AbstractRedirectView && await view.redirect()) return 1; lastView = view; - if (uri !== '/login' && uri !== '/register' && uri !== '/logout') - lastPageUrlBeforeLogin = uri; if (await renderView(view)) return 1; @@ -139,12 +143,19 @@ document.addEventListener("DOMContentLoaded", async () => { //Languages Array.from(document.getElementById('languageSelector').children).forEach(el => { - el.onclick = _ => client.lang.changeLanguage(el.value); + el.onclick = async _ => { + if (await client.lang.changeLanguage(el.value)) + return; + document.querySelector('#languageSelector > .active')?.classList.remove('active'); + el.classList.add('active'); + }; }); + document.querySelector(`#languageSelector > [value=${client.lang.chosenLang}]`) + ?.classList.add('active'); await client.isAuthentificate(); router(location.pathname); document.querySelector('a[href=\'' + location.pathname + '\']')?.classList.add('active'); }); -export { client, navigateTo } +export { client, navigateTo, reloadView } diff --git a/frontend/static/js/lang/en.json b/frontend/static/js/lang/en.json index 3d9b14e..8fc8946 100644 --- a/frontend/static/js/lang/en.json +++ b/frontend/static/js/lang/en.json @@ -5,5 +5,11 @@ "navbarRegister": "Register", "navbarProfile": "My Profile", "navbarSettings": "Settings", - "navbarLogout": "Logout" + "navbarLogout": "Logout", + "homeWindowTitle": "Home", + "homeTitle": "Home", + "homeOnline": "Play online", + "homeOffline": "Play offline", + "homeMe": "Me", + "homeLogout": "Logout" } diff --git a/frontend/static/js/lang/fr.json b/frontend/static/js/lang/fr.json index 3307204..a879558 100644 --- a/frontend/static/js/lang/fr.json +++ b/frontend/static/js/lang/fr.json @@ -1,9 +1,15 @@ { "navbarSearch": "Recherche", "navbarHome": "Maison", - "navbarLogin": "Se connecter", + "navbarLogin": "Connexion", "navbarRegister": "S'inscrire", "navbarProfile": "Mon Profil", "navbarSettings": "Paramètres", - "navbarLogout": "Se déconnecter" + "navbarLogout": "Déconnexion", + "homeWindowTitle": "Maison", + "homeTitle": "Maison", + "homeOnline": "Jouer en ligne", + "homeOffline": "Jouer hors ligne", + "homeMe": "Moi", + "homeLogout": "Déconnexion" } diff --git a/frontend/static/js/views/HomeView.js b/frontend/static/js/views/HomeView.js index fb13511..a98fd9f 100644 --- a/frontend/static/js/views/HomeView.js +++ b/frontend/static/js/views/HomeView.js @@ -1,18 +1,19 @@ +import { client } from "../index.js"; import AbstractAuthentificateView from "./abstracts/AbstractAuthentifiedView.js"; export default class extends AbstractAuthentificateView { constructor(params) { - super(params, "Home"); + super(params, client.lang.get('homeWindowTitle', 'Home')); this.redirect_url = "/login" } async getHtml() { return /* HTML */ ` -

HOME

- Play a game - Play offline - Me - Logout +

${client.lang.get('homeTitle', 'Home')}

+ ${client.lang.get('homeOnline', 'Play online')} + ${client.lang.get('homeOffline', 'Play offline')} + ${client.lang.get('homeMe', 'Me')} + ${client.lang.get('homeLogout', 'Logout')} `; } } diff --git a/transcendence/settings.py b/transcendence/settings.py index b75cedb..f6193a8 100644 --- a/transcendence/settings.py +++ b/transcendence/settings.py @@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/ import os from pathlib import Path +from django.utils.translation import gettext_lazy as _ # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -79,6 +80,7 @@ MIDDLEWARE = [ 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'django.middleware.locale.LocaleMiddleware', ] ROOT_URLCONF = 'transcendence.urls' @@ -137,6 +139,11 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = 'en-us' +LANGUAGES = [ + ('en', _('English')), + ('fr', _('French')), +] + TIME_ZONE = 'UTC' USE_I18N = True