1
0
mirror of https://github.com/tobspr/shapez.io.git synced 2025-12-11 09:11:50 +00:00

Apply formatting to the rest of files

This commit is contained in:
Даниїл Григор'єв 2024-06-20 13:02:02 +03:00
parent 5ff15f3029
commit 2f0a505297
No known key found for this signature in database
GPG Key ID: B890DF16341D8C1D
6 changed files with 423 additions and 429 deletions

View File

@ -1,19 +1,19 @@
{ {
"name": "electron", "name": "electron",
"version": "1.0.0", "version": "1.0.0",
"main": "index.js", "main": "index.js",
"license": "MIT", "license": "MIT",
"private": true, "private": true,
"scripts": { "scripts": {
"startDev": "electron --disable-direct-composition --in-process-gpu . --dev --local", "startDev": "electron --disable-direct-composition --in-process-gpu . --dev --local",
"startDevGpu": "electron --enable-gpu-rasterization --enable-accelerated-2d-canvas --num-raster-threads=8 --enable-zero-copy . --dev --local", "startDevGpu": "electron --enable-gpu-rasterization --enable-accelerated-2d-canvas --num-raster-threads=8 --enable-zero-copy . --dev --local",
"start": "electron --disable-direct-composition --in-process-gpu ." "start": "electron --disable-direct-composition --in-process-gpu ."
}, },
"devDependencies": {}, "devDependencies": {},
"optionalDependencies": {}, "optionalDependencies": {},
"dependencies": { "dependencies": {
"async-lock": "^1.4.1", "async-lock": "^1.4.1",
"electron": "^30.0.0", "electron": "^30.0.0",
"electron-window-state": "^5.0.3" "electron-window-state": "^5.0.3"
} }
} }

View File

