add language selection etting

This commit is contained in:
HF 2021-02-02 21:07:26 +01:00
parent 2e63a002bf
commit 0791e0a173
15 changed files with 198 additions and 63 deletions

25
package-lock.json generated
View File

@ -3066,18 +3066,9 @@
} }
}, },
"cookie": { "cookie": {
"version": "0.4.0", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
},
"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": { "cookie-signature": {
"version": "1.0.6", "version": "1.0.6",
@ -4501,6 +4492,11 @@
"vary": "~1.1.2" "vary": "~1.1.2"
}, },
"dependencies": { "dependencies": {
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"debug": { "debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@ -4536,6 +4532,11 @@
"uid-safe": "~2.1.5" "uid-safe": "~2.1.5"
}, },
"dependencies": { "dependencies": {
"cookie": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
"integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="
},
"debug": { "debug": {
"version": "2.6.9", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",

View File

@ -33,7 +33,7 @@
"bufferutil": "^4.0.3", "bufferutil": "^4.0.3",
"compression": "^1.7.3", "compression": "^1.7.3",
"connect-redis": "^5.0.0", "connect-redis": "^5.0.0",
"cookie-parser": "^1.4.5", "cookie": "^0.4.1",
"core-js": "^3.8.3", "core-js": "^3.8.3",
"cors": "^2.8.4", "cors": "^2.8.4",
"etag": "^1.8.1", "etag": "^1.8.1",

View File

@ -41,7 +41,7 @@ function ChatMessage({
<img <img
alt="" alt=""
title={country} title={country}
src={`${window.assetserver}/cf/${country}.gif`} src={`${window.ssv.assetserver}/cf/${country}.gif`}
onError={(e) => { onError={(e) => {
e.target.onerror = null; e.target.onerror = null;
e.target.src = './cf/xx.gif'; e.target.src = './cf/xx.gif';

View File

@ -0,0 +1,79 @@
/*
* Language selection
* language is set with cookies and requires a refresh.
* Current language is set under window.lang by the server
* Available languages under window.langSel
* [['hz', 'am'], ['de', 'de'], ...]
* [languageCode, countryCode (for flag)]
* @flow
*/
import React, { useState, useEffect } from 'react';
import { t } from 'ttag';
import { MONTH } from '../core/constants';
function LanguageSelect() {
if (!navigator.cookieEnabled) {
return null;
}
const { lang, langs } = window.ssv;
const [langSel, setLangSel] = useState(lang);
const [ccSel, setCCSel] = useState('xx');
useEffect(() => {
for (let i = 0; i < langs.length; i += 1) {
const [lc, cc] = langs[i];
if (lc === langSel) {
setCCSel(cc);
return;
}
}
}, [langSel]);
return (
<div style={{ textAlign: 'right' }}>
<span>
<select
onChange={(e) => {
const sel = e.target;
setLangSel(sel.options[sel.selectedIndex].value);
}}
>
{
langs.map(([l]) => (
<option
selected={l === langSel}
value={l}
>
{l.toUpperCase()}
</option>
))
}
</select>
</span>&nbsp;&nbsp;
<span>
<img
style={{ height: '1em', imageRendering: 'crisp-edges' }}
alt=""
src={`${window.ssv.assetserver}/cf/${ccSel}.gif`}
/>
</span>
<button
type="submit"
style={{ display: 'block', margin: 5 }}
onClick={() => {
/* set with selected language */
const d = new Date();
d.setTime(d.getTime() + 24 * MONTH);
document.cookie = `lang=${langSel};expires=${d.toUTCString()};path=/`;
window.location.reload();
}}
>
{t`Save`}
</button>
</div>
);
}
export default LanguageSelect;

View File

@ -7,6 +7,7 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { c, t } from 'ttag'; import { c, t } from 'ttag';
import LanguageSelect from './LanguageSelect';
import MdToggleButtonHover from './MdToggleButtonHover'; import MdToggleButtonHover from './MdToggleButtonHover';
import { import {
toggleGrid, toggleGrid,
@ -49,11 +50,12 @@ const rowStyles = {
}; };
const SettingsItemSelect = ({ const SettingsItemSelect = ({
title, description, values, selected, onSelect, title, description, values, selected, onSelect, icon,
}) => ( }) => (
<div style={itemStyles}> <div style={itemStyles}>
<div style={rowStyles}> <div style={rowStyles}>
<h3 style={titleStyles} className="modaltitle">{title}</h3> <h3 style={titleStyles} className="modaltitle">{title}</h3>
{(icon) && <img alt="" src={icon} />}
<select <select
onChange={(e) => { onChange={(e) => {
const sel = e.target; const sel = e.target;
@ -173,25 +175,34 @@ function SettingsModal({
value={isLightGrid} value={isLightGrid}
onToggle={onToggleLightGrid} onToggle={onToggleLightGrid}
/> />
{ (window.backupurl) {(window.ssv && window.ssv.backupurl) && (
? ( <SettingsItem
<SettingsItem title={t`Historical View`}
title={t`Historical View`} description={t`Check out past versions of the canvas.`}
description={t`Check out past versions of the canvas.`} value={isHistoricalView}
value={isHistoricalView} keyBind={c('keybinds').t`H`}
keyBind={c('keybinds').t`H`} onToggle={onToggleHistoricalView}
onToggle={onToggleHistoricalView} />
/> )}
) : null } {(window.ssv && window.ssv.availableStyles) && (
{(typeof window.availableStyles !== 'undefined') && (
<SettingsItemSelect <SettingsItemSelect
title={t`Themes`} title={t`Themes`}
description={t`How pixelplanet should look like.`} description={t`How pixelplanet should look like.`}
values={Object.keys(window.availableStyles)} values={Object.keys(window.ssv.availableStyles)}
selected={selectedStyle} selected={selectedStyle}
onSelect={onSelectStyle} onSelect={onSelectStyle}
/> />
)} )}
{(window.ssv && navigator.cookieEnabled && window.ssv.langs) && (
<div style={itemStyles}>
<div style={rowStyles}>
<h3 style={titleStyles} className="modaltitle">
{t`Select Language`}
</h3>
<LanguageSelect />
</div>
</div>
)}
</p> </p>
); );
} }

View File

@ -9,7 +9,7 @@ import { connect } from 'react-redux';
import type { State } from '../reducers'; import type { State } from '../reducers';
function Style({ style }) { function Style({ style }) {
const cssUri = window.availableStyles[style]; const cssUri = window.ssv.availableStyles[style];
return (style === 'default') ? null return (style === 'default') ? null
: (<link rel="stylesheet" type="text/css" href={cssUri} />); : (<link rel="stylesheet" type="text/css" href={cssUri} />);
} }

View File

@ -45,7 +45,7 @@ const LogInArea = ({ register, forgotPassword, me }) => (
<img <img
style={logoStyle} style={logoStyle}
width={32} width={32}
src={`${window.assetserver}/discordlogo.svg`} src={`${window.ssv.assetserver}/discordlogo.svg`}
alt="Discord" alt="Discord"
/> />
</a> </a>
@ -53,7 +53,7 @@ const LogInArea = ({ register, forgotPassword, me }) => (
<img <img
style={logoStyle} style={logoStyle}
width={32} width={32}
src={`${window.assetserver}/googlelogo.svg`} src={`${window.ssv.assetserver}/googlelogo.svg`}
alt="Google" alt="Google"
/> />
</a> </a>
@ -61,7 +61,7 @@ const LogInArea = ({ register, forgotPassword, me }) => (
<img <img
style={logoStyle} style={logoStyle}
width={32} width={32}
src={`${window.assetserver}/facebooklogo.svg`} src={`${window.ssv.assetserver}/facebooklogo.svg`}
alt="Facebook" alt="Facebook"
/> />
</a> </a>
@ -69,7 +69,7 @@ const LogInArea = ({ register, forgotPassword, me }) => (
<img <img
style={logoStyle} style={logoStyle}
width={32} width={32}
src={`${window.assetserver}/vklogo.svg`} src={`${window.ssv.assetserver}/vklogo.svg`}
alt="VK" alt="VK"
/> />
</a> </a>
@ -77,7 +77,7 @@ const LogInArea = ({ register, forgotPassword, me }) => (
<img <img
style={logoStyle} style={logoStyle}
width={32} width={32}
src={`${window.assetserver}/redditlogo.svg`} src={`${window.ssv.assetserver}/redditlogo.svg`}
alt="Reddit" alt="Reddit"
/> />
</a> </a>

View File

@ -3,6 +3,8 @@
* @flow * @flow
*/ */
import { TTag } from 'ttag'; import { TTag } from 'ttag';
import cookie from 'cookie';
import { languageFromLocalisation } from '../utils/location'; import { languageFromLocalisation } from '../utils/location';
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
@ -29,9 +31,15 @@ export function getTTag(lang) {
return ttags[lang] || ttags.default; return ttags[lang] || ttags.default;
} }
/*
* express middleware for getting language
* It checks the lang cookie, and if not present,
* the Accept-Lanuage header
*/
export function expressTTag(req, res, next) { export function expressTTag(req, res, next) {
const language = req.headers['accept-language']; const cookies = cookie.parse(req.headers.cookie || '');
req.lang = (language) ? languageFromLocalisation(language) : 'default'; const language = cookies.lang || req.headers['accept-language'];
req.lang = languageFromLocalisation(language);
req.ttag = getTTag(req.lang); req.ttag = getTTag(req.lang);
next(); next();
} }

View File

@ -44,7 +44,7 @@ export type CanvasState = {
* check if we got coords from index.html * check if we got coords from index.html
*/ */
function getGivenCoords() { function getGivenCoords() {
// if (window.coordx && window.coordy) return [window.coordx, window.coordy]; // if (window.ssv.coordx && window.ssv.coordy) return [window.ssv.coordx, window.ssv.coordy];
return [0, 0, 0]; return [0, 0, 0];
} }

View File

@ -13,7 +13,6 @@ import type { Request, Response } from 'express';
import redis from '../data/redis'; import redis from '../data/redis';
import logger from '../core/logger'; import logger from '../core/logger';
import getPasswordResetHtml from '../ssr-components/PasswordReset'; import getPasswordResetHtml from '../ssr-components/PasswordReset';
import { expressTTag } from '../core/ttag';
import { MINUTE } from '../core/constants'; import { MINUTE } from '../core/constants';
import mailProvider from '../core/mail'; import mailProvider from '../core/mail';
@ -42,12 +41,6 @@ router.use('/',
router.use(bodyParser.urlencoded({ extended: true })); router.use(bodyParser.urlencoded({ extended: true }));
/*
* use translation
*/
router.use(expressTTag);
/* /*
* Check for POST parameters, * Check for POST parameters,
* if invalid password is given, ignore it and go to next * if invalid password is given, ignore it and go to next

View File

@ -8,7 +8,8 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom/server'; import ReactDOM from 'react-dom/server';
import { getTTag } from '../core/ttag'; import { langCodeToCC } from '../utils/location';
import ttags, { getTTag } from '../core/ttag';
import Html from './Html'; import Html from './Html';
/* this one is set by webpack */ /* this one is set by webpack */
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
@ -18,11 +19,25 @@ import styleassets from './styleassets.json';
import { ASSET_SERVER, BACKUP_URL } from '../core/config'; import { ASSET_SERVER, BACKUP_URL } from '../core/config';
// eslint-disable-next-line max-len /*
let code = `window.assetserver="${ASSET_SERVER}";window.availableStyles=JSON.parse('${JSON.stringify(styleassets)}');`; * generate language list
*/
const langs = Object.keys(ttags)
.map((l) => (l === 'default' ? 'en' : l))
.map((l) => [l, langCodeToCC(l)]);
/*
* values that we pass to client scripts
*/
const ssv = {
assetserver: ASSET_SERVER,
availableStyles: styleassets,
langs,
};
if (BACKUP_URL) { if (BACKUP_URL) {
code += `window.backupurl="${BACKUP_URL}";`; ssv.backupurl = BACKUP_URL;
} }
const defaultScripts = assets.client.js.map( const defaultScripts = assets.client.js.map(
(s) => ASSET_SERVER + s, (s) => ASSET_SERVER + s,
); );
@ -34,15 +49,19 @@ const css = [
]; ];
/* /*
* generates string with html of main page * Generates string with html of main page
* including setting global variables for countryCoords
* and assetserver
* @param countryCoords Cell with coordinates of client country * @param countryCoords Cell with coordinates of client country
* @param lang language code * @param lang language code
* @return html of mainpage * @return html of mainpage
*/ */
function generateMainPage(countryCoords: Cell, lang: string): string { function generateMainPage(countryCoords: Cell, lang: string): string {
const [x, y] = countryCoords; const [x, y] = countryCoords;
const ssvR = {
...ssv,
coordx: x,
coordy: y,
lang: lang === 'default' ? 'en' : lang,
};
const scripts = (assets[`client-${lang}`]) const scripts = (assets[`client-${lang}`])
? assets[`client-${lang}`].js.map((s) => ASSET_SERVER + s) ? assets[`client-${lang}`].js.map((s) => ASSET_SERVER + s)
: defaultScripts; : defaultScripts;
@ -55,7 +74,8 @@ function generateMainPage(countryCoords: Cell, lang: string): string {
description={t`Place color pixels on an map styled canvas with other players online`} description={t`Place color pixels on an map styled canvas with other players online`}
scripts={scripts} scripts={scripts}
css={css} css={css}
code={`${code}window.coordx=${x};window.coordy=${y};`} // eslint-disable-next-line max-len
code={`window.ssv=JSON.parse('${JSON.stringify(ssvR)}');`}
useCaptcha useCaptcha
/>, />,
); );

View File

@ -236,7 +236,7 @@ class ChunkLoader {
) { ) {
const { canvasId, canvasMaxTiledZoom } = this; const { canvasId, canvasMaxTiledZoom } = this;
const center = [canvasMaxTiledZoom, cx, cy]; const center = [canvasMaxTiledZoom, cx, cy];
let url = `${window.backupurl}/${historicalDate}/`; let url = `${window.ssv.backupurl}/${historicalDate}/`;
if (historicalTime) { if (historicalTime) {
// incremential tiles // incremential tiles
url += `${canvasId}/${historicalTime}/${cx}/${cy}.png`; url += `${canvasId}/${historicalTime}/${cx}/${cy}.png`;

View File

@ -3,7 +3,7 @@
*/ */
export default function setStyle(style) { export default function setStyle(style) {
const cssUri = window.availableStyles[style]; const cssUri = window.ssv.availableStyles[style];
const domStyle = document.getElementById('globcss'); const domStyle = document.getElementById('globcss');
const curUri = domStyle.getAttribute('href'); const curUri = domStyle.getAttribute('href');
if (curUri !== cssUri) { if (curUri !== cssUri) {

View File

@ -26,6 +26,9 @@ export function ccToCoords(cc: string) {
* @return language code * @return language code
*/ */
export function languageFromLocalisation(localisation) { export function languageFromLocalisation(localisation) {
if (!localisation) {
return 'default';
}
let lang = localisation; let lang = localisation;
let i = lang.indexOf('-'); let i = lang.indexOf('-');
if (i !== -1) { if (i !== -1) {
@ -42,7 +45,27 @@ export function languageFromLocalisation(localisation) {
if (lang === 'en') { if (lang === 'en') {
lang = 'default'; lang = 'default';
} }
return lang; return lang.toLowerCase();
}
/*
* get country code to language code for displaying flags
* to languages
* @param lang 2-char lang code
* @return 2-char country code
*/
const lang2CC = {
en: 'gb',
de: 'de',
dz: 'bt',
hy: 'am',
uk: 'ua',
ru: 'ru',
fr: 'fr',
es: 'es',
};
export function langCodeToCC(lang: string) {
return lang2CC[lang] || lang;
} }
export default ccToCoords; export default ccToCoords;

View File

@ -15,6 +15,7 @@ import logger from './core/logger';
import rankings from './core/ranking'; import rankings from './core/ranking';
import models from './data/models'; import models from './data/models';
import chatProvider from './core/ChatProvider'; import chatProvider from './core/ChatProvider';
import { expressTTag } from './core/ttag';
import SocketServer from './socket/SocketServer'; import SocketServer from './socket/SocketServer';
import APISocketServer from './socket/APISocketServer'; import APISocketServer from './socket/APISocketServer';
@ -32,7 +33,7 @@ import generateMainPage from './ssr-components/Main';
import { SECOND, MONTH } from './core/constants'; import { SECOND, MONTH } from './core/constants';
import { PORT, DISCORD_INVITE, GUILDED_INVITE } from './core/config'; import { PORT, DISCORD_INVITE, GUILDED_INVITE } from './core/config';
import { ccToCoords, languageFromLocalisation } from './utils/location'; import { ccToCoords } from './utils/location';
import { startAllCanvasLoops } from './core/tileserver'; import { startAllCanvasLoops } from './core/tileserver';
startAllCanvasLoops(); startAllCanvasLoops();
@ -125,12 +126,16 @@ app.use('/guilded', (req, res) => {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
app.get('/chunks/:c([0-9]+)/:x([0-9]+)/:y([0-9]+)(/)?:z([0-9]+)?.bmp', chunks); app.get('/chunks/:c([0-9]+)/:x([0-9]+)/:y([0-9]+)(/)?:z([0-9]+)?.bmp', chunks);
// //
// Admintools // Admintools
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
app.use('/admintools', admintools); app.use('/admintools', admintools);
/*
* decide which language to use
*/
app.use(expressTTag);
// //
// Password Reset Link // Password Reset Link
@ -157,10 +162,7 @@ app.get('/globe', async (req, res) => {
return; return;
} }
const language = req.headers['accept-language']; res.status(200).send(generateGlobePage(req.lang));
const lang = (language) ? languageFromLocalisation(language) : 'en';
res.status(200).send(generateGlobePage(lang));
}); });
@ -186,12 +188,10 @@ app.get('/', async (req, res) => {
// get start coordinates based on cloudflare header country // get start coordinates based on cloudflare header country
const country = req.headers['cf-ipcountry']; const country = req.headers['cf-ipcountry'];
const language = req.headers['accept-language'];
const lang = (language) ? languageFromLocalisation(language) : 'en';
const countryCoords = (country) ? ccToCoords(country) : [0, 0]; const countryCoords = (country) ? ccToCoords(country) : [0, 0];
res.status(200).send(generateMainPage(countryCoords, lang)); res.status(200).send(generateMainPage(countryCoords, req.lang));
}); });