Merge pull request #46 from montylion/dev

Add auto locale detection and custom locale selection (preference saved to cookie)
This commit is contained in:
mrjvs
2021-08-22 22:15:46 +02:00
committed by GitHub
11 changed files with 305 additions and 6 deletions

33
package-lock.json generated
View File

@@ -10,8 +10,10 @@
"license": "ISC",
"dependencies": {
"colors": "^1.4.0",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"express-handlebars": "^4.0.4",
"express-locale": "^2.0.0",
"fs-extra": "^9.1.0",
"ioredis": "^4.26.0",
"morgan": "^1.10.0",
@@ -156,6 +158,18 @@
"node": ">= 0.6"
}
},
"node_modules/cookie-parser": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
"integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
"dependencies": {
"cookie": "0.4.0",
"cookie-signature": "1.0.6"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -287,6 +301,11 @@
"node": ">=0.10"
}
},
"node_modules/express-locale": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/express-locale/-/express-locale-2.0.0.tgz",
"integrity": "sha512-SUwsgOTV8lplAkgyfKSwKH3ZVSKUrAa+b48acPGXpwWX211WZ/BYWX2WcZO57+xLOqPYb6rSNpWCFnhGrt0moA=="
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
@@ -1114,6 +1133,15 @@
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"cookie-parser": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.5.tgz",
"integrity": "sha512-f13bPUj/gG/5mDr+xLmSxxDsB9DQiTIfhJS/sqjrmfAWiAN+x2O4i/XguTL9yDZ+/IFDanJ+5x7hC4CXT9Tdzw==",
"requires": {
"cookie": "0.4.0",
"cookie-signature": "1.0.6"
}
},
"cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@@ -1224,6 +1252,11 @@
"promise": "^8.1.0"
}
},
"express-locale": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/express-locale/-/express-locale-2.0.0.tgz",
"integrity": "sha512-SUwsgOTV8lplAkgyfKSwKH3ZVSKUrAa+b48acPGXpwWX211WZ/BYWX2WcZO57+xLOqPYb6rSNpWCFnhGrt0moA=="
},
"finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",

View File