@ -1,212 +1,212 @@
/** /**
* *
* Run `yarn global add canvas` first * Run `yarn global add canvas` first
*/ */
const { createCanvas } = require("canvas"); const { createCanvas } = require("canvas");
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const outputFolder = path.join(__dirname, "..", "wires", "sets"); const outputFolder = path.join(__dirname, "..", "wires", "sets");
const dimensions = 192; const dimensions = 192;
const lineSize = 14; const lineSize = 14;
const lowerLineSize = 32; const lowerLineSize = 32;
const variants = { const variants = {
first: "#61ef6f", first: "#61ef6f",
second: "#5fb2f1", second: "#5fb2f1",
conflict: "#f74c4c", conflict: "#f74c4c",
}; };
function hexToRGB(h) { function hexToRGB(h) {
let r = 0, let r = 0,
g = 0, g = 0,
b = 0; b = 0;
// 3 digits // 3 digits
if (h.length == 4) { if (h.length == 4) {
r = "0x" + h[1] + h[1]; r = "0x" + h[1] + h[1];
g = "0x" + h[2] + h[2]; g = "0x" + h[2] + h[2];
b = "0x" + h[3] + h[3]; b = "0x" + h[3] + h[3];
// 6 digits // 6 digits
} else if (h.length == 7) { } else if (h.length == 7) {
r = "0x" + h[1] + h[2]; r = "0x" + h[1] + h[2];
g = "0x" + h[3] + h[4]; g = "0x" + h[3] + h[4];
b = "0x" + h[5] + h[6]; b = "0x" + h[5] + h[6];
} }
return [+r, +g, +b]; return [+r, +g, +b];
} }
function RGBToHSL(r, g, b) { function RGBToHSL(r, g, b) {
// Make r, g, and b fractions of 1 // Make r, g, and b fractions of 1
r /= 255; r /= 255;
g /= 255; g /= 255;
b /= 255; b /= 255;
// Find greatest and smallest channel values // Find greatest and smallest channel values
let cmin = Math.min(r, g, b), let cmin = Math.min(r, g, b),
cmax = Math.max(r, g, b), cmax = Math.max(r, g, b),
delta = cmax - cmin, delta = cmax - cmin,
h = 0, h = 0,
s = 0, s = 0,
l = 0; l = 0;
// Calculate hue // Calculate hue
// No difference // No difference
if (delta == 0) h = 0; if (delta == 0) h = 0;
// Red is max // Red is max
else if (cmax == r) h = ((g - b) / delta) % 6; else if (cmax == r) h = ((g - b) / delta) % 6;
// Green is max // Green is max
else if (cmax == g) h = (b - r) / delta + 2; else if (cmax == g) h = (b - r) / delta + 2;
// Blue is max // Blue is max
else h = (r - g) / delta + 4; else h = (r - g) / delta + 4;
h = Math.round(h * 60); h = Math.round(h * 60);
// Make negative hues positive behind 360° // Make negative hues positive behind 360°
if (h < 0) h += 360; if (h < 0) h += 360;
// Calculate lightness // Calculate lightness
l = (cmax + cmin) / 2; l = (cmax + cmin) / 2;
// Calculate saturation // Calculate saturation
s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
// Multiply l and s by 100 // Multiply l and s by 100
s = +(s * 100).toFixed(1); s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1); l = +(l * 100).toFixed(1);
return [h, s, l]; return [h, s, l];
} }
function HSLToRGB(h, s, l) { function HSLToRGB(h, s, l) {
// Must be fractions of 1 // Must be fractions of 1
s /= 100; s /= 100;
l /= 100; l /= 100;
let c = (1 - Math.abs(2 * l - 1)) * s, let c = (1 - Math.abs(2 * l - 1)) * s,
x = c * (1 - Math.abs(((h / 60) % 2) - 1)), x = c * (1 - Math.abs(((h / 60) % 2) - 1)),
m = l - c / 2, m = l - c / 2,
r = 0, r = 0,
g = 0, g = 0,
b = 0; b = 0;
if (0 <= h && h < 60) { if (0 <= h && h < 60) {
r = c; r = c;
g = x; g = x;
b = 0; b = 0;
} else if (60 <= h && h < 120) { } else if (60 <= h && h < 120) {
r = x; r = x;
g = c; g = c;
b = 0; b = 0;
} else if (120 <= h && h < 180) { } else if (120 <= h && h < 180) {
r = 0; r = 0;
g = c; g = c;
b = x; b = x;
} else if (180 <= h && h < 240) { } else if (180 <= h && h < 240) {
r = 0; r = 0;
g = x; g = x;
b = c; b = c;
} else if (240 <= h && h < 300) { } else if (240 <= h && h < 300) {
r = x; r = x;
g = 0; g = 0;
b = c; b = c;
} else if (300 <= h && h < 360) { } else if (300 <= h && h < 360) {
r = c; r = c;
g = 0; g = 0;
b = x; b = x;
} }
r = Math.round((r + m) * 255); r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255); g = Math.round((g + m) * 255);
b = Math.round((b + m) * 255); b = Math.round((b + m) * 255);
return [r, g, b]; return [r, g, b];
} }
async function run() { async function run() {
console.log("Running"); console.log("Running");
const promises = []; const promises = [];
for (const variantId in variants) { for (const variantId in variants) {
const variantColor = variants[variantId]; const variantColor = variants[variantId];
const variantHSL = RGBToHSL(...hexToRGB(variantColor)); const variantHSL = RGBToHSL(...hexToRGB(variantColor));
const darkenedColor = HSLToRGB(variantHSL[0], variantHSL[1] - 15, variantHSL[2] - 20); const darkenedColor = HSLToRGB(variantHSL[0], variantHSL[1] - 15, variantHSL[2] - 20);
const hexDarkenedColor = "rgb(" + darkenedColor.join(",") + ")"; const hexDarkenedColor = "rgb(" + darkenedColor.join(",") + ")";
console.log(variantColor, "->", hexToRGB(variantColor), variantHSL, "->", darkenedColor); console.log(variantColor, "->", hexToRGB(variantColor), variantHSL, "->", darkenedColor);
const parts = { const parts = {
forward: [[0.5, 0, 0.5, 1]], forward: [[0.5, 0, 0.5, 1]],
turn: [ turn: [
[0.5, 0.5, 0.5, 1], [0.5, 0.5, 0.5, 1],
[0.5, 0.5, 1, 0.5], [0.5, 0.5, 1, 0.5],
], ],
split: [ split: [
[0.5, 0.5, 0.5, 1], [0.5, 0.5, 0.5, 1],
[0, 0.5, 1, 0.5], [0, 0.5, 1, 0.5],
], ],
cross: [ cross: [
[0, 0.5, 1, 0.5], [0, 0.5, 1, 0.5],
[0.5, 0, 0.5, 1], [0.5, 0, 0.5, 1],
], ],
}; };
for (const partId in parts) { for (const partId in parts) {
const partLines = parts[partId]; const partLines = parts[partId];
const canvas = createCanvas(dimensions, dimensions); const canvas = createCanvas(dimensions, dimensions);
const context = canvas.getContext("2d"); const context = canvas.getContext("2d");
context.quality = "best"; context.quality = "best";
context.clearRect(0, 0, dimensions, dimensions); context.clearRect(0, 0, dimensions, dimensions);
const lineCanvas = createCanvas(dimensions, dimensions); const lineCanvas = createCanvas(dimensions, dimensions);
const lineContext = lineCanvas.getContext("2d"); const lineContext = lineCanvas.getContext("2d");
lineContext.quality = "best"; lineContext.quality = "best";
lineContext.clearRect(0, 0, dimensions, dimensions); lineContext.clearRect(0, 0, dimensions, dimensions);
lineContext.strokeStyle = hexDarkenedColor; lineContext.strokeStyle = hexDarkenedColor;
lineContext.lineWidth = lowerLineSize; lineContext.lineWidth = lowerLineSize;
lineContext.lineCap = "square"; lineContext.lineCap = "square";
lineContext.imageSmoothingEnabled = false; lineContext.imageSmoothingEnabled = false;
// Draw lower lines // Draw lower lines
partLines.forEach(([x1, y1, x2, y2]) => { partLines.forEach(([x1, y1, x2, y2]) => {
lineContext.beginPath(); lineContext.beginPath();
lineContext.moveTo(x1 * dimensions, y1 * dimensions); lineContext.moveTo(x1 * dimensions, y1 * dimensions);
lineContext.lineTo(x2 * dimensions, y2 * dimensions); lineContext.lineTo(x2 * dimensions, y2 * dimensions);
lineContext.stroke(); lineContext.stroke();
}); });
context.globalAlpha = 0.4; context.globalAlpha = 0.4;
context.drawImage(lineCanvas, 0, 0, dimensions, dimensions); context.drawImage(lineCanvas, 0, 0, dimensions, dimensions);
context.globalAlpha = 1; context.globalAlpha = 1;
context.imageSmoothingEnabled = false; context.imageSmoothingEnabled = false;
context.lineCap = "square"; context.lineCap = "square";
context.strokeStyle = variantColor; context.strokeStyle = variantColor;
context.lineWidth = lineSize; context.lineWidth = lineSize;
// Draw upper lines // Draw upper lines
partLines.forEach(([x1, y1, x2, y2]) => { partLines.forEach(([x1, y1, x2, y2]) => {
context.beginPath(); context.beginPath();
context.moveTo(x1 * dimensions, y1 * dimensions); context.moveTo(x1 * dimensions, y1 * dimensions);
context.lineTo(x2 * dimensions, y2 * dimensions); context.lineTo(x2 * dimensions, y2 * dimensions);
context.stroke(); context.stroke();
}); });
const out = fs.createWriteStream(path.join(outputFolder, variantId + "_" + partId + ".png")); const out = fs.createWriteStream(path.join(outputFolder, variantId + "_" + partId + ".png"));
const stream = canvas.createPNGStream(); const stream = canvas.createPNGStream();
stream.pipe(out); stream.pipe(out);
promises.push(new Promise(resolve => stream.on("end", resolve))); promises.push(new Promise(resolve => stream.on("end", resolve)));
} }
} }
console.log("Waiting for completion"); console.log("Waiting for completion");
await Promise.all(promises); await Promise.all(promises);
console.log("Done!"); console.log("Done!");
} }
run(); run();

