forked from ppfun/pixelplanet
add language selection etting
This commit is contained in:
parent
2e63a002bf
commit
0791e0a173
25
package-lock.json
generated
25
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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';
|
||||
|
|
79
src/components/LanguageSelect.jsx
Normal file
79
src/components/LanguageSelect.jsx
Normal 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>
|
||||
<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;
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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} />);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
/>,
|
||||
);
|
||||
|
|
|
@ -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`;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
18
src/web.js
18
src/web.js
|
@ -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));
|
||||
});
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user