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

View File

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

View File

@ -41,7 +41,7 @@ function ChatMessage({
<img
alt=""
title={country}
src={`${window.assetserver}/cf/${country}.gif`}
src={`${window.ssv.assetserver}/cf/${country}.gif`}
onError={(e) => {
e.target.onerror = null;
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 { c, t } from 'ttag';
import LanguageSelect from './LanguageSelect';
import MdToggleButtonHover from './MdToggleButtonHover';
import {
toggleGrid,
@ -49,11 +50,12 @@ const rowStyles = {
};
const SettingsItemSelect = ({
title, description, values, selected, onSelect,
title, description, values, selected, onSelect, icon,
}) => (
<div style={itemStyles}>
<div style={rowStyles}>
<h3 style={titleStyles} className="modaltitle">{title}</h3>
{(icon) && <img alt="" src={icon} />}
<select
onChange={(e) => {
const sel = e.target;
@ -173,25 +175,34 @@ function SettingsModal({
value={isLightGrid}
onToggle={onToggleLightGrid}
/>
{ (window.backupurl)
? (
<SettingsItem
title={t`Historical View`}
description={t`Check out past versions of the canvas.`}
value={isHistoricalView}
keyBind={c('keybinds').t`H`}
onToggle={onToggleHistoricalView}
/>
) : null }
{(typeof window.availableStyles !== 'undefined') && (
{(window.ssv && window.ssv.backupurl) && (
<SettingsItem
title={t`Historical View`}
description={t`Check out past versions of the canvas.`}
value={isHistoricalView}
keyBind={c('keybinds').t`H`}
onToggle={onToggleHistoricalView}
/>
)}
{(window.ssv && window.ssv.availableStyles) && (
<SettingsItemSelect
title={t`Themes`}
description={t`How pixelplanet should look like.`}
values={Object.keys(window.availableStyles)}
values={Object.keys(window.ssv.availableStyles)}
selected={selectedStyle}
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>
);
}

View File

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

View File

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

View File

@ -3,6 +3,8 @@
* @flow
*/
import { TTag } from 'ttag';
import cookie from 'cookie';
import { languageFromLocalisation } from '../utils/location';
// eslint-disable-next-line max-len
@ -29,9 +31,15 @@ export function getTTag(lang) {
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) {
const language = req.headers['accept-language'];
req.lang = (language) ? languageFromLocalisation(language) : 'default';
const cookies = cookie.parse(req.headers.cookie || '');
const language = cookies.lang || req.headers['accept-language'];
req.lang = languageFromLocalisation(language);
req.ttag = getTTag(req.lang);
next();
}

View File

@ -44,7 +44,7 @@ export type CanvasState = {
* check if we got coords from index.html
*/
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];
}

View File

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

View File

@ -8,7 +8,8 @@
import React from 'react';
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';
/* this one is set by webpack */
// eslint-disable-next-line import/no-unresolved
@ -18,11 +19,25 @@ import styleassets from './styleassets.json';
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) {
code += `window.backupurl="${BACKUP_URL}";`;
ssv.backupurl = BACKUP_URL;
}
const defaultScripts = assets.client.js.map(
(s) => ASSET_SERVER + s,
);
@ -34,15 +49,19 @@ const css = [
];
/*
* generates string with html of main page
* including setting global variables for countryCoords
* and assetserver
* Generates string with html of main page
* @param countryCoords Cell with coordinates of client country
* @param lang language code
* @return html of mainpage
*/
function generateMainPage(countryCoords: Cell, lang: string): string {
const [x, y] = countryCoords;
const ssvR = {
...ssv,
coordx: x,
coordy: y,
lang: lang === 'default' ? 'en' : lang,
};
const scripts = (assets[`client-${lang}`])
? assets[`client-${lang}`].js.map((s) => ASSET_SERVER + s)
: 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`}
scripts={scripts}
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
/>,
);

View File

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

View File

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

View File

@ -26,6 +26,9 @@ export function ccToCoords(cc: string) {
* @return language code
*/
export function languageFromLocalisation(localisation) {
if (!localisation) {
return 'default';
}
let lang = localisation;
let i = lang.indexOf('-');
if (i !== -1) {
@ -42,7 +45,27 @@ export function languageFromLocalisation(localisation) {
if (lang === 'en') {
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;

View File

@ -15,6 +15,7 @@ import logger from './core/logger';
import rankings from './core/ranking';
import models from './data/models';
import chatProvider from './core/ChatProvider';
import { expressTTag } from './core/ttag';
import SocketServer from './socket/SocketServer';
import APISocketServer from './socket/APISocketServer';
@ -32,7 +33,7 @@ import generateMainPage from './ssr-components/Main';
import { SECOND, MONTH } from './core/constants';
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';
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);
//
// Admintools
// -----------------------------------------------------------------------------
app.use('/admintools', admintools);
/*
* decide which language to use
*/
app.use(expressTTag);
//
// Password Reset Link
@ -157,10 +162,7 @@ app.get('/globe', async (req, res) => {
return;
}
const language = req.headers['accept-language'];
const lang = (language) ? languageFromLocalisation(language) : 'en';
res.status(200).send(generateGlobePage(lang));
res.status(200).send(generateGlobePage(req.lang));
});
@ -186,12 +188,10 @@ app.get('/', async (req, res) => {
// get start coordinates based on cloudflare header country
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];
res.status(200).send(generateMainPage(countryCoords, lang));
res.status(200).send(generateMainPage(countryCoords, req.lang));
});