View File

@ -1,4 +1,4 @@
<!DOCTYPE html> <!doctype html>
<html> <html>
<head> <head>
<title>shapez</title> <title>shapez</title>
@ -18,5 +18,5 @@
<meta http-equiv="Cache-Control" content="private, max-age=0, no-store, no-cache, must-revalidate" /> <meta http-equiv="Cache-Control" content="private, max-age=0, no-store, no-cache, must-revalidate" />
<meta http-equiv="Expires" content="0" /> <meta http-equiv="Expires" content="0" />
</head> </head>
<body oncontextmenu="return false" style="background: #393747;"></body> <body oncontextmenu="return false" style="background: #393747"></body>
</html> </html>

View File

@ -1,33 +1,27 @@
{ {
"extends": [ "extends": ["@tsconfig/strictest/tsconfig"],
"@tsconfig/strictest/tsconfig" "include": ["./js/**/*"],
], "compilerOptions": {
"include": [ "allowJs": true,
"./js/**/*" "module": "es2022",
], "moduleResolution": "bundler",
"compilerOptions": { "noEmit": true,
"allowJs": true, "target": "ES2022",
"module": "es2022", /* JSX Compilation */
"moduleResolution": "bundler", "paths": {
"noEmit": true, "@/*": ["./js/*"]
"target": "ES2022", },
/* JSX Compilation */ "jsx": "react-jsx",
"paths": { "jsxImportSource": "@",
"@/*": [ // remove when comfortable
"./js/*" "exactOptionalPropertyTypes": false,
] "noImplicitAny": false,
}, "noImplicitOverride": false,
"jsx": "react-jsx", "noImplicitReturns": false,
"jsxImportSource": "@", "noPropertyAccessFromIndexSignature": false,
// remove when comfortable "noUncheckedIndexedAccess": false,
"exactOptionalPropertyTypes": false, "strictNullChecks": false,
"noImplicitAny": false, // eslint warns for this
"noImplicitOverride": false, "noUnusedLocals": true
"noImplicitReturns": false, }
"noPropertyAccessFromIndexSignature": false, }
"noUncheckedIndexedAccess": false,
"strictNullChecks": false,
// eslint warns for this
"noUnusedLocals": true,
}
}

