forked from ppfun/pixelplanet
Compare commits
32 Commits
72561d9752
...
45fc4b4d9c
Author | SHA1 | Date |
---|---|---|
|
45fc4b4d9c | |
|
18d9828b2b | |
|
9c5596d86a | |
|
8d41c6533d | |
|
058290aa16 | |
|
0a4199e633 | |
|
05298604b1 | |
|
260f6a6210 | |
|
fb87a18bd7 | |
|
1304dfb4ba | |
|
eb6c76c1b6 | |
|
6ecffafd00 | |
|
d8042552d8 | |
|
3895bf292a | |
|
dddc0d0823 | |
|
b5ccda251c | |
|
ab840c6f23 | |
|
3aad00dca7 | |
|
872c3a5659 | |
|
997d887cfd | |
|
b48821e92e | |
|
a3edd2cb44 | |
|
6d3b6edd8a | |
|
22b9cf2612 | |
|
b5d48be01f | |
|
af97cf7ec8 | |
|
bcc489eff9 | |
|
6b0404b66d | |
|
d2fa61f3c6 | |
|
fe8541bcbf | |
|
8544c42e7b | |
|
878f2cd269 |
|
@ -10,6 +10,7 @@ logs
|
|||
*.log
|
||||
npm-debug.log*
|
||||
*.tmp
|
||||
records.json
|
||||
|
||||
pids
|
||||
*.pid
|
||||
|
|
18
README.md
18
README.md
|
@ -1,3 +1,5 @@
|
|||
> **Currently pixelplanet is being developed in a another private repository. This development version diverted and is incompatible with the stable version here. Until it is ready, this public repository will be frozen.**
|
||||
|
||||
# PixelPlanet.fun
|
||||
|
||||
|
||||
|
@ -7,7 +9,7 @@ Official repository of [pixelplanet.fun](http://www.pixelplanet.fun).
|
|||
|
||||
![videothumb](promotion/videothumb.gif)
|
||||
|
||||
> **TRANSLATORS NEEDED** If you want to help us translate pixelplanet.fun, look into [i18n](./i18n)
|
||||
> If you want to help us translate pixelplanet.fun, look into [i18n](./i18n)
|
||||
|
||||
To the 2nd anniversary of r/space, pixelplanet takes pixelgames to a new level. Place pixels, create pixelart and fight faction wars on pixelplanet.fun.
|
||||
Pixelplanet is a 65k x 65k large canvas that is a map of the world and can also be seen as 3d globe, you can place pixels where ever you want, build an island, take over another country with a flag or just create pixelart.
|
||||
|
@ -256,11 +258,19 @@ If v is set and true for a canvas in the canvas.json, it will be a 3D voxel canv
|
|||
|
||||
Run `npm run lint:src` to check for code errors and warnings or `npm run lint -- ./your/file.js` to check a single file.
|
||||
|
||||
[ttag](https://github.com/ttag-org/ttag/) is used for handling translations. For server-side rendering the `Accept-Language` header gets checked and the first locale used and on-the-fly translated (`src/core/ttag.js` provides the functions for it). On the client-side a seperate bundle for every language gets provided.
|
||||
The language definitions in `i18n/template.pot` and `i18n/template-ssr.pot` get updated when doing a dev build with
|
||||
Compile with source-maps and debug options (but only english language) with
|
||||
|
||||
```
|
||||
npm run build:dev
|
||||
```
|
||||
which also only builds the default local in a development environment for debugging.
|
||||
|
||||
[ttag](https://github.com/ttag-org/ttag/) is used for handling translations. For server-side rendering the `Accept-Language` header gets checked and the first locale used and on-the-fly translated (`src/core/ttag.js` provides the functions for it). On the client-side a seperate bundle for every language gets provided.
|
||||
The language definitions in `i18n/template.pot` and `i18n/template-ssr.pot` get updated when doing a full production build with all languages (`npm run build`)-
|
||||
|
||||
To build only specific languages, you can define them with the `--langs` flag:
|
||||
|
||||
```
|
||||
npm run build -- --langs de,gr
|
||||
```
|
||||
|
||||
You can use `npm run babel-node ./utils/script.js` to execute a script with local babel (path always relative to the root directory).
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
apps:
|
||||
- script : ./server.js
|
||||
name : 'ppfun-server'
|
||||
node_args: --nouse-idle-notification --expose-gc
|
||||
- script : ./server.js
|
||||
name : 'ppfun'
|
||||
node_args : --nouse-idle-notification --expose-gc
|
||||
watch : [ 'server.js' ]
|
||||
watch_delay: 5000
|
||||
env:
|
||||
PORT: 80
|
||||
PORT: 5000
|
||||
HOST: "localhost"
|
||||
REDIS_URL: 'redis://localhost:6379'
|
||||
MYSQL_HOST: "localhost"
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
#!/bin/bash
|
||||
# This hook builds pixelplanet after a push, and deploys it, it should be ron post-receive
|
||||
# If it is the master branch, it will deploy it on the life system, and other branch will get deployed to the dev-canvas (a second canvas that is running on the server)
|
||||
# This hook builds pixelplanet after a push to a development branch,
|
||||
# and starts the dev-canvas
|
||||
#
|
||||
# To set up a server to use this, you have to go through the building steps manually first.
|
||||
#
|
||||
#folder for building the canvas (the git repository will get checkout there and the canvas will get buil thtere)
|
||||
#folder for building the canvas (the git repository will get checkout there and the canvas will get built thtere)
|
||||
BUILDDIR="/home/pixelpla/pixelplanet-build"
|
||||
#folder for dev canvas
|
||||
DEVFOLDER="/home/pixelpla/pixelplanet-dev"
|
||||
#folder for production canvas
|
||||
PFOLDER="/home/pixelpla/pixelplanet"
|
||||
|
||||
should_reinstall () {
|
||||
local TMPFILE="${BUILDDIR}/package.json.${1}.tmp"
|
||||
|
@ -33,14 +31,12 @@ npm_reinstall () {
|
|||
copy () {
|
||||
local TARGETDIR="${1}"
|
||||
local REINSTALL="${2}"
|
||||
cp -r dist/*.js "${TARGETDIR}/"
|
||||
cp -r dist/workers "${TARGETDIR}/"
|
||||
cp -r "${BUILDDIR}"/dist/*.js "${TARGETDIR}/"
|
||||
cp -r "${BUILDDIR}"/dist/workers "${TARGETDIR}/"
|
||||
rm -rf "${TARGETDIR}/public/assets"
|
||||
cp -r dist/public "${TARGETDIR}/"
|
||||
cp -r dist/captchaFonts "${TARGETDIR}/"
|
||||
cp -r dist/package.json "${TARGETDIR}/"
|
||||
cp -r dist/assets.json "${TARGETDIR}/"
|
||||
cp -r dist/styleassets.json "${TARGETDIR}/"
|
||||
cp -r "${BUILDDIR}"/dist/public "${TARGETDIR}/"
|
||||
cp -r "${BUILDDIR}"/dist/captchaFonts "${TARGETDIR}/"
|
||||
cp -r "${BUILDDIR}"/dist/package.json "${TARGETDIR}/"
|
||||
mkdir -p "${TARGETDIR}/log"
|
||||
cd "${TARGETDIR}"
|
||||
[ $REINSTALL -eq 0 ] && npm_reinstall
|
||||
|
@ -53,28 +49,10 @@ do
|
|||
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git fetch --all
|
||||
cd "$BUILDDIR"
|
||||
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
|
||||
if [ "master" == "$branch" ]; then
|
||||
echo "---UPDATING REPO ON PRODUCTION SERVER---"
|
||||
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git reset --hard "origin/$branch"
|
||||
COMMITS=`git log --pretty=format:'- %s%b' $newrev ^$oldrev`
|
||||
COMMITS=`echo "$COMMITS" | sed ':a;N;$!ba;s/\n/\\\n/g'`
|
||||
echo "---BUILDING pixelplanet---"
|
||||
should_reinstall master
|
||||
DO_REINSTALL=$?
|
||||
[ $DO_REINSTALL -eq 0 ] && npm_reinstall
|
||||
npm run build
|
||||
echo "---RESTARTING CANVAS---"
|
||||
pm2 stop ppfun-server
|
||||
pm2 stop ppfun-backups
|
||||
copy "${PFOLDER}" "${DO_REINSTALL}"
|
||||
cd "$PFOLDER"
|
||||
pm2 start ecosystem-backup.yml
|
||||
else
|
||||
if [ "test" == "$branch" ] || [ "devel" == "$branch" ]; then
|
||||
echo "---UPDATING REPO ON DEV SERVER---"
|
||||
pm2 stop ppfun-server-dev
|
||||
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git reset --hard "origin/$branch"
|
||||
COMMITS=`git log --pretty=format:'- %s%b' $newrev ^$oldrev`
|
||||
COMMITS=`echo "$COMMITS" | sed ':a;N;$!ba;s/\n/\\\n/g'`
|
||||
echo "---BUILDING pixelplanet---"
|
||||
should_reinstall dev
|
||||
DO_REINSTALL=$?
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
#!/bin/bash
|
||||
# Rebuild from master branch and restart pixelplanet
|
||||
# Rebuild and Restert pixelplanet
|
||||
|
||||
#folder for building the canvas (the git repository will get checkout there and the canvas will get buil thtere)
|
||||
BUILDDIR="/home/pixelpla/pixelplanet-build"
|
||||
#folder for dev canvas
|
||||
DEVFOLDER="/home/pixelpla/pixelplanet-dev"
|
||||
#folder for shards
|
||||
SCBFOLDER="/home/pixelpla/pixelplanet-scb"
|
||||
SCCFOLDER="/home/pixelpla/pixelplanet-scc"
|
||||
SCDFOLDER="/home/pixelpla/pixelplanet-scd"
|
||||
SCEFOLDER="/home/pixelpla/pixelplanet-sce"
|
||||
SCFFOLDER="/home/pixelpla/pixelplanet-scf"
|
||||
SCGFOLDER="/home/pixelpla/pixelplanet-scg"
|
||||
SCHFOLDER="/home/pixelpla/pixelplanet-sch"
|
||||
#folder for production canvas
|
||||
PFOLDER="/home/pixelpla/pixelplanet"
|
||||
#which branch to use
|
||||
|
@ -40,23 +32,20 @@ npm_reinstall () {
|
|||
copy () {
|
||||
local TARGETDIR="${1}"
|
||||
local REINSTALL="${2}"
|
||||
cp -r dist/*.js "${TARGETDIR}/"
|
||||
cp -r dist/workers "${TARGETDIR}/"
|
||||
cp -r "${BUILDDIR}"/dist/*.js "${TARGETDIR}/"
|
||||
cp -r "${BUILDDIR}"/dist/workers "${TARGETDIR}/"
|
||||
rm -rf "${TARGETDIR}/public/assets"
|
||||
cp -r dist/public "${TARGETDIR}/"
|
||||
cp -r dist/captchaFonts "${TARGETDIR}/"
|
||||
cp -r dist/package.json "${TARGETDIR}/"
|
||||
cp -r dist/assets.json "${TARGETDIR}/"
|
||||
cp -r dist/styleassets.json "${TARGETDIR}/"
|
||||
cp -r "${BUILDDIR}"/dist/public "${TARGETDIR}/"
|
||||
cp -r "${BUILDDIR}"/dist/captchaFonts "${TARGETDIR}/"
|
||||
cp -r "${BUILDDIR}"/dist/package.json "${TARGETDIR}/"
|
||||
mkdir -p "${TARGETDIR}/log"
|
||||
cd "${TARGETDIR}"
|
||||
[ $REINSTALL -eq 0 ] && npm_reinstall
|
||||
pm2 start ecosystem.yml
|
||||
[ ${REINSTALL} -eq 0 ] && npm_reinstall
|
||||
cd -
|
||||
}
|
||||
|
||||
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git fetch --all
|
||||
cd "$BUILDDIR"
|
||||
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git fetch --all
|
||||
echo "---UPDATING REPO ON PRODUCTION SERVER---"
|
||||
GIT_WORK_TREE="$BUILDDIR" GIT_DIR="${BUILDDIR}/.git" git reset --hard "origin/${BRANCH}"
|
||||
echo "---BUILDING pixelplanet---"
|
||||
|
@ -65,22 +54,10 @@ DO_REINSTALL=$?
|
|||
[ $DO_REINSTALL -eq 0 ] && npm_reinstall
|
||||
npm run build
|
||||
echo "---RESTARTING CANVAS---"
|
||||
pm2 stop ppfun-server
|
||||
pm2 stop ppfun-backups
|
||||
pm2 stop ppfun-scb
|
||||
pm2 stop ppfun-scc
|
||||
pm2 stop ppfun-scd
|
||||
pm2 stop ppfun-sce
|
||||
pm2 stop ppfun-scf
|
||||
pm2 stop ppfun-scg
|
||||
pm2 stop ppfun-sch
|
||||
copy "${PFOLDER}" "${DO_REINSTALL}"
|
||||
copy "${SCBFOLDER}" 1
|
||||
copy "${SCCFOLDER}" 1
|
||||
copy "${SCDFOLDER}" 1
|
||||
copy "${SCEFOLDER}" 1
|
||||
copy "${SCFFOLDER}" 1
|
||||
copy "${SCGFOLDER}" 1
|
||||
copy "${SCHFOLDER}" 1
|
||||
cp dist/canvases.json ~/
|
||||
cd "$PFOLDER"
|
||||
pm2 stop ppfun-backups
|
||||
pm2 stop ecosystem.config.js
|
||||
copy "${PFOLDER}" "${DO_REINSTALL}"
|
||||
pm2 start ecosystem-backup.yml
|
||||
pm2 start ecosystem.config.js
|
||||
|
|
|
@ -6,6 +6,8 @@ Translation files can be created out of the templates [template.pot](https://git
|
|||
|
||||
All translated languages get an own chat channel that just people who use this language can access.
|
||||
|
||||
If a language code differs from the country code of a wanted flag, it can be defined in the ssr filename, like `ssr-en-gb.po` would be the english language, with the flag of Great Britain.
|
||||
|
||||
## With poedit
|
||||
|
||||
### Create new translation
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,477 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"POT-Creation-Date: \n"
|
||||
"PO-Revision-Date: \n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: ka\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.4.1\n"
|
||||
|
||||
#: src/core/ChatProvider.js:434
|
||||
msgid "You can not send chat messages with proxy"
|
||||
msgstr "ვერ გაგზავნი მესიჯებს პროქსით"
|
||||
|
||||
#: src/core/ChatProvider.js:436
|
||||
msgid "Your country is temporary muted from this chat channel"
|
||||
msgstr "თქვენი ქვეყანა დამუდებული არის ამ ჩატიდან"
|
||||
|
||||
#: src/core/ChatProvider.js:439
|
||||
msgid "You are permanently muted, join our guilded to apppeal the mute"
|
||||
msgstr ""
|
||||
"თქვენ სამუდამოდ დადუმებული ხართ, შემოუერთდით ჩვენს გილდიას, რათა გაასაჩივროთ "
|
||||
"მუნჯი"
|
||||
|
||||
#: src/core/ChatProvider.js:441
|
||||
msgid "You are banned"
|
||||
msgstr "აკრძალული ხარ. თქვენ ფიქრობთ, რომ ეს გაუმართლებელია? შეამოწმეთ"
|
||||
|
||||
#: src/core/ChatProvider.js:443
|
||||
msgid "Your Internet Provider is banned"
|
||||
msgstr "თქვენს ინტერნეტ პროვაიდერს აეკრძალა ამ თამაშის თამაში"
|
||||
|
||||
#: src/core/ChatProvider.js:448
|
||||
#, javascript-format
|
||||
msgid "You are muted for another ${ timeMin } minutes"
|
||||
msgstr "თქვენ დადუმებული ხართ კიდევ $ {timeMin } წუთით"
|
||||
|
||||
#: src/core/ChatProvider.js:450
|
||||
msgid "You are muted for another ${ ttl } seconds"
|
||||
msgstr "თქვენ დადუმებული ხართ კიდევ ${ ttl } წამით"
|
||||
|
||||
#: src/core/ChatProvider.js:467
|
||||
#, javascript-format
|
||||
msgid "You are sending messages too fast, you have to wait ${ waitTime }s :("
|
||||
msgstr ""
|
||||
"თქვენ ძალიან სწრაფად აგზავნით შეტყობინებებს, უნდა დაელოდოთ ${ waitTime }s :("
|
||||
|
||||
#: src/core/ChatProvider.js:471
|
||||
msgid "You don't have access to this channel"
|
||||
msgstr "თქვენ არ გაქვთ წვდომა ამ არხზე"
|
||||
|
||||
#: src/core/ChatProvider.js:488
|
||||
msgid "Your mail has to be verified in order to chat"
|
||||
msgstr "თქვენი ფოსტა უნდა დადასტურდეს ჩეთისთვის"
|
||||
|
||||
#: src/core/ChatProvider.js:498
|
||||
msgid "You can't send a message this long :("
|
||||
msgstr "ამდენ ხანს მესიჯს ვერ გაუგზავნი :("
|
||||
|
||||
#: src/core/ChatProvider.js:502
|
||||
msgid "Please use int channel"
|
||||
msgstr "გთხოვთ გამოიყენოთ int არხი"
|
||||
|
||||
#: src/core/ChatProvider.js:510
|
||||
msgid "Stop flooding."
|
||||
msgstr "შეაჩერე ჩატის გასპამვა."
|
||||
|
||||
#: src/routes/reset_password.js:39
|
||||
msgid "You sent an empty password or invalid data :("
|
||||
msgstr "თქვენ გაგზავნეთ ცარიელი პაროლი ან არასწორი მონაცემები :("
|
||||
|
||||
#: src/routes/reset_password.js:51
|
||||
msgid "This password-reset link isn't valid anymore :("
|
||||
msgstr "ეს პაროლის აღდგენის ბმული აღარ არის მოქმედი :("
|
||||
|
||||
#: src/routes/reset_password.js:62
|
||||
msgid "Your passwords do not match :("
|
||||
msgstr "Თქვენი პაროლები არ ემთხვევა :("
|
||||
|
||||
#: src/routes/reset_password.js:77
|
||||
msgid "User doesn't exist in our database :("
|
||||
msgstr "მომხმარებელი არ არსებობს ჩვენს მონაცემთა ბაზაში :("
|
||||
|
||||
#: src/routes/reset_password.js:89
|
||||
msgid "Passowrd successfully changed."
|
||||
msgstr "პაროლი წარმატებით შეიცვალა."
|
||||
|
||||
#: src/routes/reset_password.js:108
|
||||
msgid "Invalid url :( Please check your mail again."
|
||||
msgstr "არასწორი url :( გთხოვთ, ხელახლა შეამოწმოთ თქვენი ფოსტა."
|
||||
|
||||
#: src/ssr/Globe.jsx:32
|
||||
msgid "PixelPlanet.Fun 3DGlobe"
|
||||
msgstr "PixelPlanet.Fun 3DGlobe"
|
||||
|
||||
#: src/ssr/Globe.jsx:33
|
||||
msgid "A 3D globe of our whole map"
|
||||
msgstr "ჩვენი მთელი რუკის 3D გლობუსი"
|
||||
|
||||
#: src/ssr/Globe.jsx:46
|
||||
msgid "Double click on globe to go back."
|
||||
msgstr "ორჯერ დააწკაპუნეთ გლობუსზე უკან დასაბრუნებლად."
|
||||
|
||||
#: src/ssr/Globe.jsx:47
|
||||
msgid "Loading..."
|
||||
msgstr "Ჩატვირთვა..."
|
||||
|
||||
#: src/ssr/PopUp.jsx:58
|
||||
msgid "ppfun"
|
||||
msgstr "ppfun"
|
||||
|
||||
#: src/ssr/PopUp.jsx:59
|
||||
msgid "PixelPlanet.Fun PopUp"
|
||||
msgstr "PixelPlanet.Fun PopUp"
|
||||
|
||||
#: src/ssr/Main.jsx:67
|
||||
msgid "PixelPlanet.Fun"
|
||||
msgstr "PixelPlanet.Fun"
|
||||
|
||||
#: src/ssr/Main.jsx:68
|
||||
msgid "Place color pixels on an map styled canvas with other players online"
|
||||
msgstr ""
|
||||
"განათავსეთ ფერადი პიქსელები რუკის კანვასზე ტილოზე სხვა მოთამაშეებთან ერთად "
|
||||
"ონლაინ რეჟიმში"
|
||||
|
||||
#: src/utils/validation.js:17
|
||||
msgid "Email can't be empty."
|
||||
msgstr "ელფოსტა არ შეიძლება იყოს ცარიელი."
|
||||
|
||||
#: src/utils/validation.js:18
|
||||
msgid "Email should be at least 5 characters long."
|
||||
msgstr "ელფოსტა უნდა იყოს მინიმუმ 5 სიმბოლო."
|
||||
|
||||
#: src/utils/validation.js:19
|
||||
msgid "Email can't be longer than 40 characters."
|
||||
msgstr "ელფოსტა არ შეიძლება იყოს 40 სიმბოლოზე მეტი."
|
||||
|
||||
#: src/utils/validation.js:20
|
||||
msgid "Email should at least contain a dot"
|
||||
msgstr "ელფოსტა მინიმუმ წერტილს უნდა შეიცავდეს"
|
||||
|
||||
#: src/utils/validation.js:22
|
||||
msgid "Email should contain a @"
|
||||
msgstr "ელფოსტა უნდა შეიცავდეს @"
|
||||
|
||||
#: src/utils/validation.js:29
|
||||
msgid "Name can't be empty."
|
||||
msgstr "სახელი არ შეიძლება იყოს ცარიელი."
|
||||
|
||||
#: src/utils/validation.js:30
|
||||
msgid "Name must be at least 2 characters long"
|
||||
msgstr "სახელი უნდა იყოს მინიმუმ 2 სიმბოლო"
|
||||
|
||||
#: src/utils/validation.js:31
|
||||
msgid "Name must be shorter than 26 characters"
|
||||
msgstr "სახელი უნდა იყოს 26 სიმბოლოზე მოკლე"
|
||||
|
||||
#: src/utils/validation.js:38
|
||||
msgid "Name contains invalid character like @, /, \\ or #"
|
||||
msgstr "სახელი შეიცავს არასწორ სიმბოლოს, როგორიცაა @, /, \\ ან #"
|
||||
|
||||
#: src/utils/validation.js:53
|
||||
msgid "No password given."
|
||||
msgstr "პაროლი არ არის მოცემული."
|
||||
|
||||
#: src/utils/validation.js:56
|
||||
msgid "Password must be at least 6 characters long."
|
||||
msgstr "პაროლი უნდა იყოს მინიმუმ 6 სიმბოლო."
|
||||
|
||||
#: src/utils/validation.js:59
|
||||
msgid "Password must be shorter than 60 characters."
|
||||
msgstr "პაროლი უნდა იყოს 60 სიმბოლოზე ნაკლები."
|
||||
|
||||
#: src/ssr/PasswordReset.jsx:20 src/ssr/PasswordReset.jsx:40
|
||||
msgid "PixelPlanet.fun Password Reset"
|
||||
msgstr "PixelPlanet.fun პაროლის აღდგენა"
|
||||
|
||||
#: src/ssr/PasswordReset.jsx:21 src/ssr/PasswordReset.jsx:41
|
||||
msgid "Reset your password here"
|
||||
msgstr "გადააყენეთ თქვენი პაროლი აქ"
|
||||
|
||||
#: src/core/MailProvider.js:105 src/ssr/PasswordReset.jsx:28
|
||||
#: src/ssr/PasswordReset.jsx:49
|
||||
msgid "Reset Password"
|
||||
msgstr "პაროლის აღდგენა"
|
||||
|
||||
#: src/ssr/PasswordReset.jsx:30 src/ssr/RedirectionPage.jsx:12
|
||||
msgid "Click here"
|
||||
msgstr "Დააკლიკე აქ"
|
||||
|
||||
#: src/ssr/PasswordReset.jsx:30
|
||||
msgid "to go back to pixelplanet"
|
||||
msgstr "პიქსელ პლანეტაზე დასაბრუნებლად"
|
||||
|
||||
#: src/ssr/PasswordReset.jsx:50
|
||||
#, javascript-format
|
||||
msgid "Hello ${ name }, you can set your new password here:"
|
||||
msgstr "გამარჯობა ${ name }, შეგიძლიათ დააყენოთ თქვენი ახალი პაროლი აქ:"
|
||||
|
||||
#: src/ssr/PasswordReset.jsx:54
|
||||
msgid "New Password"
|
||||
msgstr "ახალი პაროლი"
|
||||
|
||||
#: src/ssr/PasswordReset.jsx:60
|
||||
msgid "Confirm New Password"
|
||||
msgstr "Დაადასტურეთ ახალი პაროლი"
|
||||
|
||||
#: src/ssr/PasswordReset.jsx:65
|
||||
msgid "Submit"
|
||||
msgstr "გაგზავნა"
|
||||
|
||||
#: src/routes/api/modtools.js:53
|
||||
msgid "You are not logged in"
|
||||
msgstr "თქვენ არ ხართ შესული"
|
||||
|
||||
#: src/routes/api/modtools.js:65
|
||||
msgid "You are not allowed to access this page"
|
||||
msgstr "თქვენ არ გაქვთ უფლება შეხვიდეთ ამ გვერდზე"
|
||||
|
||||
#: src/routes/api/modtools.js:207
|
||||
msgid "Just admins can do that"
|
||||
msgstr "ამის გაკეთება მხოლოდ ადმინებს შეუძლიათ"
|
||||
|
||||
#: src/routes/api/baninfo.js:32
|
||||
msgid "You are not banned"
|
||||
msgstr "არ ხარ აკრძალული"
|
||||
|
||||
#: src/routes/api/auth/change_mail.js:21 src/routes/api/auth/register.js:24
|
||||
msgid "This email provider is not allowed"
|
||||
msgstr "ელფოსტის ეს პროვაიდერი დაუშვებელია"
|
||||
|
||||
#: src/routes/api/auth/register.js:31
|
||||
msgid "No Captcha given"
|
||||
msgstr "კაპჩა არ არის მოცემული"
|
||||
|
||||
#: src/routes/api/auth/register.js:34
|
||||
msgid "E-Mail already in use."
|
||||
msgstr "Ელექტრონული ფოსტა უკვე გამოყენებულია."
|
||||
|
||||
#: src/routes/api/auth/register.js:36
|
||||
msgid "Username already in use."
|
||||
msgstr "Მომხმარებლის სახელი უკვე გამოიყენება."
|
||||
|
||||
#: src/routes/api/auth/register.js:59
|
||||
msgid "You took too long, try again."
|
||||
msgstr "ძალიან დიდი დრო დაგჭირდათ, ისევ სცადეთ."
|
||||
|
||||
#: src/routes/api/auth/register.js:62
|
||||
msgid "You failed your captcha"
|
||||
msgstr "თქვენ ვერ მოახერხეთ თქვენი კაპტჩა"
|
||||
|
||||
#: src/routes/api/auth/register.js:65
|
||||
msgid "Unknown Captcha Error"
|
||||
msgstr "უცნობი კაპჩტას შეცდომა"
|
||||
|
||||
#: src/routes/api/auth/register.js:89
|
||||
msgid "Failed to create new user :("
|
||||
msgstr "ახალი მომხმარებლის შექმნა ვერ მოხერხდა :("
|
||||
|
||||
#: src/routes/api/auth/register.js:105
|
||||
msgid "Failed to establish session after register :("
|
||||
msgstr "რეგისტრაციის შემდეგ სესიის დამყარება ვერ მოხერხდა :("
|
||||
|
||||
#: src/routes/api/auth/logout.js:11
|
||||
msgid "You are not even logged in."
|
||||
msgstr "თქვენ არც კი ხართ შესული."
|
||||
|
||||
#: src/routes/api/auth/delete_account.js:55 src/routes/api/auth/logout.js:20
|
||||
msgid "Server error when logging out."
|
||||
msgstr "სერვერის შეცდომა გამოსვლისას."
|
||||
|
||||
#: src/routes/api/auth/change_mail.js:43
|
||||
#: src/routes/api/auth/change_passwd.js:34
|
||||
#: src/routes/api/auth/delete_account.js:35
|
||||
msgid "You are not authenticated."
|
||||
msgstr "თქვენ არ ხართ დამოწმებული."
|
||||
|
||||
#: src/routes/api/auth/change_mail.js:52
|
||||
#: src/routes/api/auth/change_passwd.js:43
|
||||
#: src/routes/api/auth/delete_account.js:45
|
||||
msgid "Incorrect password!"
|
||||
msgstr "Არასწორი პაროლი!"
|
||||
|
||||
#: src/routes/api/auth/verify.js:26 src/routes/api/auth/verify.js:35
|
||||
msgid "Mail verification"
|
||||
msgstr "ფოსტის გადამოწმება"
|
||||
|
||||
#: src/routes/api/auth/verify.js:27
|
||||
msgid "You are now verified :)"
|
||||
msgstr "ახლა დამოწმებული ხარ :)"
|
||||
|
||||
#: src/routes/api/auth/verify.js:35
|
||||
msgid ""
|
||||
"Your mail verification code is invalid or already expired :(, please request "
|
||||
"a new one."
|
||||
msgstr ""
|
||||
"თქვენი ფოსტის დამადასტურებელი კოდი არასწორია ან უკვე ვადაგასულია :(, გთხოვთ, "
|
||||
"მოითხოვოთ ახალი."
|
||||
|
||||
#: src/ssr/RedirectionPage.jsx:19
|
||||
msgid "PixelPlanet.fun Accounts"
|
||||
msgstr "PixelPlanet.fun ანგარიშები"
|
||||
|
||||
#: src/ssr/RedirectionPage.jsx:29
|
||||
msgid "You will be automatically redirected after 15s"
|
||||
msgstr "თქვენ ავტომატურად გადამისამართდებით 15 წამის შემდეგ"
|
||||
|
||||
#: src/ssr/RedirectionPage.jsx:30
|
||||
#, javascript-format
|
||||
msgid "Or ${ clickHere } to go back to pixelplanet"
|
||||
msgstr "ან ${ clickHere } pixelplanet-ზე დასაბრუნებლად"
|
||||
|
||||
#: src/canvasesDesc.js:19
|
||||
msgid "Earth"
|
||||
msgstr "დედამიწა"
|
||||
|
||||
#: src/canvasesDesc.js:20
|
||||
msgid "Moon"
|
||||
msgstr "მთვარე"
|
||||
|
||||
#: src/canvasesDesc.js:21
|
||||
msgid "3D Canvas"
|
||||
msgstr "3დ კანვასი"
|
||||
|
||||
#: src/canvasesDesc.js:22
|
||||
msgid "Coronavirus"
|
||||
msgstr "კორონავირუსი"
|
||||
|
||||
#: src/canvasesDesc.js:23
|
||||
msgid "PixelZone"
|
||||
msgstr "პიქსელზონა"
|
||||
|
||||
#: src/canvasesDesc.js:24
|
||||
msgid "PixelCanvas"
|
||||
msgstr "PixelCanvas"
|
||||
|
||||
#: src/canvasesDesc.js:25
|
||||
msgid "1bit"
|
||||
msgstr "1 ბიტი"
|
||||
|
||||
#: src/canvasesDesc.js:26
|
||||
msgid "Top10"
|
||||
msgstr "10 საუკეთესო"
|
||||
|
||||
#: src/canvasesDesc.js:29
|
||||
msgid "Our main canvas, a huge map of the world. Place everywhere you like"
|
||||
msgstr ""
|
||||
"ჩვენი მთავარი კანვასი, მსოფლიოს უზარმაზარი რუკა. განათავსეთ ყველგან, სადაც "
|
||||
"გსურთ"
|
||||
|
||||
#: src/canvasesDesc.js:30
|
||||
msgid ""
|
||||
"Moon canvas. Safe space for art. No flags or large text (unless part of art) "
|
||||
"or art larger than 1.5k x 1.5k pixels."
|
||||
msgstr ""
|
||||
"მთვარის კანვასი. უსაფრთხო სივრცე ხელოვნებისთვის. არ არის დროშები ან დიდი "
|
||||
"ტექსტი (თუ არ არის ხელოვნების ნაწილი) ან 1,5 კ x 1,5 კ პიქსელზე მეტი "
|
||||
"ნამუშევარი."
|
||||
|
||||
#: src/canvasesDesc.js:31
|
||||
msgid "Place Voxels on a 3D canvas with others"
|
||||
msgstr "მოათავსეთ ვოქსელები 3D ტილოზე სხვებთან ერთად"
|
||||
|
||||
#: src/canvasesDesc.js:32
|
||||
msgid "Special canvas to spread awareness of SARS-CoV2"
|
||||
msgstr "სპეციალური ტილო SARS-CoV2-ის შესახებ ცნობადობის გასავრცელებლად"
|
||||
|
||||
#: src/canvasesDesc.js:33
|
||||
msgid "Mirror of PixelZone"
|
||||
msgstr "PixelZone-ის სარკე"
|
||||
|
||||
#: src/canvasesDesc.js:34
|
||||
msgid "Mirror of PixelCanvas"
|
||||
msgstr "PixelCanvas-ის სარკე"
|
||||
|
||||
#: src/canvasesDesc.js:35
|
||||
msgid "Black and White canvas"
|
||||
msgstr "შავ-თეთრი ტილო"
|
||||
|
||||
#: src/canvasesDesc.js:36
|
||||
msgid ""
|
||||
"A canvas for the most active players from the the previous day. Daily "
|
||||
"ranking updates at 00:00 UTC."
|
||||
msgstr ""
|
||||
"ტილო წინა დღის ყველაზე აქტიური მოთამაშეებისთვის. ყოველდღიური რეიტინგის "
|
||||
"განახლებები 00:00 UTC."
|
||||
|
||||
#: src/core/MailProvider.js:66
|
||||
#, javascript-format
|
||||
msgid "Welcome ${ name } to PixelPlanet, plese verify your mail"
|
||||
msgstr ""
|
||||
"მოგესალმებით ${ name } PixelPlanet-ში, გთხოვთ, გადაამოწმოთ თქვენი ფოსტა"
|
||||
|
||||
#: src/core/MailProvider.js:67
|
||||
msgid "Hello ${ name }"
|
||||
msgstr "გამარჯობა ${ name }"
|
||||
|
||||
#: src/core/MailProvider.js:68
|
||||
msgid ""
|
||||
"welcome to our little community of pixelplacers, to use your account, you "
|
||||
"have to verify your mail. You can do that here: "
|
||||
msgstr ""
|
||||
"კეთილი იყოს თქვენი მობრძანება pixelplacers-ის ჩვენს პატარა საზოგადოებაში, "
|
||||
"თქვენი ანგარიშის გამოსაყენებლად, თქვენ უნდა გადაამოწმოთ თქვენი ფოსტა. ამის "
|
||||
"გაკეთება შეგიძლიათ აქ:"
|
||||
|
||||
#: src/core/MailProvider.js:68
|
||||
msgid "Click to Verify"
|
||||
msgstr "დააწკაპუნეთ დასადასტურებლად"
|
||||
|
||||
#: src/core/MailProvider.js:68 src/core/MailProvider.js:105
|
||||
msgid "Or by copying following url:"
|
||||
msgstr "ან შემდეგი url-ის კოპირებით:"
|
||||
|
||||
#: src/core/MailProvider.js:69
|
||||
msgid ""
|
||||
"Have fun and don't hesitate to contact us if you encouter any problems :)"
|
||||
msgstr ""
|
||||
"გაერთეთ და ნუ მოგერიდებათ დაგვიკავშირდეთ თუ რაიმე პრობლემა შეგექმნათ :)"
|
||||
|
||||
#: src/core/MailProvider.js:70 src/core/MailProvider.js:107
|
||||
msgid "Thanks"
|
||||
msgstr "მადლობა"
|
||||
|
||||
#: src/core/MailProvider.js:87
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"We already sent you a verification mail, you can request another one in "
|
||||
"${ minLeft } minutes."
|
||||
msgstr ""
|
||||
"ჩვენ უკვე გამოგიგზავნეთ დამადასტურებელი წერილი, შეგიძლიათ მოითხოვოთ სხვა "
|
||||
"${ minLeft } წუთში."
|
||||
|
||||
#: src/core/MailProvider.js:103
|
||||
msgid "You forgot your password for PixelPlanet? Get a new one here"
|
||||
msgstr "დაგავიწყდათ თქვენი პაროლი PixelPlanet-ისთვის? მიიღეთ ახალი აქ"
|
||||
|
||||
#: src/core/MailProvider.js:104
|
||||
msgid "Hello"
|
||||
msgstr "გამარჯობა"
|
||||
|
||||
#: src/core/MailProvider.js:105
|
||||
msgid ""
|
||||
"You requested to get a new password. You can change your password within the "
|
||||
"next 30min here: "
|
||||
msgstr ""
|
||||
"თქვენ მოითხოვეთ ახალი პაროლის მიღება. თქვენ შეგიძლიათ შეცვალოთ თქვენი პაროლი "
|
||||
"შემდეგი 30 წუთის განმავლობაში აქ:"
|
||||
|
||||
#: src/core/MailProvider.js:106
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"If you did not request this mail, please just ignore it (the ip that "
|
||||
"requested this mail was ${ ip })."
|
||||
msgstr ""
|
||||
"თუ თქვენ არ მოითხოვეთ ეს წერილი, გთხოვთ, უბრალოდ უგულებელყოთ იგი (IP, "
|
||||
"რომელმაც მოითხოვა ეს წერილი, იყო ${ ip })."
|
||||
|
||||
#: src/core/MailProvider.js:114
|
||||
msgid "Mail is not configured on the server"
|
||||
msgstr "ფოსტა არ არის კონფიგურირებული სერვერზე"
|
||||
|
||||
#: src/core/MailProvider.js:122
|
||||
msgid ""
|
||||
"We already sent you a mail with instructions. Please wait before requesting "
|
||||
"another mail."
|
||||
msgstr ""
|
||||
"ჩვენ უკვე გამოგიგზავნეთ მეილი ინსტრუქციებით. გთხოვთ, დაელოდოთ სხვა ფოსტის "
|
||||
"მოთხოვნამდე."
|
||||
|
||||
#: src/core/MailProvider.js:130
|
||||
msgid "Couldn't find this mail in our database"
|
||||
msgstr "ვერ ვიპოვე ეს წერილი ჩვენს მონაცემთა ბაზაში"
|
|
@ -29,6 +29,7 @@ msgid "You are muted for another ${ timeMin } minutes"
|
|||
msgstr ""
|
||||
|
||||
#: src/core/ChatProvider.js:450
|
||||
#, javascript-format
|
||||
msgid "You are muted for another ${ ttl } seconds"
|
||||
msgstr ""
|
||||
|
||||
|
@ -81,35 +82,35 @@ msgstr ""
|
|||
msgid "Invalid url :( Please check your mail again."
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr/Globe.jsx:32
|
||||
#: src/ssr/Globe.jsx:37
|
||||
msgid "PixelPlanet.Fun 3DGlobe"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr/Globe.jsx:33
|
||||
#: src/ssr/Globe.jsx:38
|
||||
msgid "A 3D globe of our whole map"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr/Globe.jsx:46
|
||||
#: src/ssr/Globe.jsx:51
|
||||
msgid "Double click on globe to go back."
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr/Globe.jsx:47
|
||||
#: src/ssr/Globe.jsx:52
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr/PopUp.jsx:58
|
||||
#: src/ssr/PopUp.jsx:55
|
||||
msgid "ppfun"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr/PopUp.jsx:59
|
||||
#: src/ssr/PopUp.jsx:56
|
||||
msgid "PixelPlanet.Fun PopUp"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr/Main.jsx:67
|
||||
#: src/ssr/Main.jsx:64
|
||||
msgid "PixelPlanet.Fun"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr/Main.jsx:68
|
||||
#: src/ssr/Main.jsx:65
|
||||
msgid "Place color pixels on an map styled canvas with other players online"
|
||||
msgstr ""
|
||||
|
||||
|
@ -219,31 +220,7 @@ msgstr ""
|
|||
msgid "You are not banned"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/logout.js:11
|
||||
msgid "You are not even logged in."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/delete_account.js:55
|
||||
#: src/routes/api/auth/logout.js:20
|
||||
msgid "Server error when logging out."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/verify.js:26
|
||||
#: src/routes/api/auth/verify.js:35
|
||||
msgid "Mail verification"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/verify.js:27
|
||||
msgid "You are now verified :)"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/verify.js:35
|
||||
msgid ""
|
||||
"Your mail verification code is invalid or already expired :(, please "
|
||||
"request a new one."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/change_mail.js:21
|
||||
#: src/routes/api/auth/change_mail.js:22
|
||||
#: src/routes/api/auth/register.js:24
|
||||
msgid "This email provider is not allowed"
|
||||
msgstr ""
|
||||
|
@ -280,18 +257,50 @@ msgstr ""
|
|||
msgid "Failed to establish session after register :("
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/change_mail.js:43
|
||||
#: src/routes/api/auth/verify.js:26
|
||||
#: src/routes/api/auth/verify.js:35
|
||||
msgid "Mail verification"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/verify.js:27
|
||||
msgid "You are now verified :)"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/verify.js:35
|
||||
msgid ""
|
||||
"Your mail verification code is invalid or already expired :(, please "
|
||||
"request a new one."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/logout.js:11
|
||||
msgid "You are not even logged in."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/delete_account.js:65
|
||||
#: src/routes/api/auth/logout.js:20
|
||||
msgid "Server error when logging out."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/change_mail.js:44
|
||||
#: src/routes/api/auth/change_passwd.js:34
|
||||
#: src/routes/api/auth/delete_account.js:35
|
||||
#: src/routes/api/auth/delete_account.js:36
|
||||
msgid "You are not authenticated."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/change_mail.js:52
|
||||
#: src/routes/api/auth/change_mail.js:53
|
||||
#: src/routes/api/auth/change_passwd.js:43
|
||||
#: src/routes/api/auth/delete_account.js:45
|
||||
#: src/routes/api/auth/delete_account.js:55
|
||||
msgid "Incorrect password!"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/delete_account.js:46
|
||||
msgid "Muted users can not delete their account."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/change_mail.js:62
|
||||
msgid "Muted users can not do this."
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr/RedirectionPage.jsx:19
|
||||
msgid "PixelPlanet.fun Accounts"
|
||||
msgstr ""
|
||||
|
@ -337,48 +346,59 @@ msgstr ""
|
|||
msgid "Top10"
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:29
|
||||
msgid "Our main canvas, a huge map of the world. Place everywhere you like"
|
||||
#: src/canvasesDesc.js:27
|
||||
msgid "Thoia"
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:30
|
||||
msgid "Our main canvas, a huge map of the world. Place everywhere you like"
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:31
|
||||
msgid ""
|
||||
"Moon canvas. Safe space for art. No flags or large text (unless part of "
|
||||
"art) or art larger than 1.5k x 1.5k pixels."
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:31
|
||||
#: src/canvasesDesc.js:32
|
||||
msgid "Place Voxels on a 3D canvas with others"
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:32
|
||||
#: src/canvasesDesc.js:33
|
||||
msgid "Special canvas to spread awareness of SARS-CoV2"
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:33
|
||||
#: src/canvasesDesc.js:34
|
||||
msgid "Mirror of PixelZone"
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:34
|
||||
#: src/canvasesDesc.js:35
|
||||
msgid "Mirror of PixelCanvas"
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:35
|
||||
#: src/canvasesDesc.js:36
|
||||
msgid "Black and White canvas"
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:36
|
||||
#: src/canvasesDesc.js:37
|
||||
msgid ""
|
||||
"A canvas for the most active players from the the previous day. Daily "
|
||||
"ranking updates at 00:00 UTC."
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:38
|
||||
msgid ""
|
||||
"Thoia World Canvas. Advanced fictional worldbuilding and arts. Abandon the "
|
||||
"old world and all it entails."
|
||||
msgstr ""
|
||||
|
||||
#: src/core/MailProvider.js:66
|
||||
#, javascript-format
|
||||
msgid "Welcome ${ name } to PixelPlanet, please verify your mail"
|
||||
msgstr ""
|
||||
|
||||
#: src/core/MailProvider.js:67
|
||||
#, javascript-format
|
||||
msgid "Hello ${ name }"
|
||||
msgstr ""
|
||||
|
||||
|
@ -446,4 +466,4 @@ msgstr ""
|
|||
|
||||
#: src/core/MailProvider.js:130
|
||||
msgid "Couldn't find this mail in our database"
|
||||
msgstr ""
|
||||
msgstr ""
|
||||
|
|
|
@ -194,6 +194,7 @@ msgid "try again after ${ ti }min"
|
|||
msgstr ""
|
||||
|
||||
#: src/store/actions/fetch.js:70
|
||||
#, javascript-format
|
||||
msgid "Connection error ${ code } :("
|
||||
msgstr ""
|
||||
|
||||
|
@ -230,12 +231,6 @@ msgstr ""
|
|||
msgid "You have new messages in chat"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:561
|
||||
#: src/components/CoordinatesBox.jsx:31
|
||||
#: src/components/ModWatchtools.jsx:371
|
||||
msgid "Copy to Clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/OnlineBox.jsx:40
|
||||
msgid "Online Users on Canvas"
|
||||
msgstr ""
|
||||
|
@ -248,6 +243,13 @@ msgstr ""
|
|||
msgid "Pixels placed"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:565
|
||||
#: src/components/CoordinatesBox.jsx:31
|
||||
#: src/components/ModWatchtools.jsx:378
|
||||
#: src/components/ModWatchtools.jsx:396
|
||||
msgid "Copy to Clipboard"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/CanvasSwitchButton.jsx:20
|
||||
#: src/components/windows/index.js:19
|
||||
msgid "Canvas Selection"
|
||||
|
@ -279,7 +281,7 @@ msgstr ""
|
|||
|
||||
#: src/components/Admintools.jsx:103
|
||||
#: src/components/ModCanvastools.jsx:222
|
||||
#: src/components/ModWatchtools.jsx:118
|
||||
#: src/components/ModWatchtools.jsx:120
|
||||
#: src/components/Window.jsx:157
|
||||
#: src/components/Window.jsx:260
|
||||
#: src/components/contextmenus/ChannelContextMenu.jsx:59
|
||||
|
@ -310,6 +312,26 @@ msgstr ""
|
|||
msgid "Resize"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/BanInfo.jsx:75
|
||||
#: src/components/buttons/HelpButton.jsx:20
|
||||
#: src/components/windows/index.js:13
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/SettingsButton.jsx:21
|
||||
#: src/components/windows/index.js:14
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/LogInButton.jsx:20
|
||||
#: src/components/windows/index.js:15
|
||||
msgid "User Area"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/DownloadButton.jsx:36
|
||||
msgid "Make Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/GlobeButton.jsx:34
|
||||
msgid "Globe View"
|
||||
msgstr ""
|
||||
|
@ -322,26 +344,6 @@ msgstr ""
|
|||
msgid "Open Palette"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/SettingsButton.jsx:21
|
||||
#: src/components/windows/index.js:14
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/BanInfo.jsx:75
|
||||
#: src/components/buttons/HelpButton.jsx:20
|
||||
#: src/components/windows/index.js:13
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/LogInButton.jsx:20
|
||||
#: src/components/windows/index.js:15
|
||||
msgid "User Area"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/DownloadButton.jsx:36
|
||||
msgid "Make Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/index.js:16
|
||||
msgid "Registration"
|
||||
msgstr ""
|
||||
|
@ -447,99 +449,6 @@ msgstr ""
|
|||
msgid "Why?"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:86
|
||||
msgid "Show Grid"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:91
|
||||
msgid "Turn on grid to highlight pixel borders."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:94
|
||||
msgid "Show Pixel Activity"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:99
|
||||
msgid "Show circles where pixels are placed."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:102
|
||||
msgid "Disable Game Sounds"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:108
|
||||
msgid "All sound effects will be disabled."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:112
|
||||
msgid ""
|
||||
"Your Browser doesn't allow us to use AudioContext to play sounds. Do you "
|
||||
"have some privacy feature blocking us?"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:118
|
||||
msgid "Enable chat notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:122
|
||||
msgid "Play a sound when new chat messages arrive"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:125
|
||||
msgid "Auto Zoom In"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:130
|
||||
msgid ""
|
||||
"Zoom in instead of placing a pixel when you tap the canvas and your zoom is "
|
||||
"small."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:133
|
||||
msgid "Compact Palette"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:138
|
||||
msgid "Display Palette in a compact form that takes less screen space."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:141
|
||||
msgid "Potato Mode"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:145
|
||||
msgid "For when you are playing on a potato."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:376
|
||||
#: src/components/windows/Settings.jsx:148
|
||||
msgid "Light Grid"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:152
|
||||
msgid "Show Grid in white instead of black."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:156
|
||||
msgid "Historical View"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:161
|
||||
msgid "Check out past versions of the canvas."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:166
|
||||
msgid "Themes"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:171
|
||||
msgid "How pixelplanet should look like."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:178
|
||||
msgid "Select Language"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:42
|
||||
msgid "Place color pixels on a large canvas with other players online!"
|
||||
msgstr ""
|
||||
|
@ -609,6 +518,7 @@ msgid "Press ${ bindG } to toggle grid"
|
|||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:64
|
||||
#, javascript-format
|
||||
msgid "Press ${ bindX } to toggle showing of pixel activity"
|
||||
msgstr ""
|
||||
|
||||
|
@ -618,6 +528,7 @@ msgid "Press ${ bindH } to toggle historical view"
|
|||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:66
|
||||
#, javascript-format
|
||||
msgid "Press ${ bindR } to copy coordinates"
|
||||
msgstr ""
|
||||
|
||||
|
@ -639,6 +550,7 @@ msgid "Press ${ bindAUp }, ${ bindALeft }, ${ bindADown }, ${ bindARight } to mo
|
|||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:70
|
||||
#, javascript-format
|
||||
msgid "Drag ${ mouseSymbol } mouse or ${ touchSymbol } pan to move"
|
||||
msgstr ""
|
||||
|
||||
|
@ -648,6 +560,7 @@ msgid "Scroll ${ mouseSymbol } mouse wheel or ${ touchSymbol } pinch to zoom"
|
|||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:72
|
||||
#, javascript-format
|
||||
msgid "Hold left ${ bindShift } for placing while moving mouse"
|
||||
msgstr ""
|
||||
|
||||
|
@ -666,12 +579,14 @@ msgstr ""
|
|||
|
||||
#: src/components/windows/Help.jsx:75
|
||||
#: src/components/windows/Help.jsx:87
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Click ${ mouseSymbol } middle mouse button or ${ touchSymbol } long-tap to "
|
||||
"select current hovering color"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:81
|
||||
#, javascript-format
|
||||
msgid "Press ${ bindE } and ${ bindC } to fly up and down"
|
||||
msgstr ""
|
||||
|
||||
|
@ -688,6 +603,7 @@ msgid ""
|
|||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:84
|
||||
#, javascript-format
|
||||
msgid "${ mouseSymbol } Right click and drag mouse to pan"
|
||||
msgstr ""
|
||||
|
||||
|
@ -710,9 +626,103 @@ msgid "Credit for the Palette of the Moon goes to ${ starhouseLink }."
|
|||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:97
|
||||
#, javascript-format
|
||||
msgid "Credit for the Palette of the Top10 canvas goes to ${ vinikLink }."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:86
|
||||
msgid "Show Grid"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:91
|
||||
msgid "Turn on grid to highlight pixel borders."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:94
|
||||
msgid "Show Pixel Activity"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:99
|
||||
msgid "Show circles where pixels are placed."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:102
|
||||
msgid "Disable Game Sounds"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:108
|
||||
msgid "All sound effects will be disabled."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:112
|
||||
msgid ""
|
||||
"Your Browser doesn't allow us to use AudioContext to play sounds. Do you "
|
||||
"have some privacy feature blocking us?"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:118
|
||||
msgid "Enable chat notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:122
|
||||
msgid "Play a sound when new chat messages arrive"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:125
|
||||
msgid "Auto Zoom In"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:130
|
||||
msgid ""
|
||||
"Zoom in instead of placing a pixel when you tap the canvas and your zoom is "
|
||||
"small."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:133
|
||||
msgid "Compact Palette"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:138
|
||||
msgid "Display Palette in a compact form that takes less screen space."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:141
|
||||
msgid "Potato Mode"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:145
|
||||
msgid "For when you are playing on a potato."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:380
|
||||
#: src/components/windows/Settings.jsx:148
|
||||
msgid "Light Grid"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:152
|
||||
msgid "Show Grid in white instead of black."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:156
|
||||
msgid "Historical View"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:161
|
||||
msgid "Check out past versions of the canvas."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:166
|
||||
msgid "Themes"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:171
|
||||
msgid "How pixelplanet should look like."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:178
|
||||
msgid "Select Language"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:36
|
||||
#: src/components/windows/UserArea.jsx:56
|
||||
msgid "Profile"
|
||||
|
@ -826,14 +836,6 @@ msgid ""
|
|||
"how the canvas was at that time."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/ForgotPassword.jsx:58
|
||||
msgid "Sent you a mail with instructions to reset your password."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/ForgotPassword.jsx:69
|
||||
msgid "Enter your mail address and we will send you a new password:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Chat.jsx:180
|
||||
msgid "Start chatting here"
|
||||
msgstr ""
|
||||
|
@ -850,6 +852,14 @@ msgstr ""
|
|||
msgid "Channel settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/ForgotPassword.jsx:58
|
||||
msgid "Sent you a mail with instructions to reset your password."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/ForgotPassword.jsx:69
|
||||
msgid "Enter your mail address and we will send you a new password:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Captcha.jsx:51
|
||||
#: src/components/Captcha.jsx:105
|
||||
msgid "Could not load captcha"
|
||||
|
@ -931,13 +941,6 @@ msgstr ""
|
|||
msgid "Password must be shorter than 60 characters."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeMail.jsx:91
|
||||
#: src/components/ChangeName.jsx:68
|
||||
#: src/components/ChangePassword.jsx:109
|
||||
#: src/components/LanguageSelect.jsx:80
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/GetIID.jsx:44
|
||||
msgid "Get IID"
|
||||
msgstr ""
|
||||
|
@ -946,6 +949,13 @@ msgstr ""
|
|||
msgid "Copy"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeMail.jsx:91
|
||||
#: src/components/ChangeName.jsx:68
|
||||
#: src/components/ChangePassword.jsx:109
|
||||
#: src/components/LanguageSelect.jsx:80
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInArea.jsx:19
|
||||
msgid "Login to access more features and stats."
|
||||
msgstr ""
|
||||
|
@ -1020,77 +1030,77 @@ msgstr ""
|
|||
msgid "Choose Canvas"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:216
|
||||
#: src/components/Converter.jsx:217
|
||||
msgid "Palette Download"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:218
|
||||
#: src/components/Converter.jsx:219
|
||||
#, javascript-format
|
||||
msgid "Palette for ${ gimpLink }"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:236
|
||||
#: src/components/Converter.jsx:240
|
||||
msgid "Image Converter"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:237
|
||||
#: src/components/Converter.jsx:241
|
||||
msgid "Convert an image to canvas colors"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:256
|
||||
#: src/components/Converter.jsx:260
|
||||
msgid "Choose Strategy"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:294
|
||||
#: src/components/Converter.jsx:298
|
||||
msgid "Serpentine"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:296
|
||||
#: src/components/Converter.jsx:300
|
||||
msgid "Minimum Color Distance"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:323
|
||||
#: src/components/Converter.jsx:327
|
||||
msgid "Calculate like GIMP"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:327
|
||||
#: src/components/Converter.jsx:331
|
||||
msgid "Choose Color Mode"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:356
|
||||
#: src/components/Converter.jsx:360
|
||||
msgid "Add Grid (uncheck if you need a 1:1 template)"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:378
|
||||
#: src/components/Converter.jsx:394
|
||||
#: src/components/Converter.jsx:382
|
||||
#: src/components/Converter.jsx:398
|
||||
msgid "Offset"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:423
|
||||
#: src/components/Converter.jsx:427
|
||||
msgid "Scale Image"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:434
|
||||
#: src/components/Converter.jsx:438
|
||||
msgid "Width"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:465
|
||||
#: src/components/Converter.jsx:469
|
||||
msgid "Height"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:504
|
||||
#: src/components/Converter.jsx:508
|
||||
msgid "Keep Ratio"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:517
|
||||
#: src/components/Converter.jsx:521
|
||||
msgid "Anti Aliasing"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:531
|
||||
#: src/components/Converter.jsx:535
|
||||
msgid "Reset"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:549
|
||||
#: src/components/Converter.jsx:553
|
||||
msgid "Download Template"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1189,7 +1199,7 @@ msgstr ""
|
|||
msgid "Countries by Pixels Today"
|
||||
msgstr ""
|
||||
|
||||
#: src/core/chartSettings.js:352
|
||||
#: src/core/chartSettings.js:351
|
||||
msgid "Total Pixels placed per day"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1215,10 +1225,6 @@ msgstr ""
|
|||
msgid "Click here to request a new verification mail."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeName.jsx:64
|
||||
msgid "New Username"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangePassword.jsx:21
|
||||
msgid "Passwords do not match."
|
||||
msgstr ""
|
||||
|
@ -1239,6 +1245,10 @@ msgstr ""
|
|||
msgid "Confirm New Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeName.jsx:64
|
||||
msgid "New Username"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeMail.jsx:59
|
||||
msgid ""
|
||||
"Changed Mail successfully. We sent you a verification mail, "
|
||||
|
@ -1339,14 +1349,14 @@ msgstr ""
|
|||
#: src/components/ModCanvastools.jsx:330
|
||||
#: src/components/ModCanvastools.jsx:403
|
||||
#: src/components/ModCanvastools.jsx:486
|
||||
#: src/components/ModWatchtools.jsx:174
|
||||
#: src/components/ModWatchtools.jsx:176
|
||||
msgid "Top-left corner"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModCanvastools.jsx:347
|
||||
#: src/components/ModCanvastools.jsx:420
|
||||
#: src/components/ModCanvastools.jsx:503
|
||||
#: src/components/ModWatchtools.jsx:191
|
||||
#: src/components/ModWatchtools.jsx:193
|
||||
msgid "Bottom-right corner"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1370,54 +1380,6 @@ msgstr ""
|
|||
msgid "Stop Cleaner"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:48
|
||||
msgid "Interval is invalid"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:122
|
||||
msgid "Check who placed in an area"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:123
|
||||
msgid "Canvas"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:142
|
||||
msgid "Interval"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:157
|
||||
msgid "IID (optional)"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:236
|
||||
msgid "Get Pixels"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:267
|
||||
msgid "Get Users"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModIIDtools.jsx:20
|
||||
msgid "You must enter a duration"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModIIDtools.jsx:24
|
||||
msgid "You must enter an IID"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModIIDtools.jsx:53
|
||||
msgid "IID Actions"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModIIDtools.jsx:80
|
||||
msgid "Enter Reason"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModIIDtools.jsx:97
|
||||
msgid "(0 = infinite)"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:109
|
||||
msgid "IP Actions"
|
||||
msgstr ""
|
||||
|
@ -1450,8 +1412,52 @@ msgstr ""
|
|||
msgid "User Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/contextmenus/ChannelContextMenu.jsx:46
|
||||
msgid "Mute"
|
||||
#: src/components/ModWatchtools.jsx:48
|
||||
msgid "Interval is invalid"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:124
|
||||
msgid "Check who placed in an area"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:125
|
||||
msgid "Canvas"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:144
|
||||
msgid "Interval"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:159
|
||||
msgid "IID (optional)"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:239
|
||||
msgid "Get Pixels"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModWatchtools.jsx:271
|
||||
msgid "Get Users"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModIIDtools.jsx:20
|
||||
msgid "You must enter a duration"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModIIDtools.jsx:24
|
||||
msgid "You must enter an IID"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModIIDtools.jsx:53
|
||||
msgid "IID Actions"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModIIDtools.jsx:80
|
||||
msgid "Enter Reason"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ModIIDtools.jsx:97
|
||||
msgid "(0 = infinite)"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/contextmenus/UserContextMenu.jsx:49
|
||||
|
@ -1466,6 +1472,10 @@ msgstr ""
|
|||
msgid "Block"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/contextmenus/ChannelContextMenu.jsx:46
|
||||
msgid "Mute"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:15
|
||||
#: src/components/windows/Settings.jsx:87
|
||||
msgctxt "keybinds"
|
||||
|
@ -1478,11 +1488,6 @@ msgctxt "keybinds"
|
|||
msgid "X"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:103
|
||||
msgctxt "keybinds"
|
||||
msgid "M"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:17
|
||||
#: src/components/windows/Settings.jsx:158
|
||||
msgctxt "keybinds"
|
||||
|
@ -1532,4 +1537,9 @@ msgstr ""
|
|||
#: src/components/windows/Help.jsx:32
|
||||
msgctxt "keybinds"
|
||||
msgid "C"
|
||||
msgstr ""
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:103
|
||||
msgctxt "keybinds"
|
||||
msgid "M"
|
||||
msgstr ""
|
||||
|
|
File diff suppressed because it is too large
Load Diff
66
package.json
66
package.json
|
@ -8,8 +8,11 @@
|
|||
"description": "Unlimited planet canvas for placing pixels",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"build": "node scripts/build.js && npm run minify-css",
|
||||
"build:dev": "webpack --env extract --config ./webpack.config.server.js && webpack --env extract --env development --config ./webpack.config.client.js && npm run minify-css",
|
||||
"build": "node scripts/build.js",
|
||||
"build:server": "webpack --config ./webpack.config.server.js",
|
||||
"build:client": "node scripts/build.js --client",
|
||||
"build:dev": "webpack --config ./webpack.config.server.js && webpack --env development --config ./webpack.config.client.js && npm run minify-css",
|
||||
"update-browserlist": "browserslist --update-db",
|
||||
"deploy": "ssh pixelplanet /home/pixelpla/rebuild.sh",
|
||||
"minify-css": "node scripts/minifyCss.js",
|
||||
"babel-node": "babel-node",
|
||||
|
@ -23,11 +26,11 @@
|
|||
"not IE_Mob 11"
|
||||
],
|
||||
"dependencies": {
|
||||
"bcrypt": "^5.0.1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"chart.js": "^3.9.1",
|
||||
"compression": "^1.7.3",
|
||||
"cookie": "^0.5.0",
|
||||
"core-js": "^3.23.4",
|
||||
"core-js": "^3.34.0",
|
||||
"etag": "^1.8.1",
|
||||
"express": "^4.17.2",
|
||||
"express-session": "^1.17.2",
|
||||
|
@ -36,7 +39,7 @@
|
|||
"morgan": "^1.10.0",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"mysql2": "^2.3.3",
|
||||
"nodemailer": "^6.7.8",
|
||||
"nodemailer": "^6.9.7",
|
||||
"passport": "^0.6.0",
|
||||
"passport-discord": "^0.1.4",
|
||||
"passport-facebook": "^3.0.0",
|
||||
|
@ -48,57 +51,56 @@
|
|||
"react": "^18.2.0",
|
||||
"react-chartjs-2": "^4.3.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-stay-scrolled": "^8.0.0",
|
||||
"react-toggle": "^4.1.3",
|
||||
"redis": "^4.3.1",
|
||||
"redis": "^4.6.11",
|
||||
"redux": "^4.1.2",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.4.1",
|
||||
"reselect": "^4.1.6",
|
||||
"sequelize": "^6.21.6",
|
||||
"sequelize": "^6.35.2",
|
||||
"sharp": "^0.31.0",
|
||||
"startaudiocontext": "^1.2.1",
|
||||
"three": "^0.143.0",
|
||||
"three-trackballcontrols": "^0.9.0",
|
||||
"ttag": "^1.7.24",
|
||||
"url-search-params-polyfill": "^8.1.1",
|
||||
"winston": "^3.8.2",
|
||||
"ttag": "^1.8.3",
|
||||
"url-search-params-polyfill": "^8.2.5",
|
||||
"winston": "^3.11.0",
|
||||
"winston-daily-rotate-file": "^4.5.5",
|
||||
"ws": "^8.4.0"
|
||||
"ws": "^8.15.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.21.0",
|
||||
"@babel/core": "^7.21.0",
|
||||
"@babel/eslint-parser": "^7.19.1",
|
||||
"@babel/node": "^7.20.7",
|
||||
"@babel/plugin-transform-react-constant-elements": "^7.20.2",
|
||||
"@babel/plugin-transform-react-inline-elements": "^7.21.0",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"assets-webpack-plugin": "^7.1.1",
|
||||
"babel-loader": "^8.2.3",
|
||||
"@babel/cli": "^7.23.4",
|
||||
"@babel/core": "^7.23.6",
|
||||
"@babel/eslint-parser": "^7.23.3",
|
||||
"@babel/node": "^7.22.19",
|
||||
"@babel/plugin-transform-react-constant-elements": "^7.23.3",
|
||||
"@babel/plugin-transform-react-inline-elements": "^7.23.3",
|
||||
"@babel/preset-env": "^7.23.6",
|
||||
"@babel/preset-react": "^7.23.3",
|
||||
"babel-loader": "^9.1.3",
|
||||
"babel-plugin-transform-react-pure-class-to-function": "^1.0.1",
|
||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
||||
"babel-plugin-ttag": "^1.8.5",
|
||||
"clean-css": "^5.2.2",
|
||||
"clean-css-loader": "^4.1.1",
|
||||
"clean-css-loader": "^4.2.1",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"css-loader": "^6.5.1",
|
||||
"eslint": "^8.36.0",
|
||||
"css-loader": "^6.8.1",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-import": "^2.29.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.8.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"generate-package-json-webpack-plugin": "^2.6.0",
|
||||
"ttag-cli": "^1.9.3",
|
||||
"ttag-cli": "^1.10.9",
|
||||
"ttag-po-loader": "0.0.2",
|
||||
"webpack": "^5.67.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-bundle-analyzer": "^4.10.1",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-node-externals": "^3.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* webpack loader that
|
||||
* marks modules that include ttag as non-cachable
|
||||
*/
|
||||
const filtered = {};
|
||||
|
||||
module.exports = function (source) {
|
||||
if (filtered.hasOwnProperty(this.resourcePath)) {
|
||||
if (filtered[this.resourcePath]) {
|
||||
this.cacheable(false);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
const hasTtag = source.slice(0, 400).includes('ttag');
|
||||
filtered[this.resourcePath] = hasTtag;
|
||||
if (hasTtag) {
|
||||
this.cacheable(false);
|
||||
}
|
||||
return source;
|
||||
}
|
183
scripts/build.js
183
scripts/build.js
|
@ -3,14 +3,52 @@
|
|||
* Lets split that here
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { spawn } = require('child_process');
|
||||
const webpack = require('webpack');
|
||||
|
||||
const minifyCss = require('./minifyCss');
|
||||
const serverConfig = require('../webpack.config.server.js');
|
||||
const clientConfig = require('../webpack.config.client.js');
|
||||
const { getAllAvailableLocals } = clientConfig;
|
||||
|
||||
let langs = 'all';
|
||||
let doBuildServer = false;
|
||||
let doBuildClient = false;
|
||||
let parallel = false;
|
||||
let recursion = false;
|
||||
for (let i = 0; i < process.argv.length; i += 1) {
|
||||
switch (process.argv[i]) {
|
||||
case '--langs': {
|
||||
const newLangs = process.argv[++i];
|
||||
if (newLangs) langs = newLangs;
|
||||
break;
|
||||
}
|
||||
case '--client':
|
||||
doBuildClient = true;
|
||||
break;
|
||||
case `--server`:
|
||||
doBuildServer = true;
|
||||
break;
|
||||
case '--parallel':
|
||||
parallel = true;
|
||||
break;
|
||||
case '--recursion':
|
||||
recursion = true;
|
||||
break;
|
||||
default:
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
if (!doBuildServer && !doBuildClient) {
|
||||
doBuildServer = true;
|
||||
doBuildClient = true;
|
||||
}
|
||||
|
||||
function compile(webpackConfig) {
|
||||
return new Promise((resolve, reject) => {
|
||||
webpack(webpackConfig).run((err, stats) => {
|
||||
webpack(webpackConfig, (err, stats) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
@ -21,31 +59,146 @@ function compile(webpackConfig) {
|
|||
});
|
||||
}
|
||||
|
||||
async function buildProduction() {
|
||||
// server files
|
||||
function buildServer() {
|
||||
console.log('-----------------------------');
|
||||
console.log(`Build server...`);
|
||||
console.log('-----------------------------');
|
||||
await compile(serverConfig({
|
||||
development: false,
|
||||
extract: false,
|
||||
}));
|
||||
// client files
|
||||
const langs = getAllAvailableLocals();
|
||||
console.log('Available locales:', langs);
|
||||
const st = Date.now();
|
||||
for(let i = 0; i < langs.length; i += 1) {
|
||||
const lang = langs[i];
|
||||
const ts = Date.now();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const argsc = (langs === 'all')
|
||||
? ['webpack', '--env', 'extract', '--config', './webpack.config.server.js']
|
||||
: ['webpack', '--config', './webpack.config.server.js']
|
||||
const serverCompile = spawn('npx', argsc);
|
||||
serverCompile.stdout.on('data', (data) => {
|
||||
console.log(data.toString());
|
||||
});
|
||||
serverCompile.stderr.on('data', (data) => {
|
||||
console.error(data.toString());
|
||||
});
|
||||
serverCompile.on('close', (code) => {
|
||||
if (code) {
|
||||
reject(new Error('Server compilation failed!'));
|
||||
} else {
|
||||
console.log('---------------------------------------');
|
||||
console.log(`Server Compilation finished in ${Math.floor((Date.now() - ts) / 1000)}s`);
|
||||
console.log('---------------------------------------');
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function buildClients(slangs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const clientCompile = spawn('npm', ['run', 'build', '--', '--client', '--recursion', '--langs', slangs.join(',')]);
|
||||
clientCompile.stdout.on('data', (data) => {
|
||||
console.log(data.toString());
|
||||
});
|
||||
clientCompile.stderr.on('data', (data) => {
|
||||
console.error(data.toString());
|
||||
});
|
||||
clientCompile.on('close', (code) => {
|
||||
if (code) {
|
||||
reject(new Error('Client compilation failed!'));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function buildClientsSync(avlangs) {
|
||||
for(let i = 0; i < avlangs.length; i += 1) {
|
||||
const lang = avlangs[i];
|
||||
console.log(`Build client for locale ${lang}...`);
|
||||
console.log('-----------------------------');
|
||||
await compile(clientConfig({
|
||||
development: false,
|
||||
analyze: false,
|
||||
extract: false,
|
||||
locale: lang,
|
||||
clean: false,
|
||||
readonly: recursion,
|
||||
}));
|
||||
}
|
||||
console.log(`Finished building in ${(Date.now() - st) / 1000}s`);
|
||||
}
|
||||
|
||||
function buildClientsParallel(avlangs) {
|
||||
const st = Date.now();
|
||||
const numProc = 3;
|
||||
let nump = Math.floor(avlangs.length / numProc);
|
||||
if (!nump) nump = 1;
|
||||
|
||||
const promises = [];
|
||||
while (avlangs.length >= nump) {
|
||||
const slangs = avlangs.splice(0, nump);
|
||||
promises.push(buildClients(slangs));
|
||||
}
|
||||
if (avlangs.length) {
|
||||
promises.push(buildClientsSync(avlangs));
|
||||
}
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
async function buildProduction() {
|
||||
const st = Date.now();
|
||||
// cleanup old files
|
||||
if (!recursion) {
|
||||
fs.rmSync(path.resolve(__dirname, '..', 'node_modules', '.cache', 'webpack'), { recursive: true, force: true });
|
||||
}
|
||||
|
||||
// decide which languages to build
|
||||
let avlangs = getAllAvailableLocals();
|
||||
if (langs !== 'all') {
|
||||
avlangs = langs.split(',').map((l) => l.trim())
|
||||
.filter((l) => avlangs.includes(l));
|
||||
if (!avlangs.length) {
|
||||
console.error(`ERROR: language ${langs} not available`);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log('Building locales:', avlangs);
|
||||
|
||||
const promises = [];
|
||||
|
||||
if (doBuildServer) {
|
||||
promises.push(buildServer());
|
||||
}
|
||||
|
||||
if (doBuildClient) {
|
||||
if (!recursion) {
|
||||
console.log(
|
||||
'Building one package seperately to populate cache and possibly extract langs...',
|
||||
);
|
||||
await compile(clientConfig({
|
||||
development: false,
|
||||
analyze: false,
|
||||
extract: (langs === 'all'),
|
||||
locale: avlangs.shift(),
|
||||
clean: true,
|
||||
readonly: false,
|
||||
}));
|
||||
|
||||
console.log('-----------------------------');
|
||||
console.log(`Minify CSS assets...`);
|
||||
console.log('-----------------------------');
|
||||
await minifyCss();
|
||||
}
|
||||
|
||||
if (parallel) {
|
||||
promises.push(buildClientsParallel(avlangs));
|
||||
} else {
|
||||
promises.push(buildClientsSync(avlangs));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
|
||||
if (!recursion) {
|
||||
console.log(`Finished building in ${(Date.now() - st) / 1000}s`);
|
||||
} else {
|
||||
console.log(`Worker done in ${(Date.now() - st) / 1000}s`);
|
||||
}
|
||||
}
|
||||
|
||||
buildProduction();
|
||||
|
|
|
@ -14,6 +14,7 @@ const path = require('path');
|
|||
const CleanCSS = require('clean-css');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const buildTs = Date.now();
|
||||
const assetdir = path.resolve(__dirname, '..', 'dist', 'public', 'assets');
|
||||
const builddir = path.resolve(__dirname, '..', 'dist');
|
||||
|
||||
|
@ -23,7 +24,6 @@ FILES.push('default.css');
|
|||
|
||||
async function minifyCss() {
|
||||
console.log('Minifying css');
|
||||
const assets = {};
|
||||
FILES.forEach((file) => {
|
||||
const input = fs.readFileSync(path.resolve(FOLDER, file), 'utf8');
|
||||
const options = {};
|
||||
|
@ -48,10 +48,7 @@ async function minifyCss() {
|
|||
}
|
||||
const filename = `${key}.${hash.substr(0, 8)}.css`;
|
||||
fs.writeFileSync(path.resolve(assetdir, filename), output.styles, 'utf8');
|
||||
assets[key] = `/assets/${filename}`;
|
||||
});
|
||||
const json = JSON.stringify(assets);
|
||||
fs.writeFileSync(path.resolve(builddir, 'styleassets.json'), json);
|
||||
}
|
||||
|
||||
async function doMinifyCss() {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import assetWatcher from './core/fsWatcher';
|
||||
import canvases from './core/canvases';
|
||||
import ttag from './core/ttag';
|
||||
|
||||
|
@ -58,16 +59,21 @@ function getCanvases(t) {
|
|||
return localizedCanvases;
|
||||
}
|
||||
|
||||
const lCanvases = {};
|
||||
(() => {
|
||||
function translateCanvases() {
|
||||
const parsedCanvases = {};
|
||||
const langs = Object.keys(ttag);
|
||||
langs.forEach((lang) => {
|
||||
lCanvases[lang] = getCanvases(ttag[lang].t);
|
||||
parsedCanvases[lang] = getCanvases(ttag[lang].t);
|
||||
});
|
||||
})();
|
||||
|
||||
export function getLocalizedCanvases(lang) {
|
||||
return lCanvases[lang] || lCanvases.default;
|
||||
return parsedCanvases;
|
||||
}
|
||||
|
||||
export default lCanvases;
|
||||
let lCanvases = translateCanvases();
|
||||
// reload on asset change
|
||||
assetWatcher.onChange(() => {
|
||||
lCanvases = translateCanvases();
|
||||
});
|
||||
|
||||
export default function getLocalizedCanvases(lang = 'en') {
|
||||
return lCanvases[lang] || lCanvases.en;
|
||||
}
|
||||
|
|
|
@ -66,14 +66,14 @@ function LanguageSelect() {
|
|||
/* set with selected language */
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + 24 * MONTH);
|
||||
let { host } = window.location;
|
||||
if (host.lastIndexOf('.') !== host.indexOf('.')) {
|
||||
host = host.slice(host.indexOf('.'));
|
||||
let { hostname } = window.location;
|
||||
if (hostname.lastIndexOf('.') !== hostname.indexOf('.')) {
|
||||
hostname = hostname.slice(hostname.indexOf('.'));
|
||||
} else {
|
||||
host = `.${host}`;
|
||||
hostname = `.${hostname}`;
|
||||
}
|
||||
// eslint-disable-next-line max-len
|
||||
document.cookie = `plang=${langSel};expires=${d.toUTCString()};path=/;domain=${host}`;
|
||||
document.cookie = `plang=${langSel};expires=${d.toUTCString()};path=/;domain=${hostname}`;
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
|
|
|
@ -192,7 +192,7 @@ export class ChatProvider {
|
|||
|
||||
getDefaultChannels(lang) {
|
||||
const langChannel = {};
|
||||
if (lang && lang !== 'default') {
|
||||
if (lang && lang !== 'en') {
|
||||
const { langChannels } = this;
|
||||
if (langChannels[lang]) {
|
||||
const {
|
||||
|
|
|
@ -68,7 +68,7 @@ export class MailProvider {
|
|||
${t`welcome to our little community of pixelplacers, to use your account, you have to verify your mail. You can do that here: `} <a href="${verifyUrl}">${t`Click to Verify`}</a>. ${t`Or by copying following url:`}<br />${verifyUrl}\n<br />
|
||||
${t`Have fun and don't hesitate to contact us if you encounter any problems :)`}<br />
|
||||
${t`Thanks`}<br /><br />
|
||||
<img alt="" src="https://assets.pixelplanet.fun/tile.png" style="height:64px; width:64px" />`;
|
||||
<img alt="" src="https://pixelplanet.fun/tile.png" style="height:64px; width:64px" />`;
|
||||
this.sendMail(to, subject, html);
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ export class MailProvider {
|
|||
const html = `<em>${t`Hello`}</em>,<br />
|
||||
${t`You requested to get a new password. You can change your password within the next 30min here: `} <a href="${restoreUrl}">${t`Reset Password`}</a>. ${t`Or by copying following url:`}<br />${restoreUrl}\n<br />
|
||||
${t`If you did not request this mail, please just ignore it (the ip that requested this mail was ${ip}).`}<br />
|
||||
${t`Thanks`}<br /><br />\n<img alt="" src="https://assets.pixelplanet.fun/tile.png" style="height:64px; width:64px" />`;
|
||||
${t`Thanks`}<br /><br />\n<img alt="" src="https://pixelplanet.fun/tile.png" style="height:64px; width:64px" />`;
|
||||
this.sendMail(to, subject, html);
|
||||
}
|
||||
|
||||
|
|
|
@ -268,14 +268,18 @@ export async function executeImageAction(
|
|||
* register responses on socket for Watch Actions
|
||||
*/
|
||||
socketEvents.onReq('watch', (action, ...args) => {
|
||||
if (action === 'getIIDSummary') {
|
||||
return getIIDSummary(...args);
|
||||
} if (action === 'getIIDPixels') {
|
||||
return getIIDPixels(...args);
|
||||
} if (action === 'getSummaryFromArea') {
|
||||
return getSummaryFromArea(...args);
|
||||
} if (action === 'getPixelsFromArea') {
|
||||
return getPixelsFromArea(...args);
|
||||
try {
|
||||
if (action === 'getIIDSummary') {
|
||||
return getIIDSummary(...args);
|
||||
} if (action === 'getIIDPixels') {
|
||||
return getIIDPixels(...args);
|
||||
} if (action === 'getSummaryFromArea') {
|
||||
return getSummaryFromArea(...args);
|
||||
} if (action === 'getPixelsFromArea') {
|
||||
return getPixelsFromArea(...args);
|
||||
}
|
||||
} catch {
|
||||
// silently fail when file couldn't be parsed
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
|
|
@ -1,9 +1,133 @@
|
|||
import path from 'path';
|
||||
import { readFileSync } from 'fs';
|
||||
/*
|
||||
* Provide css and js asset files for client
|
||||
*/
|
||||
|
||||
export const assets = JSON.parse(readFileSync(
|
||||
path.resolve(__dirname, './assets.json'),
|
||||
));
|
||||
export const styleassets = JSON.parse(readFileSync(
|
||||
path.resolve(__dirname, './styleassets.json'),
|
||||
));
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import assetWatcher from './fsWatcher';
|
||||
import { ASSET_DIR } from './config';
|
||||
|
||||
const assetDir = path.join(__dirname, 'public', ASSET_DIR);
|
||||
/*
|
||||
* {
|
||||
* js:
|
||||
* client:
|
||||
* en: "/assets/client.defult.134234.js",
|
||||
* de: "/assets/client.de.32834234.js",
|
||||
* [...]
|
||||
* [...]
|
||||
* css:
|
||||
* default: "/assets/default.234234.css",
|
||||
* dark-round: "/assets/dark-round.234233324.css",
|
||||
* [...]
|
||||
* }
|
||||
*/
|
||||
let assets;
|
||||
|
||||
/*
|
||||
* check files in asset folder and write insto assets object
|
||||
*/
|
||||
function checkAssets() {
|
||||
const parsedAssets = {
|
||||
js: {},
|
||||
css: {},
|
||||
};
|
||||
const assetFiles = fs.readdirSync(assetDir);
|
||||
const mtimes = {};
|
||||
|
||||
for (const filename of assetFiles) {
|
||||
const parts = filename.split('.');
|
||||
|
||||
// File needs to have a timestamp in its name
|
||||
if (parts.length < 3) {
|
||||
continue;
|
||||
}
|
||||
// if multiple candidates exist, take most recent created file
|
||||
const mtime = fs.statSync(path.resolve(assetDir, filename))
|
||||
.mtime.getTime();
|
||||
const ident = parts.filter((a, ind) => ind !== parts.length - 2).join('.');
|
||||
if (mtimes[ident] && mtimes[ident] > mtime) {
|
||||
continue;
|
||||
}
|
||||
mtimes[ident] = mtime;
|
||||
|
||||
const ext = parts[parts.length - 1];
|
||||
const relPath = `${ASSET_DIR}/${filename}`;
|
||||
|
||||
switch (ext.toLowerCase()) {
|
||||
case 'js': {
|
||||
// Format: name.[lang].[timestamp].js
|
||||
if (parts.length === 4) {
|
||||
const [name, lang] = parts;
|
||||
let nameObj = parsedAssets.js[name];
|
||||
if (typeof nameObj !== 'object') {
|
||||
nameObj = {};
|
||||
parsedAssets.js[name] = nameObj;
|
||||
}
|
||||
nameObj[lang] = relPath;
|
||||
} else {
|
||||
const [name] = parts;
|
||||
parsedAssets.js[name] = relPath;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'css': {
|
||||
// Format: [dark-]name.[timestamp].js
|
||||
parsedAssets.css[parts[0]] = relPath;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
return parsedAssets;
|
||||
}
|
||||
|
||||
assets = checkAssets();
|
||||
// reload on asset change
|
||||
assetWatcher.onChange(() => {
|
||||
assets = checkAssets();
|
||||
});
|
||||
|
||||
export function getLangsOfJsAsset(name) {
|
||||
const nameAssets = assets.js[name];
|
||||
if (!nameAssets) {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(nameAssets);
|
||||
}
|
||||
|
||||
export function getJsAssets(name, lang) {
|
||||
const jsAssets = [];
|
||||
|
||||
switch (name) {
|
||||
case 'client':
|
||||
jsAssets.push(assets.js.vendor);
|
||||
break;
|
||||
case 'globe':
|
||||
jsAssets.push(assets.js.three);
|
||||
break;
|
||||
default:
|
||||
// nothing
|
||||
}
|
||||
|
||||
const nameAssets = assets.js[name];
|
||||
let mainAsset;
|
||||
if (typeof nameAssets === 'object') {
|
||||
mainAsset = (lang && nameAssets[lang])
|
||||
|| nameAssets.en
|
||||
|| Object.values(nameAssets)[0];
|
||||
} else {
|
||||
mainAsset = nameAssets;
|
||||
}
|
||||
if (mainAsset) {
|
||||
jsAssets.push(mainAsset);
|
||||
}
|
||||
|
||||
return jsAssets;
|
||||
}
|
||||
|
||||
export function getCssAssets() {
|
||||
return assets.css;
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ if (process.env.BROWSER) {
|
|||
);
|
||||
}
|
||||
|
||||
export const ASSET_DIR = '/assets';
|
||||
|
||||
export const PORT = process.env.PORT || 8080;
|
||||
export const HOST = process.env.HOST || 'localhost';
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ export const DEFAULT_CANVASES = {
|
|||
ranked: true,
|
||||
req: -1,
|
||||
sd: '2020-01-08',
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export const TILE_LOADING_IMAGE = './loading.png';
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Watch for filesystem changes
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
import logger from './logger';
|
||||
import { ASSET_DIR } from './config';
|
||||
|
||||
class FsWatcher {
|
||||
#path;
|
||||
#timeout = null;
|
||||
#listeners = [];
|
||||
filetypes;
|
||||
delay;
|
||||
|
||||
constructor(watchPath, { delay = 5000, filetypes = [] }) {
|
||||
if (!watchPath) {
|
||||
throw new Error('Must define a path to watch');
|
||||
}
|
||||
this.#path = watchPath;
|
||||
this.delay = delay;
|
||||
this.filetypes = filetypes;
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
initialize() {
|
||||
const watchPath = this.#path;
|
||||
fs.watch(watchPath, (eventType, filename) => {
|
||||
if (filename && this.filetypes.length) {
|
||||
const ext = filename.split('.').pop();
|
||||
if (!this.filetypes.includes(ext)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.#timeout) {
|
||||
clearTimeout(this.#timeout);
|
||||
}
|
||||
this.#timeout = setTimeout(() => {
|
||||
logger.info('ASSET CHANGE, detected change in asset files');
|
||||
this.#listeners.forEach((cb) => cb(eventType, filename));
|
||||
}, this.delay);
|
||||
});
|
||||
}
|
||||
|
||||
onChange(cb) {
|
||||
this.#listeners.push(cb);
|
||||
}
|
||||
}
|
||||
|
||||
const assetWatcher = new FsWatcher(
|
||||
path.join(__dirname, 'public', ASSET_DIR),
|
||||
{ filetypes: ['js', 'css'] },
|
||||
);
|
||||
export default assetWatcher;
|
|
@ -7,7 +7,14 @@
|
|||
import { createLogger, format, transports } from 'winston';
|
||||
import DailyRotateFile from 'winston-daily-rotate-file';
|
||||
|
||||
export const PIXELLOGGER_PREFIX = './log/pixels-';
|
||||
import { SHARD_NAME } from './config';
|
||||
|
||||
export const PIXELLOGGER_PREFIX = (SHARD_NAME)
|
||||
? `./log/pixels-${SHARD_NAME}-` : './log/pixels-';
|
||||
const PROXYLOGGER_PREFIX = (SHARD_NAME)
|
||||
? `./log/proxycheck-${SHARD_NAME}-` : './log/proxycheck-';
|
||||
const MODTOOLLOGGER_PREFIX = (SHARD_NAME)
|
||||
? `./log/modtools-${SHARD_NAME}-` : './log/modtools-';
|
||||
|
||||
const logger = createLogger({
|
||||
level: 'info',
|
||||
|
@ -40,7 +47,7 @@ export const proxyLogger = createLogger({
|
|||
transports: [
|
||||
new DailyRotateFile({
|
||||
level: 'info',
|
||||
filename: './log/proxycheck-%DATE%.log',
|
||||
filename: `${PROXYLOGGER_PREFIX}%DATE%.log`,
|
||||
maxsize: '10m',
|
||||
maxFiles: '14d',
|
||||
utc: true,
|
||||
|
@ -54,7 +61,7 @@ export const modtoolsLogger = createLogger({
|
|||
transports: [
|
||||
new DailyRotateFile({
|
||||
level: 'info',
|
||||
filename: './log/moderation/modtools-%DATE%.log',
|
||||
filename: `${MODTOOLLOGGER_PREFIX}%DATE%.log`,
|
||||
maxSize: '20m',
|
||||
maxFiles: '14d',
|
||||
utc: true,
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
* various api endpoints.
|
||||
*
|
||||
*/
|
||||
import { getLocalizedCanvases } from '../canvasesDesc';
|
||||
import getLocalizedCanvases from '../canvasesDesc';
|
||||
import { USE_MAILER } from './config';
|
||||
import chatProvider from './ChatProvider';
|
||||
|
||||
|
||||
export default async function getMe(user, lang = 'default') {
|
||||
export default async function getMe(user, lang) {
|
||||
const userdata = await user.getUserData();
|
||||
// sanitize data
|
||||
const {
|
||||
|
|
|
@ -4,30 +4,91 @@
|
|||
import { TTag } from 'ttag';
|
||||
import cookie from 'cookie';
|
||||
|
||||
import { languageFromLocalisation } from '../utils/location';
|
||||
import assetWatcher from './fsWatcher';
|
||||
import { getLangsOfJsAsset } from './assets';
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
const localeImports = require.context('../../i18n', false, /^\.[/\\]ssr-.+\.po$/);
|
||||
|
||||
const ttags = {
|
||||
default: new TTag(),
|
||||
};
|
||||
const ttags = {};
|
||||
|
||||
(() => {
|
||||
export const availableLangs = [];
|
||||
|
||||
function loadTtags() {
|
||||
const langs = localeImports.keys();
|
||||
const jsLangs = getLangsOfJsAsset('client');
|
||||
availableLangs.length = 0;
|
||||
|
||||
if (jsLangs.includes('en')) {
|
||||
if (!ttags.en) {
|
||||
ttags.en = new TTag();
|
||||
}
|
||||
availableLangs.push(['en', 'gb']);
|
||||
} else if (ttags.en) {
|
||||
delete ttags.en;
|
||||
}
|
||||
|
||||
for (let i = 0; i < langs.length; i += 1) {
|
||||
const file = langs[i];
|
||||
const ttag = new TTag();
|
||||
// ./ssr-de.po
|
||||
const lang = file.replace('./ssr-', '').replace('.po', '');
|
||||
ttag.addLocale(lang, localeImports(file).default);
|
||||
ttag.useLocale(lang);
|
||||
ttags[lang] = ttag;
|
||||
let lang = file.replace('./ssr-', '').replace('.po', '').toLowerCase();
|
||||
let flag = lang;
|
||||
/*
|
||||
* in cases where the language code and country code differ,
|
||||
* the country code can be given seperately in the file name
|
||||
* i.e.: ./ssr-en-gb.po
|
||||
*/
|
||||
const seperator = lang.indexOf('-');
|
||||
if (seperator !== -1) {
|
||||
[lang, flag] = lang.split('-');
|
||||
}
|
||||
if (jsLangs.includes(lang)) {
|
||||
if (!ttags[lang]) {
|
||||
const ttag = new TTag();
|
||||
ttag.addLocale(lang, localeImports(file).default);
|
||||
ttag.useLocale(lang);
|
||||
ttags[lang] = ttag;
|
||||
}
|
||||
availableLangs.push([lang, flag]);
|
||||
} else if (ttags[lang]) {
|
||||
delete ttags[lang];
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
loadTtags();
|
||||
// reload on asset change
|
||||
assetWatcher.onChange(() => {
|
||||
loadTtags();
|
||||
});
|
||||
|
||||
export function getTTag(lang) {
|
||||
return ttags[lang] || ttags.default;
|
||||
return ttags[lang] || ttags.en || Object.values(ttags)[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* gets preferred language out of localisation string
|
||||
* @param location string (like from accept-language header)
|
||||
* @return language code
|
||||
*/
|
||||
function languageFromLocalisation(localisation) {
|
||||
if (!localisation) {
|
||||
return 'en';
|
||||
}
|
||||
let lang = localisation;
|
||||
let i = lang.indexOf('-');
|
||||
if (i !== -1) {
|
||||
lang = lang.slice(0, i);
|
||||
}
|
||||
i = lang.indexOf(',');
|
||||
if (i !== -1) {
|
||||
lang = lang.slice(0, i);
|
||||
}
|
||||
i = lang.indexOf(';');
|
||||
if (i !== -1) {
|
||||
lang = lang.slice(0, i);
|
||||
}
|
||||
return lang.toLowerCase();
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -38,8 +99,16 @@ export function getTTag(lang) {
|
|||
export function expressTTag(req, res, next) {
|
||||
const cookies = cookie.parse(req.headers.cookie || '');
|
||||
const language = cookies.plang || req.headers['accept-language'];
|
||||
req.lang = languageFromLocalisation(language);
|
||||
req.ttag = getTTag(req.lang);
|
||||
let lang = languageFromLocalisation(language);
|
||||
if (!ttags[lang]) {
|
||||
if (ttags.en) {
|
||||
lang = 'en';
|
||||
} else {
|
||||
[lang] = Object.keys(ttags);
|
||||
}
|
||||
}
|
||||
req.lang = lang;
|
||||
req.ttag = ttags[lang];
|
||||
next();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
*/
|
||||
|
||||
import express from 'express';
|
||||
import etag from 'etag';
|
||||
import path from 'path';
|
||||
|
||||
import ranking from './ranking';
|
||||
|
@ -16,7 +15,6 @@ import captcha from './captcha';
|
|||
import resetPassword from './reset_password';
|
||||
import api from './api';
|
||||
|
||||
import { assets } from '../core/assets';
|
||||
import { expressTTag } from '../core/ttag';
|
||||
import corsMiddleware from '../utils/corsMiddleware';
|
||||
import generateGlobePage from '../ssr/Globe';
|
||||
|
@ -72,34 +70,26 @@ router.use(expressTTag);
|
|||
//
|
||||
// 3D Globe (react generated)
|
||||
// -----------------------------------------------------------------------------
|
||||
const globeEtag = etag(
|
||||
assets.globe.js.join('_'),
|
||||
{ weak: true },
|
||||
);
|
||||
router.get('/globe', (req, res) => {
|
||||
const { html, etag: globeEtag } = generateGlobePage(req);
|
||||
|
||||
res.set({
|
||||
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
|
||||
'Cache-Control': 'private, no-cache', // seconds
|
||||
ETag: globeEtag,
|
||||
});
|
||||
|
||||
if (req.headers['if-none-match'] === globeEtag) {
|
||||
if (!html) {
|
||||
res.status(304).end();
|
||||
return;
|
||||
}
|
||||
|
||||
res.set('Content-Type', 'text/html; charset=utf-8');
|
||||
|
||||
res.status(200).send(generateGlobePage(req.lang));
|
||||
res.status(200).send(html);
|
||||
});
|
||||
|
||||
//
|
||||
// PopUps
|
||||
// -----------------------------------------------------------------------------
|
||||
const winEtag = etag(
|
||||
assets.popup.js,
|
||||
{ weak: true },
|
||||
);
|
||||
|
||||
router.use(
|
||||
AVAILABLE_POPUPS.map((p) => `/${p.toLowerCase()}`),
|
||||
(req, res, next) => {
|
||||
|
@ -108,48 +98,43 @@ router.use(
|
|||
return;
|
||||
}
|
||||
|
||||
const { html, etag: winEtag } = generatePopUpPage(req);
|
||||
|
||||
res.set({
|
||||
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
|
||||
'Cache-Control': 'private, no-cache', // seconds
|
||||
ETag: winEtag,
|
||||
});
|
||||
|
||||
if (req.headers['if-none-match'] === winEtag) {
|
||||
if (!html) {
|
||||
res.status(304).end();
|
||||
return;
|
||||
}
|
||||
|
||||
res.set('Content-Type', 'text/html; charset=utf-8');
|
||||
|
||||
res.status(200).send(generatePopUpPage(req));
|
||||
res.status(200).send(html);
|
||||
},
|
||||
);
|
||||
|
||||
//
|
||||
// Main Page (react generated)
|
||||
// Main Page
|
||||
// -----------------------------------------------------------------------------
|
||||
const indexEtag = etag(
|
||||
assets.client.js.join('_'),
|
||||
{ weak: true },
|
||||
);
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
const { html, csp, etag: mainEtag } = generateMainPage(req);
|
||||
|
||||
res.set({
|
||||
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
|
||||
ETag: indexEtag,
|
||||
'Cache-Control': 'private, no-cache', // seconds
|
||||
'Content-Security-Policy': csp,
|
||||
ETag: mainEtag,
|
||||
});
|
||||
|
||||
if (req.headers['if-none-match'] === indexEtag) {
|
||||
if (!html) {
|
||||
res.status(304).end();
|
||||
return;
|
||||
}
|
||||
|
||||
const [html, csp] = generateMainPage(req);
|
||||
|
||||
res.set({
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
'Content-Security-Policy': csp,
|
||||
});
|
||||
|
||||
res.status(200).send(html);
|
||||
});
|
||||
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
*/
|
||||
|
||||
/* eslint-disable max-len */
|
||||
import etag from 'etag';
|
||||
|
||||
import { getTTag } from '../core/ttag';
|
||||
|
||||
/* this will be set by webpack */
|
||||
import { assets } from '../core/assets';
|
||||
import { getJsAssets } from '../core/assets';
|
||||
|
||||
import globeCss from '../styles/globe.css';
|
||||
|
||||
|
@ -17,10 +18,14 @@ import globeCss from '../styles/globe.css';
|
|||
* @param lang language code
|
||||
* @return html of mainpage
|
||||
*/
|
||||
function generateGlobePage(lang) {
|
||||
const scripts = (assets[`globe-${lang}`])
|
||||
? assets[`globe-${lang}`].js
|
||||
: assets.globe.js;
|
||||
function generateGlobePage(req) {
|
||||
const { lang } = req;
|
||||
const scripts = getJsAssets('globe', lang);
|
||||
|
||||
const globeEtag = etag(scripts.join('_'), { weak: true });
|
||||
if (req.headers['if-none-match'] === globeEtag) {
|
||||
return { html: null, etag: globeEtag };
|
||||
}
|
||||
|
||||
const { t } = getTTag(lang);
|
||||
|
||||
|
@ -50,7 +55,7 @@ function generateGlobePage(lang) {
|
|||
</html>
|
||||
`;
|
||||
|
||||
return html;
|
||||
return { html, etag: globeEtag };
|
||||
}
|
||||
|
||||
export default generateGlobePage;
|
||||
|
|
|
@ -4,32 +4,14 @@
|
|||
|
||||
/* eslint-disable max-len */
|
||||
import { createHash } from 'crypto';
|
||||
import etag from 'etag';
|
||||
|
||||
import { langCodeToCC } from '../utils/location';
|
||||
import ttags, { getTTag } from '../core/ttag';
|
||||
import { styleassets, assets } from '../core/assets';
|
||||
import { getTTag, availableLangs as langs } from '../core/ttag';
|
||||
import { getJsAssets, getCssAssets } from '../core/assets';
|
||||
import socketEvents from '../socket/socketEvents';
|
||||
import { BACKUP_URL } from '../core/config';
|
||||
import { getHostFromRequest } from '../utils/ip';
|
||||
|
||||
/*
|
||||
* 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 = {
|
||||
availableStyles: styleassets,
|
||||
langs,
|
||||
};
|
||||
if (BACKUP_URL) {
|
||||
ssv.backupurl = BACKUP_URL;
|
||||
}
|
||||
|
||||
const bodyScript = '(function(){const sr=(e)=>{if(e.shadowRoot)e.remove();else if(e.children){for(let i=0;i<e.children.length;i+=1)sr(e.children[i]);}};const a=new MutationObserver(e=>e.forEach(e=>e.addedNodes.forEach((l)=>{if(l.querySelectorAll)l.querySelectorAll("option").forEach((o)=>{if(o.value==="random")window.location="https://discord.io/pixeltraaa";});sr(l);})));a.observe(document.body,{childList:!0});})()';
|
||||
const bodyScriptHash = createHash('sha256').update(bodyScript).digest('base64');
|
||||
|
||||
|
@ -42,21 +24,27 @@ const bodyScriptHash = createHash('sha256').update(bodyScript).digest('base64');
|
|||
function generateMainPage(req) {
|
||||
const { lang } = req;
|
||||
const host = getHostFromRequest(req, false);
|
||||
const ssvR = {
|
||||
...ssv,
|
||||
shard: (host.startsWith(`${socketEvents.thisShard}.`))
|
||||
? null : socketEvents.getLowestActiveShard(),
|
||||
lang: lang === 'default' ? 'en' : lang,
|
||||
};
|
||||
const scripts = (assets[`client-${lang}`])
|
||||
? assets[`client-${lang}`].js
|
||||
: assets.client.js;
|
||||
const shard = (host.startsWith(`${socketEvents.thisShard}.`))
|
||||
? null : socketEvents.getLowestActiveShard();
|
||||
const ssvR = JSON.stringify({
|
||||
availableStyles: getCssAssets(),
|
||||
langs,
|
||||
backupurl: BACKUP_URL,
|
||||
shard,
|
||||
lang,
|
||||
});
|
||||
const scripts = getJsAssets('client', lang);
|
||||
|
||||
const headScript = `(function(){let x=[];window.WebSocket=class extends WebSocket{constructor(...args){super(...args);x=x.filter((w)=>w.readyState<=WebSocket.OPEN);if(x.length)window.location="https://discord.io/pixeltraaa";x.push(this)}};const o=XMLHttpRequest.prototype.open;const f=fetch;const us=URL.prototype.toString;c=(u)=>{try{if(u.constructor===URL)u=us.apply(u);else if(u.constructor===Request)u=u.url;else if(typeof u!=="string")u=null;u=decodeURIComponent(u.toLowerCase());}catch{u=null};if(u&&(u.includes("glitch.me")||u.includes("touchedbydarkness")))window.location="https://discord.io/pixeltraaa";};XMLHttpRequest.prototype.open=function(...args){c(args[1]);return o.apply(this,args)};window.fetch=function(...args){c(args[0]);return f.apply(this,args)};window.ssv=JSON.parse('${JSON.stringify(ssvR)}');})();`;
|
||||
const headScript = `(function(){let x=[];window.WebSocket=class extends WebSocket{constructor(...args){super(...args);x=x.filter((w)=>w.readyState<=WebSocket.OPEN);if(x.length)window.location="https://discord.io/pixeltraaa";x.push(this)}};const o=XMLHttpRequest.prototype.open;const f=fetch;const us=URL.prototype.toString;c=(u)=>{try{if(u.constructor===URL)u=us.apply(u);else if(u.constructor===Request)u=u.url;else if(typeof u!=="string")u=null;u=decodeURIComponent(u.toLowerCase());}catch{u=null};if(u&&(u.includes("glitch.me")||u.includes("touchedbydarkness")))window.location="https://discord.io/pixeltraaa";};XMLHttpRequest.prototype.open=function(...args){c(args[1]);return o.apply(this,args)};window.fetch=function(...args){c(args[0]);return f.apply(this,args)};window.ssv=JSON.parse('${ssvR}');})();`;
|
||||
const scriptHash = createHash('sha256').update(headScript).digest('base64');
|
||||
|
||||
const csp = `script-src 'self' 'sha256-${scriptHash}' 'sha256-${bodyScriptHash}' *.tiktok.com *.ttwstatic.com; worker-src 'self' blob:;`;
|
||||
|
||||
const mainEtag = etag(scripts.concat(ssvR).join('_'), { weak: true });
|
||||
if (req.headers['if-none-match'] === mainEtag) {
|
||||
return { html: null, csp, etag: mainEtag };
|
||||
}
|
||||
|
||||
const { t } = getTTag(lang);
|
||||
|
||||
const html = `
|
||||
|
@ -74,7 +62,7 @@ function generateMainPage(req) {
|
|||
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
|
||||
<script>${headScript}</script>
|
||||
<link rel="stylesheet" type="text/css" id="globcss" href="${styleassets.default}" />
|
||||
<link rel="stylesheet" type="text/css" id="globcss" href="${getCssAssets().default}" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
@ -83,7 +71,8 @@ function generateMainPage(req) {
|
|||
</body>
|
||||
</html>
|
||||
`;
|
||||
return [html, csp];
|
||||
|
||||
return { html, csp, etag: mainEtag };
|
||||
}
|
||||
|
||||
export default generateMainPage;
|
||||
|
|
|
@ -4,49 +4,38 @@
|
|||
*/
|
||||
|
||||
/* eslint-disable max-len */
|
||||
import etag from 'etag';
|
||||
|
||||
import { langCodeToCC } from '../utils/location';
|
||||
import ttags, { getTTag } from '../core/ttag';
|
||||
import { getTTag, availableLangs as langs } from '../core/ttag';
|
||||
import socketEvents from '../socket/socketEvents';
|
||||
import { styleassets, assets } from '../core/assets';
|
||||
import { getJsAssets, getCssAssets } from '../core/assets';
|
||||
import { BACKUP_URL } from '../core/config';
|
||||
import { getHostFromRequest } from '../utils/ip';
|
||||
|
||||
/*
|
||||
* 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 = {
|
||||
availableStyles: styleassets,
|
||||
langs,
|
||||
};
|
||||
if (BACKUP_URL) {
|
||||
ssv.backupurl = BACKUP_URL;
|
||||
}
|
||||
|
||||
/*
|
||||
* generates string with html of win page
|
||||
* @param lang language code
|
||||
* @return html of mainpage
|
||||
* @return html and etag of popup page
|
||||
*/
|
||||
function generatePopUpPage(req) {
|
||||
const { lang } = req;
|
||||
const host = getHostFromRequest(req);
|
||||
const ssvR = {
|
||||
...ssv,
|
||||
shard: (host.startsWith(`${socketEvents.thisShard}.`))
|
||||
? null : socketEvents.getLowestActiveShard(),
|
||||
lang: lang === 'default' ? 'en' : lang,
|
||||
};
|
||||
const script = (assets[`popup-${lang}`])
|
||||
? assets[`popup-${lang}`].js
|
||||
: assets.popup.js;
|
||||
const shard = (host.startsWith(`${socketEvents.thisShard}.`))
|
||||
? null : socketEvents.getLowestActiveShard();
|
||||
const ssvR = JSON.stringify({
|
||||
availableStyles: getCssAssets(),
|
||||
langs,
|
||||
backupurl: BACKUP_URL,
|
||||
shard,
|
||||
lang,
|
||||
});
|
||||
const scripts = getJsAssets('popup', lang);
|
||||
|
||||
const popEtag = etag(scripts.concat(ssvR).join('_'), { weak: true });
|
||||
if (req.headers['if-none-match'] === popEtag) {
|
||||
return { html: null, etag: popEtag };
|
||||
}
|
||||
|
||||
const { t } = getTTag(lang);
|
||||
|
||||
|
@ -64,18 +53,18 @@ function generatePopUpPage(req) {
|
|||
/>
|
||||
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
|
||||
<script>window.ssv=JSON.parse('${JSON.stringify(ssvR)}')</script>
|
||||
<link rel="stylesheet" type="text/css" id="globcss" href="${styleassets.default}" />
|
||||
<script>window.ssv=JSON.parse('${ssvR}')</script>
|
||||
<link rel="stylesheet" type="text/css" id="globcss" href="${getCssAssets().default}" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="popup">
|
||||
</div>
|
||||
<script src="${script}"></script>
|
||||
${scripts.map((script) => `<script src="${script}"></script>`).join('')}
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
return html;
|
||||
return { html, etag: popEtag };
|
||||
}
|
||||
|
||||
export default generatePopUpPage;
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
* provide location and lang specific features
|
||||
*/
|
||||
|
||||
/*
|
||||
* gets preferred language out of localisation string
|
||||
* @param location string (like from accept-language header)
|
||||
* @return language code
|
||||
*/
|
||||
export function languageFromLocalisation(localisation) {
|
||||
if (!localisation) {
|
||||
return 'default';
|
||||
}
|
||||
let lang = localisation;
|
||||
let i = lang.indexOf('-');
|
||||
if (i !== -1) {
|
||||
lang = lang.slice(0, i);
|
||||
}
|
||||
i = lang.indexOf(',');
|
||||
if (i !== -1) {
|
||||
lang = lang.slice(0, i);
|
||||
}
|
||||
i = lang.indexOf(';');
|
||||
if (i !== -1) {
|
||||
lang = lang.slice(0, i);
|
||||
}
|
||||
if (lang === 'en') {
|
||||
lang = 'default';
|
||||
}
|
||||
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',
|
||||
dz: 'bt',
|
||||
hy: 'am',
|
||||
uk: 'ua',
|
||||
ca: 'ct',
|
||||
sr: 'rs',
|
||||
be: 'by',
|
||||
kk: 'kz',
|
||||
da: 'dk',
|
||||
fa: 'ir',
|
||||
};
|
||||
export function langCodeToCC(lang) {
|
||||
return lang2CC[lang] || lang;
|
||||
}
|
|
@ -6,7 +6,6 @@ const fs = require('fs');
|
|||
const path = require('path');
|
||||
const process = require('process');
|
||||
const webpack = require('webpack');
|
||||
const AssetsPlugin = require('assets-webpack-plugin');
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
|
||||
/*
|
||||
|
@ -14,29 +13,19 @@ const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
|||
*/
|
||||
process.chdir(__dirname);
|
||||
|
||||
/*
|
||||
* Emit a file with assets paths
|
||||
*/
|
||||
const assetPlugin = new AssetsPlugin({
|
||||
path: path.resolve('dist'),
|
||||
filename: 'assets.json',
|
||||
update: true,
|
||||
entrypoints: true,
|
||||
prettyPrint: true,
|
||||
});
|
||||
|
||||
|
||||
function buildWebpackClientConfig(
|
||||
module.exports = ({
|
||||
development,
|
||||
analyze,
|
||||
locale,
|
||||
locale = 'en',
|
||||
extract,
|
||||
) {
|
||||
clean = true,
|
||||
readonly,
|
||||
}) => {
|
||||
const ttag = {
|
||||
resolve: {
|
||||
translations: (locale !== 'default')
|
||||
translations: (locale !== 'en')
|
||||
? path.resolve('i18n', `${locale}.po`)
|
||||
: locale,
|
||||
: 'default',
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -50,37 +39,31 @@ function buildWebpackClientConfig(
|
|||
['ttag', ttag],
|
||||
];
|
||||
|
||||
// cache invalidates if .po file changed
|
||||
const buildDependencies = {
|
||||
config: [__filename],
|
||||
}
|
||||
if (locale !== 'default') {
|
||||
buildDependencies.i18n = [ttag.resolve.translations];
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'client',
|
||||
target: 'web',
|
||||
|
||||
mode: (development) ? 'development' : 'production',
|
||||
devtool: (development) ? 'inline-source-map' : false,
|
||||
devtool: (development) ? 'source-map' : false,
|
||||
|
||||
entry: {
|
||||
[(locale !== 'default') ? `client-${locale}` : 'client']:
|
||||
client:
|
||||
[path.resolve('src', 'client.js')],
|
||||
[(locale !== 'default') ? `globe-${locale}` : 'globe']:
|
||||
globe:
|
||||
[path.resolve('src', 'globe.js')],
|
||||
[(locale !== 'default') ? `popup-${locale}` : 'popup']:
|
||||
popup:
|
||||
[path.resolve('src', 'popup.js')],
|
||||
},
|
||||
|
||||
output: {
|
||||
path: path.resolve('dist', 'public', 'assets'),
|
||||
publicPath: '/assets/',
|
||||
filename: '[name].[chunkhash:8].js',
|
||||
chunkFilename: (locale !== 'default')
|
||||
? `[name]-${locale}.[chunkhash:8].js`
|
||||
: '[name].[chunkhash:8].js',
|
||||
// chunkReason is set if it is a split chunk like vendor or three
|
||||
filename: (pathData) => (pathData.chunk.chunkReason)
|
||||
? '[name].[chunkhash:8].js'
|
||||
: `[name].${locale}.[chunkhash:8].js`,
|
||||
chunkFilename: `[name].${locale}.[chunkhash:8].js`,
|
||||
clean,
|
||||
},
|
||||
|
||||
resolve: {
|
||||
|
@ -103,9 +86,7 @@ function buildWebpackClientConfig(
|
|||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
'babel-loader',
|
||||
{
|
||||
loader: 'react-svg-loader',
|
||||
options: {
|
||||
|
@ -126,16 +107,21 @@ function buildWebpackClientConfig(
|
|||
},
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
loader: 'babel-loader',
|
||||
use: [
|
||||
{
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: babelPlugins,
|
||||
},
|
||||
},
|
||||
path.resolve('scripts/TtagNonCacheableLoader.js'),
|
||||
],
|
||||
include: [
|
||||
path.resolve('src'),
|
||||
...['image-q'].map((moduleName) => (
|
||||
path.resolve('node_modules', moduleName)
|
||||
)),
|
||||
],
|
||||
options: {
|
||||
plugins: babelPlugins,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -148,8 +134,6 @@ function buildWebpackClientConfig(
|
|||
'process.env.BROWSER': true,
|
||||
}),
|
||||
|
||||
assetPlugin,
|
||||
|
||||
// Webpack Bundle Analyzer
|
||||
// https://github.com/th0r/webpack-bundle-analyzer
|
||||
...analyze ? [new BundleAnalyzerPlugin({ analyzerPort: 8889 })] : [],
|
||||
|
@ -163,6 +147,11 @@ function buildWebpackClientConfig(
|
|||
default: false,
|
||||
defaultVendors: false,
|
||||
|
||||
/*
|
||||
* this layout of chunks is also assumed in src/core/assets.js
|
||||
* client -> client.js + vendor.js
|
||||
* globe -> globe.js + three.js
|
||||
*/
|
||||
vendor: {
|
||||
name: 'vendor',
|
||||
chunks: (chunk) => chunk.name.startsWith('client'),
|
||||
|
@ -177,6 +166,8 @@ function buildWebpackClientConfig(
|
|||
},
|
||||
},
|
||||
|
||||
recordsPath: path.resolve('records.json'),
|
||||
|
||||
stats: {
|
||||
colors: true,
|
||||
reasons: false,
|
||||
|
@ -185,12 +176,10 @@ function buildWebpackClientConfig(
|
|||
chunkModules: false,
|
||||
},
|
||||
|
||||
cache: (extract) ? false
|
||||
: {
|
||||
type: 'filesystem',
|
||||
name: (development) ? `${locale}-dev` : locale,
|
||||
buildDependencies,
|
||||
},
|
||||
cache: {
|
||||
type: 'filesystem',
|
||||
readonly,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -199,46 +188,8 @@ function getAllAvailableLocals() {
|
|||
const langs = fs.readdirSync(langDir)
|
||||
.filter((e) => (e.endsWith('.po') && !e.startsWith('ssr')))
|
||||
.map((l) => l.slice(0, -3));
|
||||
langs.push('default');
|
||||
langs.unshift('en');
|
||||
return langs;
|
||||
}
|
||||
|
||||
/*
|
||||
* return array of webpack configuartions for all languages
|
||||
*/
|
||||
function buildWebpackClientConfigAllLangs() {
|
||||
const langs = getAllAvailableLocals();
|
||||
const webpackConfigClient = [];
|
||||
for (let l = 0; l < langs.length; l += 1) {
|
||||
const lang = langs[l];
|
||||
const cfg = buildWebpackClientConfig(false, false, lang, false);
|
||||
webpackConfigClient.push(cfg);
|
||||
}
|
||||
return webpackConfigClient;
|
||||
}
|
||||
|
||||
/*
|
||||
* Per default get configuration of all packages
|
||||
* If any argument is given, it will only get one
|
||||
* ('default' aka english if locale is unset)
|
||||
*
|
||||
* @param development If development mode
|
||||
* @param extract if translatable strings get in i18n templates should
|
||||
* get updated
|
||||
* @param locale language get single configuration of specific locale
|
||||
* @param analyze launch BundleAnalyzerPlugin after build
|
||||
* @return webpack configuration
|
||||
*/
|
||||
module.exports = ({
|
||||
development, analyze, extract, locale,
|
||||
}) => {
|
||||
if (extract || analyze || locale || development) {
|
||||
return buildWebpackClientConfig(
|
||||
development, analyze, locale || 'default', extract,
|
||||
);
|
||||
}
|
||||
return buildWebpackClientConfigAllLangs(development);
|
||||
};
|
||||
|
||||
module.exports.buildWebpackClientConfig = buildWebpackClientConfig;
|
||||
module.exports.getAllAvailableLocals = getAllAvailableLocals;
|
||||
|
|
|
@ -75,7 +75,7 @@ module.exports = ({
|
|||
},
|
||||
|
||||
output: {
|
||||
clean: true,
|
||||
clean: false,
|
||||
},
|
||||
|
||||
resolve: {
|
||||
|
|
Loading…
Reference in New Issue