@@ -18,8 +18,10 @@
"homepage": "https://github.com/PretendoNetwork/website#readme",
"dependencies": {
"colors": "^1.4.0",
"cookie-parser": "^1.4.5",
"express": "^4.17.1",
"express-handlebars": "^4.0.4",
"express-locale": "^2.0.0",
"fs-extra": "^9.1.0",
"ioredis": "^4.26.0",
"morgan": "^1.10.0",

View File

@@ -0,0 +1,106 @@
.select-box {
display: flex;
width: 200px;
flex-direction: column;
position: relative;
user-select: none;
}
.select-box > * {
box-sizing: border-box;
}
.select-box .options-container {
max-height: 0;
width: calc(100% - 12px);
opacity: 0;
transition: all 0.4s;
overflow: hidden;
border-radius: 5px;
background-color: var(--btn-secondary);
order: 1;
position: absolute;
top: 50px;
}
.selected-locale {
margin-bottom: 8px;
position: relative;
width: 188px;
height: 45px;
border-radius: 5px;
display: flex;
align-items: center;
background-color: var(--btn-secondary);
color: white;
order: 0;
}
.selected-locale::after {
content: "";
width: 1.2rem;
height: 1.2rem;
background: url("/assets/icons/down-arrow.svg");
position: absolute;
right: 15px;
top: 50%;
transition: transform 150ms;
transform: translateY(-50%);
background-size: contain;
background-position: center;
}
.select-box .option .item {
color: #afb5dd;
}
.select-box .lang {
width: 1.3rem;
height: 1rem;
margin-right: .2rem;
display: inline-block;
}
.select-box .options-container.active {
max-height: 240px;
opacity: 1;
overflow-y: auto;
}
.select-box .options-container.active + .selected-locale::after {
transform: translateY(-50%) rotateX(180deg);
}
.select-box .options-container::-webkit-scrollbar {
width: 8px;
background: #0d141f;
background: #81878f;
background: #f1f2f3;
border-radius: 0 5px 5px 0;
}
.select-box .options-container::-webkit-scrollbar-thumb {
background: #525861;
background: #81878f;
border-radius: 0 5px 5px 0;
}
.select-box .option,
.selected-locale {
padding: 12px 15px;
cursor: pointer;
}
.select-box .option:hover {
background: #2f345b;
}
.select-box .option:hover .item {
color: white;
}
.select-box label {
cursor: pointer;
}
.select-box .option .radio {
display: none;
}

View File

@@ -4,6 +4,7 @@
--text: white;
--text-secondary: #A1A8D9;
--btn: #673DB6;
--btn-secondary: #333960;
--theme: var(--btn);
--text-secondary-2: #8990C1;
}
@@ -80,6 +81,10 @@ header nav a {
text-decoration: none;
}
.locale-dropdown {
margin-left: auto;
z-index: 2;
}
/* Misc */
.purple-circle {
@@ -114,7 +119,7 @@ header nav a {
left: 50%;
transform: translate(-50%, -50%);
margin-left: -40px;
z-index: 3;
z-index: 1;
}
.light-purple-circle .deco {
position: absolute;
@@ -124,6 +129,15 @@ header nav a {
z-index: -2;
}
/* Twemoji */
img.emoji {
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}
/* Title business */
.sect .title[id] {
padding-top: 100px;
@@ -217,7 +231,7 @@ header nav a {
background: var(--btn);
}
.button.secondary {
background: #333960;
background: var(--btn-secondary);
}
.button.secondary.discord svg {
margin-bottom: -1px;

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>

After

Width:  |  Height:  |  Size: 261 B

View File

@@ -0,0 +1,56 @@
function setDefaultDropdownLocale(localeString) {
const selected = document.querySelector(".selected-locale");
selected.innerHTML = document.querySelector(`label[for=${localeString}`).innerHTML
}
function localeDropdownHandler(selectedLocale) {
document.cookie = `preferredLocale=${selectedLocale};max-age=31536000`;
window.location.reload();
}
const dropdowns = document.querySelectorAll("*[data-dropdown]");
const dropdownOptions = document.querySelectorAll(
"*[data-dropdown] .options-container"
);
dropdowns.forEach((el) => {
const selected = el.querySelector(".selected-locale");
const optionsContainer = el.querySelector(".options-container");
const optionsList = el.querySelectorAll(".option");
// click dropdown element will open dropdown
selected.addEventListener("click", () => {
optionsContainer.classList.toggle("active");
});
// clicking on any option will close dropdown and change value
optionsList.forEach((option) => {
option.addEventListener("click", () => {
selected.innerHTML = option.querySelector("label").innerHTML;
optionsContainer.classList.remove("active");
const selectedLocale = option.querySelector("label").getAttribute("for");
localeDropdownHandler(selectedLocale);
});
});
});
// close all dropdowns on scroll
document.addEventListener("scroll", () => {
dropdownOptions.forEach((el) => el.classList.remove("active"));
});
// click outside of dropdown will close all dropdowns
document.addEventListener("click", (e) => {
const targetElement = e.target;
// check if target is from a dropdown
let found = false;
dropdowns.forEach((v) => {
if (v == targetElement || v.contains(targetElement)) found = true;
});
if (found) return;
// click outside of dropdowns
dropdownOptions.forEach((el) => el.classList.remove("active"));
});

View File

@@ -5,12 +5,16 @@ const router = new Router();
const { getTrelloCache } = require('../trello');
router.get('/', async (request, response) => {
const tmpLocale = util.getLocale('US', 'en');
const reqLocale = request.locale
const locale = util.getLocale(reqLocale.region, reqLocale.language);
const cache = await getTrelloCache();
response.render('home', {
layout: 'main',
locale: tmpLocale,
locale,
localeString: reqLocale.toString(),
featuredFeatureList: cache.sections[0]
});
});

View File

@@ -5,12 +5,18 @@ const router = new Router();
const { getTrelloCache } = require('../trello');
router.get('/', async (request, response) => {
const tmpLocale = util.getLocale('US', 'en');
const reqLocale = request.locale
const locale = util.getLocale(reqLocale.region, reqLocale.language);
const localeString = reqLocale.toString()
const cache = await getTrelloCache();
response.render('progress', {
layout: 'main',
locale: tmpLocale,
locale,
localeString,
progressLists: cache
});
});

View File

@@ -3,6 +3,8 @@ process.title = 'Pretendo - Website';
const express = require('express');
const handlebars = require('express-handlebars');
const morgan = require('morgan');
const expressLocale = require('express-locale');
const cookieParser = require('cookie-parser')
const logger = require('./logger');
const util = require('./util');
const config = require('../config.json');
@@ -23,6 +25,28 @@ const routers = {
progress: require('./routers/progress')
};
app.use(cookieParser())
// Locale express middleware setup
app.use(expressLocale({
"priority": ["cookie", "accept-language", "map", "default"],
cookie: {name: 'preferredLocale'},
// fallbacks for browsers that don't send a region code, but only a language
map: {
de: 'de-DE',
es: 'es-ES',
fr: 'fr-FR',
it: 'it-IT',
ko: 'ko-KR',
ru: 'ru-RU',
tr: 'tr-TR'
},
"default": "en-US"
}))
app.use('/', routers.home);
app.use('/faq', routers.faq);
app.use('/progress', routers.progress);

View File

@@ -28,11 +28,20 @@
<link rel="shortcut icon" href="/assets/icons/favicon.ico"><title>Pretendo</title>
<!-- css files -->
<link rel="stylesheet" href="/assets/css/main.css">
<link rel="stylesheet" href="/assets/css/dropdown.css" />
<!-- favicon -->
<link rel="shortcut icon" href="/assets/icons/favicon.ico">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap" rel="stylesheet">
<!-- Twemoji setup -->
<script src="https://twemoji.maxcdn.com/v/latest/twemoji.min.js" crossorigin="anonymous"></script>
<script>
window.onload = function() {
twemoji.parse(document.body, {folder: 'svg', ext: '.svg'});
}
</script>
</head>
<body>
<div class="main-body">
@@ -40,5 +49,8 @@
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.0.2/chart.min.js"></script>
<script src="/assets/js/progress-charts.js"></script>
<script src="/assets/js/locale-dropdown-handler.js"></script>
<script>setDefaultDropdownLocale("{{localeString}}")</script>
</body>
</html>

View File

@@ -30,4 +30,45 @@
<a href="/#credits">{{ locale.nav.credits }}</a>
<a href="/progress" class="keep-on-mobile">{{ locale.nav.progress }}</a>
</nav>
<div class="select-box locale-dropdown" data-dropdown>
<div class="options-container">
<div class="option">
<input type="radio" class="radio" id="en-US" name="category" />
<label for="en-US">
<div class="item"><span class="lang">🇺🇸</span>English</div>
</label>
</div>
<div class="option">
<input type="radio" class="radio" id="es-ES" name="category" />
<label for="es-ES">
<div class="item"><span class="lang">🇪🇸</span>Español</div>
</label>
</div>
<div class="option">
<input type="radio" class="radio" id="it-IT" name="category" />
<label for="it-IT">
<div class="item"><span class="lang">🇮🇹</span>Italiano</div>
</label>
</div>
<div class="option">
<input type="radio" class="radio" id="ru-RU" name="category" />
<label for="ru-RU">
<div class="item"><span class="lang">🇷🇺</span>Pусский</div>
</label>
</div>
<div class="option">
<input type="radio" class="radio" id="tr-TR" name="category" />
<label for="tr-TR">
<div class="item"><span class="lang">🇹🇷</span>Türkçe</div>
</label>
</div>
</div>
<div class="selected-locale">
<div class="item"><span class="lang"></span></div>
</div>
</div>
</header>