View File

@ -1,80 +1,80 @@
// Synchronizes all translations // Synchronizes all translations
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const YAML = require("yaml"); const YAML = require("yaml");
const files = fs const files = fs
.readdirSync(path.join(__dirname, "translations")) .readdirSync(path.join(__dirname, "translations"))
.filter(x => x.endsWith(".yaml")) .filter(x => x.endsWith(".yaml"))
.filter(x => x.indexOf("base-en") < 0); .filter(x => x.indexOf("base-en") < 0);
const originalContents = fs const originalContents = fs
.readFileSync(path.join(__dirname, "translations", "base-en.yaml")) .readFileSync(path.join(__dirname, "translations", "base-en.yaml"))
.toString("utf-8"); .toString("utf-8");
const original = YAML.parse(originalContents); const original = YAML.parse(originalContents);
const placeholderRegexp = /[[<]([a-zA-Z_0-9/-_]+?)[\]>]/gi; const placeholderRegexp = /[[<]([a-zA-Z_0-9/-_]+?)[\]>]/gi;
function match(originalObj, translatedObj, path = "/", ignorePlaceholderMismatch = false) { function match(originalObj, translatedObj, path = "/", ignorePlaceholderMismatch = false) {
for (const key in originalObj) { for (const key in originalObj) {
if (!translatedObj.hasOwnProperty(key)) { if (!translatedObj.hasOwnProperty(key)) {
console.warn(" | Missing key", path + key); console.warn(" | Missing key", path + key);
translatedObj[key] = originalObj[key]; translatedObj[key] = originalObj[key];
continue; continue;
} }
const valueOriginal = originalObj[key]; const valueOriginal = originalObj[key];
const valueMatching = translatedObj[key]; const valueMatching = translatedObj[key];
if (typeof valueOriginal !== typeof valueMatching) { if (typeof valueOriginal !== typeof valueMatching) {
console.warn(" | MISMATCHING type (obj|non-obj) in", path + key); console.warn(" | MISMATCHING type (obj|non-obj) in", path + key);
translatedObj[key] = originalObj[key]; translatedObj[key] = originalObj[key];
continue; continue;
} }
if (typeof valueOriginal === "object") { if (typeof valueOriginal === "object") {
match(valueOriginal, valueMatching, path + key + "/", ignorePlaceholderMismatch); match(valueOriginal, valueMatching, path + key + "/", ignorePlaceholderMismatch);
} else if (typeof valueOriginal === "string") { } else if (typeof valueOriginal === "string") {
const originalPlaceholders = [...valueOriginal.matchAll(placeholderRegexp)]; const originalPlaceholders = [...valueOriginal.matchAll(placeholderRegexp)];
const translatedPlaceholders = [...valueMatching.matchAll(placeholderRegexp)]; const translatedPlaceholders = [...valueMatching.matchAll(placeholderRegexp)];
if (!ignorePlaceholderMismatch && originalPlaceholders.length !== translatedPlaceholders.length) { if (!ignorePlaceholderMismatch && originalPlaceholders.length !== translatedPlaceholders.length) {
console.warn( console.warn(
" | Mismatching placeholders in", " | Mismatching placeholders in",
path + key, path + key,
"->", "->",
originalPlaceholders, originalPlaceholders,
"vs", "vs",
translatedPlaceholders translatedPlaceholders
); );
translatedObj[key] = originalObj[key]; translatedObj[key] = originalObj[key];
continue; continue;
} }
} else { } else {
console.warn(" | Unknown type: ", typeof valueOriginal); console.warn(" | Unknown type: ", typeof valueOriginal);
} }
} }
for (const key in translatedObj) { for (const key in translatedObj) {
if (!originalObj.hasOwnProperty(key)) { if (!originalObj.hasOwnProperty(key)) {
console.warn(" | Obsolete key", path + key); console.warn(" | Obsolete key", path + key);
delete translatedObj[key]; delete translatedObj[key];
} }
} }
} }
for (let i = 0; i < files.length; ++i) { for (let i = 0; i < files.length; ++i) {
const filename = files[i]; const filename = files[i];
const filePath = path.join(__dirname, "translations", filename); const filePath = path.join(__dirname, "translations", filename);
console.log("Processing", filename); console.log("Processing", filename);
const translatedContents = fs.readFileSync(filePath).toString("utf-8"); const translatedContents = fs.readFileSync(filePath).toString("utf-8");
const json = YAML.parse(translatedContents); const json = YAML.parse(translatedContents);
match(original, json, "/", filename.toLowerCase().includes("zh-cn")); match(original, json, "/", filename.toLowerCase().includes("zh-cn"));
const stringified = YAML.stringify(json, { const stringified = YAML.stringify(json, {
indent: 4, indent: 4,
simpleKeys: true, simpleKeys: true,
}); });
fs.writeFileSync(filePath, stringified, "utf-8"); fs.writeFileSync(filePath, stringified, "utf-8");
} }

