feat: Add new i18n
This commit is contained in:
parent
40079e1145
commit
3e28cb35e9
14
app.js
14
app.js
@ -1,4 +1,8 @@
|
|||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const redisHost = process.env.REDIS_HOST ? process.env.REDIS_HOST : 'localhost';
|
||||||
|
const redisPort = process.env.REDIS_PORT ? process.env.REDIS_PORT : 6379;
|
||||||
|
|
||||||
const createError = require('http-errors');
|
const createError = require('http-errors');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
@ -34,11 +38,11 @@ const sess = {
|
|||||||
cookie: { secure: false }
|
cookie: { secure: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (app.get('env') === 'production') {
|
// if (app.get('env') === 'production') {
|
||||||
app.set('trust proxy', 1); // trust first proxy
|
// app.set('trust proxy', 1); // trust first proxy
|
||||||
sess.cookie.secure = true; // serve secure cookies
|
// sess.cookie.secure = true; // serve secure cookies
|
||||||
sess.store = new RedisStore({ client: redisClient });
|
// sess.store = new RedisStore({ host: redisHost, port: redisPort, client: redisClient });
|
||||||
}
|
// }
|
||||||
|
|
||||||
app.use(session(sess));
|
app.use(session(sess));
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ const QUIZZ_SIZE = process.env.QUIZZ_SIZE ? process.env.QUIZZ_SIZE : 5;
|
|||||||
|
|
||||||
function getHome(req, res) {
|
function getHome(req, res) {
|
||||||
res.render('api', {
|
res.render('api', {
|
||||||
title: "SoundBirder api",
|
title: "SoundBirder API",
|
||||||
version: 0
|
version: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -24,7 +24,7 @@ async function region(req, res) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function quizz(req, res) {
|
async function quizz(req, res) {
|
||||||
debug('Generating quizz');
|
debug('Generating quizz');
|
||||||
const { region } = req.query;
|
const { region } = req.query;
|
||||||
// debug(`Coordinates: ${lat}, ${lng}`);
|
// debug(`Coordinates: ${lat}, ${lng}`);
|
||||||
|
@ -8,7 +8,7 @@ function indexPage(req, res) {
|
|||||||
|
|
||||||
function loginPage(req, res) {
|
function loginPage(req, res) {
|
||||||
res.render('auth/login', {
|
res.render('auth/login', {
|
||||||
title: req.i18n.__("SoundBirder - Login"),
|
title: req.i18n.__('SoundBirder - Login'),
|
||||||
locale: req.i18n.locale
|
locale: req.i18n.locale
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@ function logout(req, res) {
|
|||||||
|
|
||||||
function registerPage(req, res) {
|
function registerPage(req, res) {
|
||||||
res.render('auth/register', {
|
res.render('auth/register', {
|
||||||
title: req.i18n.__("SoundBirder - Register"),
|
title: req.i18n.__('SoundBirder - Register'),
|
||||||
locale: req.i18n.locale
|
locale: req.i18n.locale
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ function getIndex(req, res) {
|
|||||||
|
|
||||||
function getAbout(req, res) {
|
function getAbout(req, res) {
|
||||||
res.render('about', {
|
res.render('about', {
|
||||||
title: 'About SoundBirder',
|
title: req.i18n.__('About SoundBirder'),
|
||||||
locale: req.i18n.locale
|
locale: req.i18n.locale
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -46,9 +46,6 @@ async function generateQuizz(region, locale, size) {
|
|||||||
let answer;
|
let answer;
|
||||||
do {
|
do {
|
||||||
answer = choice(speciesSelectionLocalized);
|
answer = choice(speciesSelectionLocalized);
|
||||||
if (answer === undefined) {
|
|
||||||
debug('Species', speciesSelectionLocalized);
|
|
||||||
}
|
|
||||||
quizz.answer = answer;
|
quizz.answer = answer;
|
||||||
quizz.audio = await getAudio(answer.sciName);
|
quizz.audio = await getAudio(answer.sciName);
|
||||||
if (quizz.audio === undefined) {
|
if (quizz.audio === undefined) {
|
||||||
@ -89,8 +86,9 @@ async function getLocalizedNames(speciesCodes, locale) {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
if (!flag) {
|
if (!flag) {
|
||||||
|
let names;
|
||||||
try {
|
try {
|
||||||
const names = { speciesCode, sciName, comName } = await getLocalizedName(code, locale);
|
names = { speciesCode, sciName, comName } = await getLocalizedName(code, locale);
|
||||||
cache.cacheResponse(`${code}-${locale}`, names);
|
cache.cacheResponse(`${code}-${locale}`, names);
|
||||||
allNames.push(names);
|
allNames.push(names);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -121,7 +119,11 @@ function getLocalizedName(speciesCode, locale) {
|
|||||||
async function getSpeciesList(regionCode) {
|
async function getSpeciesList(regionCode) {
|
||||||
const cached = await cache.getCached(`spplist-${regionCode}`);
|
const cached = await cache.getCached(`spplist-${regionCode}`);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached;
|
if (cached != []) {
|
||||||
|
return cached;
|
||||||
|
} else {
|
||||||
|
console.log("Empty cached species list, retrieving again");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return eBird.product.spplist.in(regionCode)()
|
return eBird.product.spplist.in(regionCode)()
|
||||||
.then(species => {
|
.then(species => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
"Game": "Spiel",
|
"Game": "Spiel",
|
||||||
"About": "About"
|
"About": "About",
|
||||||
|
"It was a": "__It was a"
|
||||||
}
|
}
|
16
locales/en.json
Executable file → Normal file
16
locales/en.json
Executable file → Normal file
@ -1,15 +1 @@
|
|||||||
{
|
{}
|
||||||
"Home": "Home",
|
|
||||||
"About": "About",
|
|
||||||
"Game": "Game",
|
|
||||||
"Contact": "Contact",
|
|
||||||
"SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird": "SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird",
|
|
||||||
"Author": "Author",
|
|
||||||
"The project is made with ♥ by Samuel ORTION": "The project is made with ♥ by Samuel ORTION",
|
|
||||||
"SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.": "SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.",
|
|
||||||
"The project is made with ♥ by Samuel ORTION.": "The project is made with ♥ by Samuel ORTION.",
|
|
||||||
"Welcome to SoundBirder' API": "Welcome to SoundBirder' API",
|
|
||||||
"Wrong!": "Wrong!",
|
|
||||||
"Correct!": "Correct!",
|
|
||||||
"It was a": "It was a"
|
|
||||||
}
|
|
@ -1,4 +1,9 @@
|
|||||||
{
|
{
|
||||||
"Home": "Inicio",
|
"Home": "Inicio",
|
||||||
"About": "Acerca de"
|
"About": "Acerca de",
|
||||||
|
"SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.": "__SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.",
|
||||||
|
"Author": "__Author",
|
||||||
|
"The project is made with ♥ by Samuel Ortion.": "__The project is made with ♥ by Samuel Ortion.",
|
||||||
|
"It was a": "__It was a",
|
||||||
|
"Game": "__Game"
|
||||||
}
|
}
|
19
locales/fr.json
Executable file → Normal file
19
locales/fr.json
Executable file → Normal file
@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"Game": "Jeu",
|
"Game": "Jeu",
|
||||||
"About": "À propos",
|
"About": "À propos",
|
||||||
"Contact": "Contact",
|
"Contact": "Contact",
|
||||||
"SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird": "SoundBirder est une application web open-source qui permet d'apprendre à reconnaitre les chants d'oiseaux. Elle se base sur des enregistrements audios de Xeno-Canto et sur les données de répartitions d'eBird",
|
"Author": "Auteur",
|
||||||
"Author": "Auteur",
|
"The project is made with ♥ by Samuel Ortion": "Ce projet est fait avec ♥ par Samuel Ortion",
|
||||||
"The project is made with ♥ by Samuel ORTION": "Ce projet est fait avec ♥ par Samuel ORTION",
|
"Correct!": "Correct !",
|
||||||
"Correct!": "Correct!",
|
"Wrong!": "Incorrect !",
|
||||||
"Wrong!": "Incorrect!",
|
"It was a": "C'était un",
|
||||||
"It was a": "C'était un"
|
"SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.": "SoundBirder est une application open-source pour apprendre les chant d'oiseaux. Elle est basée sur les enregistrements de Xeno-Canto et sur les données d'eBird",
|
||||||
|
"The project is made with ♥ by Samuel Ortion.": "Ce projet est fait avec ♥ par Samuel Ortion."
|
||||||
}
|
}
|
@ -5,18 +5,24 @@ let map = document.getElementById('map');
|
|||||||
let startButton = document.querySelector('.game .start-button');
|
let startButton = document.querySelector('.game .start-button');
|
||||||
let gameMapStep = document.querySelector('.game-map-step');
|
let gameMapStep = document.querySelector('.game-map-step');
|
||||||
let gameQuizzStep = document.querySelector('.game-quizz-step');
|
let gameQuizzStep = document.querySelector('.game-quizz-step');
|
||||||
|
let gameQuizz = document.querySelector('.game-quizz');
|
||||||
let gameResultStep = document.querySelector('.game-results-step');
|
let gameResultStep = document.querySelector('.game-results-step');
|
||||||
|
let gameLoading = document.querySelector('.game-loading');
|
||||||
let audio = document.querySelector('.game-quizz-step audio');
|
let audio = document.querySelector('.game-quizz-step audio');
|
||||||
let proposals = document.querySelector('.game-quizz-step .proposals');
|
let proposals = document.querySelector('.game-quizz-step .proposals');
|
||||||
let result = document.querySelector('.game-results-step .message');
|
let result = document.querySelector('.game-results-step .message');
|
||||||
let restartButton = document.querySelector('.game-results-step .restart-button');
|
let restartButton = document.querySelector('.game-quizz .restart-button');
|
||||||
|
let mapButton = document.querySelector('.game-quizz .map-button');
|
||||||
let resultComName = document.querySelector('.game-results-step .species .com');
|
let resultComName = document.querySelector('.game-results-step .species .com');
|
||||||
let resultSciName = document.querySelector('.game-results-step .species .sci');
|
let resultSciName = document.querySelector('.game-results-step .species .sci');
|
||||||
|
|
||||||
const API_VERSION = "0";
|
|
||||||
|
|
||||||
let region = 'FR';
|
let region = 'FR';
|
||||||
|
|
||||||
|
function returnToMap() {
|
||||||
|
gameMapStep.classList.remove("none");
|
||||||
|
gameQuizz.classList.add("none");
|
||||||
|
}
|
||||||
|
|
||||||
function geolocationStep() {
|
function geolocationStep() {
|
||||||
if (map != undefined)
|
if (map != undefined)
|
||||||
geolocationHandler();
|
geolocationHandler();
|
||||||
@ -37,8 +43,11 @@ function regionCoder() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function quizzStep() {
|
function quizzStep() {
|
||||||
|
gameQuizz.classList.remove("none");
|
||||||
|
gameLoading.classList.remove("none");
|
||||||
client.getQuizz(region)
|
client.getQuizz(region)
|
||||||
.then(quizz => {
|
.then(quizz => {
|
||||||
|
gameLoading.classList.add("none");
|
||||||
displayQuizz(quizz);
|
displayQuizz(quizz);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@ -47,6 +56,10 @@ function quizzStep() {
|
|||||||
|
|
||||||
|
|
||||||
function displayQuizz(quizz) {
|
function displayQuizz(quizz) {
|
||||||
|
if (quizz.audio == undefined) {
|
||||||
|
quizzStep();
|
||||||
|
return;
|
||||||
|
}
|
||||||
audio.src = quizz.audio;
|
audio.src = quizz.audio;
|
||||||
audio.classList.remove("none"); // Display the audio controls
|
audio.classList.remove("none"); // Display the audio controls
|
||||||
gameQuizzStep.classList.remove("none");
|
gameQuizzStep.classList.remove("none");
|
||||||
@ -82,6 +95,7 @@ function verifyAnswer(event) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
restartButton.addEventListener('click', restart);
|
restartButton.addEventListener('click', restart);
|
||||||
|
mapButton.addEventListener('click', returnToMap);
|
||||||
|
|
||||||
function displayResult(message_class, message, species) {
|
function displayResult(message_class, message, species) {
|
||||||
gameQuizzStep.classList.toggle('none');
|
gameQuizzStep.classList.toggle('none');
|
||||||
|
101
public/stylesheets/spinner.css
Normal file
101
public/stylesheets/spinner.css
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/* html, body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/* body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
} */
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
-webkit-animation: rotator 1.4s linear infinite;
|
||||||
|
animation: rotator 1.4s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes rotator {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(270deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotator {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(270deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.path {
|
||||||
|
stroke-dasharray: 187;
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
transform-origin: center;
|
||||||
|
-webkit-animation: dash 1.4s ease-in-out infinite, colors 5.6s ease-in-out infinite;
|
||||||
|
animation: dash 1.4s ease-in-out infinite, colors 5.6s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes colors {
|
||||||
|
0% {
|
||||||
|
stroke: #4285F4;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
stroke: #DE3E35;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke: #F7C223;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
stroke: #1B9A59;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke: #4285F4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes colors {
|
||||||
|
0% {
|
||||||
|
stroke: #4285F4;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
stroke: #DE3E35;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke: #F7C223;
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
stroke: #1B9A59;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke: #4285F4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes dash {
|
||||||
|
0% {
|
||||||
|
stroke-dashoffset: 187;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke-dashoffset: 46.75;
|
||||||
|
transform: rotate(135deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 187;
|
||||||
|
transform: rotate(450deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes dash {
|
||||||
|
0% {
|
||||||
|
stroke-dashoffset: 187;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
stroke-dashoffset: 46.75;
|
||||||
|
transform: rotate(135deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 187;
|
||||||
|
transform: rotate(450deg);
|
||||||
|
}
|
||||||
|
}
|
@ -166,15 +166,15 @@ input,
|
|||||||
optgroup,
|
optgroup,
|
||||||
select,
|
select,
|
||||||
textarea {
|
textarea {
|
||||||
font-family: inherit; /* 1 */
|
font-family: inherit;
|
||||||
font-feature-settings: inherit; /* 1 */
|
font-feature-settings: inherit;
|
||||||
font-variation-settings: inherit; /* 1 */
|
font-variation-settings: inherit;
|
||||||
font-size: 100%; /* 1 */
|
font-size: 100%;
|
||||||
font-weight: inherit; /* 1 */
|
font-weight: inherit;
|
||||||
line-height: inherit; /* 1 */
|
line-height: inherit;
|
||||||
color: inherit; /* 1 */
|
color: inherit;
|
||||||
margin: 0; /* 2 */
|
margin: 1em;
|
||||||
padding: 0; /* 3 */
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
3
redis.js
3
redis.js
@ -2,6 +2,7 @@ const redis = require('redis');
|
|||||||
|
|
||||||
const redisHost = process.env.REDIS_HOST ? process.env.REDIS_HOST : 'localhost';
|
const redisHost = process.env.REDIS_HOST ? process.env.REDIS_HOST : 'localhost';
|
||||||
const redisPort = process.env.REDIS_PORT ? process.env.REDIS_PORT : 6379;
|
const redisPort = process.env.REDIS_PORT ? process.env.REDIS_PORT : 6379;
|
||||||
|
|
||||||
const url = `redis://${redisHost}:${redisPort}`;
|
const url = `redis://${redisHost}:${redisPort}`;
|
||||||
const redisClient = redis.createClient({
|
const redisClient = redis.createClient({
|
||||||
url,
|
url,
|
||||||
@ -9,7 +10,7 @@ const redisClient = redis.createClient({
|
|||||||
});
|
});
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
redisClient.connect()
|
redisClient.connect().catch(console.error)
|
||||||
})();
|
})();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
71
utils/extract-i18n.js
Normal file
71
utils/extract-i18n.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const messagePattern = /\_\_\('([^']+)'\)/g;
|
||||||
|
|
||||||
|
function getSourceFiles(dirs) {
|
||||||
|
let sourceFiles = [];
|
||||||
|
for (let dir of dirs) {
|
||||||
|
let files = fs.readdirSync(dir);
|
||||||
|
let sourceFilesDir = files.filter(file => file.endsWith('.pug') || file.endsWith('.js'));
|
||||||
|
sourceFilesDir = sourceFilesDir.map(file => path.join(dir, file));
|
||||||
|
sourceFiles = [...sourceFiles, ...sourceFilesDir];
|
||||||
|
}
|
||||||
|
return sourceFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractAllMessages(files) {
|
||||||
|
let allMessages = [];
|
||||||
|
files.forEach(file => {
|
||||||
|
const content = fs.readFileSync(file, 'utf8');
|
||||||
|
messages = extractMessages(content);
|
||||||
|
allMessages = allMessages.concat(messages);
|
||||||
|
});
|
||||||
|
return allMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMessages(content) {
|
||||||
|
let messages = [];
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while (match = messagePattern.exec(content)) {
|
||||||
|
messages.push(match[1]);
|
||||||
|
}
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dumps(dir, locales, messages) {
|
||||||
|
const uniqueMessages = [...new Set(messages)];
|
||||||
|
locales.forEach(lang => {
|
||||||
|
const file = path.join(dir, 'locales', lang + '.json');
|
||||||
|
let content = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||||
|
uniqueMessages.forEach(message => {
|
||||||
|
if (!content[message]) {
|
||||||
|
content[message] = `__${message}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fs.writeFileSync(file, JSON.stringify(content, null, 2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractor = {
|
||||||
|
run(dirs) {
|
||||||
|
const files = getSourceFiles(dirs);
|
||||||
|
const messages = extractAllMessages(files);
|
||||||
|
return messages;
|
||||||
|
},
|
||||||
|
|
||||||
|
dumps
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const [,, ...args] = process.argv
|
||||||
|
|
||||||
|
const DIRS = ["controllers", "views", "views/auth"];
|
||||||
|
|
||||||
|
const LOCALES = args.slice(args.length) || ['en'];
|
||||||
|
|
||||||
|
const keys = extractor.run(DIRS);
|
||||||
|
extractor.dumps('.', LOCALES, keys);
|
2
utils/translate.sh
Normal file
2
utils/translate.sh
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
node ./utils/extract-i18n.js views fr de es
|
@ -1,8 +1,8 @@
|
|||||||
extends layout.pug
|
extends layout.pug
|
||||||
|
|
||||||
block content
|
block content
|
||||||
p #{ __("SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.") }
|
p #{ __('SoundBirder is an open-source web application to learn bird song identification. It is based on bird records from Xeno-Canto and data from eBird.') }
|
||||||
|
|
||||||
h2 #{ __("Author") }
|
h2 #{ __('Author') }
|
||||||
p #{ __("The project is made with ♥ by Samuel ORTION.") }
|
p #{ __('The project is made with ♥ by Samuel Ortion.') }
|
||||||
|
|
||||||
|
@ -5,18 +5,26 @@
|
|||||||
i(data-feather="map-pin")
|
i(data-feather="map-pin")
|
||||||
button.button.start-button
|
button.button.start-button
|
||||||
i(data-feather="play")
|
i(data-feather="play")
|
||||||
.game-quizz-step.none
|
.game-quizz.none
|
||||||
ul.proposals
|
.game-loading
|
||||||
audio(controls).none
|
<svg class="spinner" width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
|
||||||
.game-results-step.none
|
<circle class="path" fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
|
||||||
p.message
|
</svg>
|
||||||
.species.answer
|
.game-quizz-step.none
|
||||||
p #{ __('It was a') }
|
ul.proposals
|
||||||
span.com
|
audio(controls).none
|
||||||
span.sci
|
.game-results-step.none
|
||||||
|
p.message
|
||||||
|
.species.answer
|
||||||
|
p #{ __('It was a') }
|
||||||
|
span.com
|
||||||
|
span.sci
|
||||||
button.button.restart-button
|
button.button.restart-button
|
||||||
i(data-feather="repeat")
|
i(data-feather="repeat")
|
||||||
|
button.button.map-button
|
||||||
|
i(data-feather="map")
|
||||||
link(rel="stylesheet" href="/dist/leaflet/leaflet.css")
|
link(rel="stylesheet" href="/dist/leaflet/leaflet.css")
|
||||||
|
link(rel="stylesheet" href="/stylesheets/spinner.css")
|
||||||
script(src="/dist/leaflet/leaflet.js")
|
script(src="/dist/leaflet/leaflet.js")
|
||||||
script(src="/dist/axios/axios.min.js")
|
script(src="/dist/axios/axios.min.js")
|
||||||
script(src="/javascripts/game.js" type="module")
|
script(src="/javascripts/game.js" type="module")
|
@ -14,17 +14,19 @@ html
|
|||||||
- var i18n_prefix = locale ? '/' + locale : ''
|
- var i18n_prefix = locale ? '/' + locale : ''
|
||||||
ul.flex.flex-row.text-center.justify-evenly
|
ul.flex.flex-row.text-center.justify-evenly
|
||||||
li
|
li
|
||||||
a(href=`${i18n_prefix}/`) #{ __("Game") }
|
a(href=`${i18n_prefix}/`) #{ __('Game') }
|
||||||
li
|
li
|
||||||
a(href=`${i18n_prefix}/about`) #{ __("About") }
|
a(href=`${i18n_prefix}/about`) #{ __('About') }
|
||||||
header
|
header
|
||||||
h1= title
|
h1= title
|
||||||
main
|
main
|
||||||
block content
|
block content
|
||||||
footer.w-full.bg-black.text-white.text-center.p-5
|
footer.w-full.bg-black.text-white.text-center.p-5
|
||||||
.description
|
.description
|
||||||
.copyright Copyright © 2022 -
|
.copyright
|
||||||
|
a(href="https://fsf.org/") 🄯
|
||||||
|
| 2022 -
|
||||||
span.author
|
span.author
|
||||||
a(href="https://samuel.ortion.fr" class="link") Samuel ORTION
|
a(href="https://samuel.ortion.fr" class="link") Samuel Ortion
|
||||||
script(src="/javascripts/app.js" type="module")
|
script(src="/javascripts/app.js" type="module")
|
||||||
script(src="/dist/feather/feather.min.js")
|
script(src="/dist/feather/feather.min.js")
|
Loading…
Reference in New Issue
Block a user