View File

@ -1,83 +1,83 @@
# Translations # Translations
The base language is English and can be found [here](base-en.yaml). The base language is English and can be found [here](base-en.yaml).
## Languages ## Languages
- [German](base-de.yaml) - [German](base-de.yaml)
- [French](base-fr.yaml) - [French](base-fr.yaml)
- [Korean](base-kor.yaml) - [Korean](base-kor.yaml)
- [Dutch](base-nl.yaml) - [Dutch](base-nl.yaml)
- [Polish](base-pl.yaml) - [Polish](base-pl.yaml)
- [Portuguese (Brazil)](base-pt-BR.yaml) - [Portuguese (Brazil)](base-pt-BR.yaml)
- [Portuguese (Portugal)](base-pt-PT.yaml) - [Portuguese (Portugal)](base-pt-PT.yaml)
- [Russian](base-ru.yaml) - [Russian](base-ru.yaml)
- [Greek](base-el.yaml) - [Greek](base-el.yaml)
- [Italian](base-it.yaml) - [Italian](base-it.yaml)
- [Romanian](base-ro.yaml) - [Romanian](base-ro.yaml)
- [Swedish](base-sv.yaml) - [Swedish](base-sv.yaml)
- [Chinese (Simplified)](base-zh-CN.yaml) - [Chinese (Simplified)](base-zh-CN.yaml)
- [Chinese (Traditional)](base-zh-TW.yaml) - [Chinese (Traditional)](base-zh-TW.yaml)
- [Spanish](base-es.yaml) - [Spanish](base-es.yaml)
- [Hungarian](base-hu.yaml) - [Hungarian](base-hu.yaml)
- [Turkish](base-tr.yaml) - [Turkish](base-tr.yaml)
- [Japanese](base-ja.yaml) - [Japanese](base-ja.yaml)
- [Lithuanian](base-lt.yaml) - [Lithuanian](base-lt.yaml)
- [Arabic](base-ar.yaml) - [Arabic](base-ar.yaml)
- [Norwegian](base-no.yaml) - [Norwegian](base-no.yaml)
- [Kroatian](base-hr.yaml) - [Kroatian](base-hr.yaml)
- [Danish](base-da.yaml) - [Danish](base-da.yaml)
- [Finnish](base-fi.yaml) - [Finnish](base-fi.yaml)
- [Catalan](base-cat.yaml) - [Catalan](base-cat.yaml)
- [Slovenian](base-sl.yaml) - [Slovenian](base-sl.yaml)
- [Ukrainian](base-uk.yaml) - [Ukrainian](base-uk.yaml)
- [Indonesian](base-ind.yaml) - [Indonesian](base-ind.yaml)
- [Serbian](base-sr.yaml) - [Serbian](base-sr.yaml)
- [Czech](base-cz.yaml) - [Czech](base-cz.yaml)
(If you want to translate into a new language, see below!) (If you want to translate into a new language, see below!)
## Editing existing translations ## Editing existing translations
If you want to edit an existing translation (Fixing typos, updating it to a newer version, etc), you can just use the github file editor to edit the file. If you want to edit an existing translation (Fixing typos, updating it to a newer version, etc), you can just use the github file editor to edit the file.
- Click the language you want to edit from the list above - Click the language you want to edit from the list above
- Click the small "edit" symbol on the top right - Click the small "edit" symbol on the top right
<img src="https://i.imgur.com/gZnUQoe.png" alt="edit symbol" width="200"> <img src="https://i.imgur.com/gZnUQoe.png" alt="edit symbol" width="200">
- Do the changes you wish to do (Be sure **not** to translate placeholders! For example, `<amount> minutes` should get `<amount> Minuten` and **not** `<anzahl> Minuten`!) - Do the changes you wish to do (Be sure **not** to translate placeholders! For example, `<amount> minutes` should get `<amount> Minuten` and **not** `<anzahl> Minuten`!)
- Click "Propose Changes" - Click "Propose Changes"
<img src="https://i.imgur.com/KT9ZFp6.png" alt="propose changes" width="200"> <img src="https://i.imgur.com/KT9ZFp6.png" alt="propose changes" width="200">
- Click "Create pull request" - Click "Create pull request"
<img src="https://i.imgur.com/oVljvRE.png" alt="create pull request" width="200"> <img src="https://i.imgur.com/oVljvRE.png" alt="create pull request" width="200">
- I will review your changes and make comments, and eventually merge them so they will be in the next release! Be sure to regulary check the created pull request for comments. - I will review your changes and make comments, and eventually merge them so they will be in the next release! Be sure to regulary check the created pull request for comments.
## Adding a new language ## Adding a new language
Please DM me on Discord (tobspr#5407), so I can add the language template for you. Please DM me on Discord (tobspr#5407), so I can add the language template for you.
**Important: I am currently not accepting new languages until the wires update is out!** **Important: I am currently not accepting new languages until the wires update is out!**
Please use the following template: Please use the following template:
``` ```
Hey, could you add a new translation? Hey, could you add a new translation?
Language: <Language, e.g. 'German'> Language: <Language, e.g. 'German'>
Short code: <Short code, e.g. 'de', see below> Short code: <Short code, e.g. 'de', see below>
Local Name: <Name of your Language, e.g. 'Deutsch'> Local Name: <Name of your Language, e.g. 'Deutsch'>
``` ```
You can find the short code [here](https://www.science.co.il/language/Codes.php) (In column `Code 2`). You can find the short code [here](https://www.science.co.il/language/Codes.php) (In column `Code 2`).
PS: I'm super busy, but I'll give my best to do it quickly! PS: I'm super busy, but I'll give my best to do it quickly!
## Updating a language to the latest version ## Updating a language to the latest version
Run `yarn syncTranslations` in the root directory to synchronize all translations to the latest version! This will remove obsolete keys and add newly added keys. (Run `yarn` before to install packages). Run `yarn syncTranslations` in the root directory to synchronize all translations to the latest version! This will remove obsolete keys and add newly added keys. (Run `yarn` before to install packages).