diff --git a/Dockerfile b/Dockerfile index 8aaf57b8d..bbfe0e2cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,7 +50,8 @@ RUN npm ci # Final image FROM base - +ARG GIT_COMMIT=unknown +ENV GIT_COMMIT="$GIT_COMMIT" RUN apt-get update && apt-get install -y \ nginx \ supervisor \ diff --git a/eslint.config.js b/eslint.config.js index 0f85fec76..b4e1bf857 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,12 +1,13 @@ import eslintConfigPrettier from "eslint-config-prettier/flat"; +import eslintPluginLocal from "./eslint-plugin-local/plugin.js"; +import { fileURLToPath } from "node:url"; import globals from "globals"; +import { includeIgnoreFile } from "@eslint/compat"; +import jest from "eslint-plugin-jest"; import path from "node:path"; import pluginJs from "@eslint/js"; -import stylisticTs from "@stylistic/eslint-plugin"; +import stylistic from "@stylistic/eslint-plugin"; import tseslint from "typescript-eslint"; -import { fileURLToPath } from "node:url"; -import { includeIgnoreFile } from "@eslint/compat"; -import eslintPluginLocal from "./eslint-plugin-local/plugin.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -43,22 +44,21 @@ export default [ { rules: { // Disable rules that would fail. The failures should be fixed, and the entries here removed. - "@typescript-eslint/no-explicit-any": "off", // https://github.com/openfrontio/OpenFrontIO/issues/1789 "@typescript-eslint/no-unused-expressions": "off", // https://github.com/openfrontio/OpenFrontIO/issues/1790 "no-case-declarations": "off", // https://github.com/openfrontio/OpenFrontIO/issues/1791 }, }, { plugins: { - "@stylistic/ts": stylisticTs, + "@stylistic": stylistic, }, rules: { // Enable rules - // '@stylistic/ts/quotes': ['error', 'single'], TODO: Enable this rule, https://github.com/openfrontio/OpenFrontIO/issues/1788 - "@stylistic/ts/indent": ["error", 2], - "@stylistic/ts/semi": "error", - "@stylistic/ts/space-infix-ops": "error", - "@stylistic/ts/type-annotation-spacing": [ + "@stylistic/quotes": ["error", "double", { avoidEscape: true }], + "@stylistic/indent": ["error", 2], + "@stylistic/semi": "error", + "@stylistic/space-infix-ops": "error", + "@stylistic/type-annotation-spacing": [ "error", { after: true, @@ -70,11 +70,13 @@ export default [ }, }, ], + "@stylistic/eol-last": "error", "@typescript-eslint/consistent-type-definitions": [ "error", "type", ], "@typescript-eslint/no-duplicate-enum-values": "error", + "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-inferrable-types": "error", "@typescript-eslint/no-mixed-enums": "error", "@typescript-eslint/no-require-imports": "error", @@ -84,12 +86,13 @@ export default [ "@typescript-eslint/prefer-includes": "error", "@typescript-eslint/prefer-literal-enum-member": "error", "@typescript-eslint/prefer-nullish-coalescing": "error", + "@typescript-eslint/prefer-readonly": "error", "eqeqeq": "error", - "indent": "off", // @stylistic/ts/indent + "indent": "off", // @stylistic/indent "sort-keys": "error", - // "@typescript-eslint/no-unsafe-argument": "error", // TODO: Enable this rule, https://github.com/openfrontio/OpenFrontIO/issues/1780 - // "@typescript-eslint/no-unsafe-assignment": "error", // TODO: Enable this rule, https://github.com/openfrontio/OpenFrontIO/issues/1781 - // "@typescript-eslint/no-unsafe-member-access": "error", // TODO: Enable this rule, https://github.com/openfrontio/OpenFrontIO/issues/1783 + "@typescript-eslint/no-unsafe-argument": "error", + "@typescript-eslint/no-unsafe-assignment": "error", + "@typescript-eslint/no-unsafe-member-access": "error", // "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], // TODO: Enable this rule, https://github.com/openfrontio/OpenFrontIO/issues/1784 "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/prefer-for-of": "error", @@ -102,18 +105,25 @@ export default [ "func-call-spacing": ["error", "never"], "function-call-argument-newline": ["error", "consistent"], "max-depth": ["error", { max: 5 }], - // "max-len": ["error", { code: 120 }], // TODO: Enable this rule, https://github.com/openfrontio/OpenFrontIO/issues/1785 - "max-lines": ["error", { max: 1065, skipBlankLines: true, skipComments: true }], + "max-len": ["error", { code: 120 }], + "max-lines": ["error", { max: 676, skipBlankLines: true, skipComments: true }], "max-lines-per-function": ["error", { max: 561 }], "no-loss-of-precision": "error", "no-multi-spaces": "error", + "no-multiple-empty-lines": ["error", { max: 1, maxEOF: 0 }], + "no-trailing-spaces": "error", "object-curly-newline": ["error", { multiline: true, consistent: true }], "object-curly-spacing": ["error", "always"], "object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }], - // "no-undef": "error", // TODO: Enable this rule, https://github.com/openfrontio/OpenFrontIO/issues/1786 + "object-shorthand": ["error", "always"], + "no-undef": "error", "no-unused-vars": "off", // @typescript-eslint/no-unused-vars + "prefer-destructuring": ["error", { + array: false, + object: true, + }], "quote-props": ["error", "consistent-as-needed"], - // 'sort-imports': 'error', // TODO: Enable this rule, https://github.com/openfrontio/OpenFrontIO/issues/1787 + "sort-imports": "error", "space-before-blocks": ["error", "always"], "space-before-function-paren": ["error", { anonymous: "always", @@ -127,9 +137,38 @@ export default [ files: [ "**/*.config.{js,ts,jsx,tsx}", "**/*.test.{js,ts,jsx,tsx}", + "tests/**/*.{js,ts,jsx,tsx}", + "eslint-plugin-local/**/*.{js,ts,jsx,tsx}", + ], + rules: { + // Disabled rules for tests, configs + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "max-len": "off", + "sort-keys": "off", + }, + }, + { + languageOptions: { + globals: { + ...globals.jest, + }, + }, + files: [ + "**/*.test.{js,ts,jsx,tsx}", + "tests/**/*.{js,ts,jsx,tsx}", + ], + plugins: ["jest"], + ...jest.configs["flat/style"], + }, + { + files: [ "src/client/**/*.{js,ts,jsx,tsx}", ], rules: { + // Disabled rules for frontend "sort-keys": "off", }, }, diff --git a/package-lock.json b/package-lock.json index 2612dafad..6b0ba88a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "js-yaml": "^4.1.0", "nanoid": "^3.3.6", "obscenity": "^0.4.3", + "seedrandom": "^3.0.5", "ts-node": "^10.9.2", "uuid": "^11.1.0", "winston": "^3.17.0", @@ -43,6 +44,7 @@ "@eslint/compat": "^1.2.7", "@eslint/js": "^9.21.0", "@stylistic/eslint-plugin": "^5.2.3", + "@swc/core": "^1.13.3", "@swc/jest": "^0.2.39", "@total-typescript/ts-reset": "^0.6.1", "@types/benchmark": "^2.1.5", @@ -57,6 +59,7 @@ "@types/msgpack5": "^3.4.6", "@types/node": "^22.10.2", "@types/pg": "^8.11.11", + "@types/seedrandom": "^3.0.8", "@types/sinon": "^17.0.3", "@types/systeminformation": "^3.23.1", "@types/ws": "^8.5.11", @@ -74,6 +77,7 @@ "eslint": "^9.21.0", "eslint-config-prettier": "^10.1.1", "eslint-formatter-gha": "^1.5.2", + "eslint-plugin-jest": "^29.0.1", "eslint-webpack-plugin": "^5.0.0", "file-loader": "^6.2.0", "globals": "^16.0.0", @@ -3600,13 +3604,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", - "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { @@ -3614,9 +3618,9 @@ } }, "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", - "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6415,7 +6419,6 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.23" @@ -6460,7 +6463,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=10" } @@ -6477,7 +6479,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=10" } @@ -6494,7 +6495,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -6511,7 +6511,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -6528,7 +6527,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -6545,7 +6543,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -6562,7 +6559,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -6579,7 +6575,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -6596,7 +6591,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -6613,7 +6607,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -6649,7 +6642,6 @@ "integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -7357,6 +7349,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/seedrandom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-3.0.8.tgz", + "integrity": "sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/send": { "version": "0.17.5", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", @@ -11168,6 +11167,32 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/eslint-plugin-jest": { + "version": "29.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.0.1.tgz", + "integrity": "sha512-EE44T0OSMCeXhDrrdsbKAhprobKkPtJTbQz5yEktysNpHeDZTAL1SfDTNKmcFfJkY6yrQLtTKZALrD3j/Gpmiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.0.0" + }, + "engines": { + "node": "^20.12.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^8.0.0", + "eslint": "^8.57.0 || ^9.0.0", + "jest": "*" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + }, + "jest": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -17969,6 +17994,12 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", diff --git a/package.json b/package.json index e0480f9d5..bf3f746b7 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@eslint/compat": "^1.2.7", "@eslint/js": "^9.21.0", "@stylistic/eslint-plugin": "^5.2.3", + "@swc/core": "^1.13.3", "@swc/jest": "^0.2.39", "@total-typescript/ts-reset": "^0.6.1", "@types/benchmark": "^2.1.5", @@ -45,6 +46,7 @@ "@types/msgpack5": "^3.4.6", "@types/node": "^22.10.2", "@types/pg": "^8.11.11", + "@types/seedrandom": "^3.0.8", "@types/sinon": "^17.0.3", "@types/systeminformation": "^3.23.1", "@types/ws": "^8.5.11", @@ -62,6 +64,7 @@ "eslint": "^9.21.0", "eslint-config-prettier": "^10.1.1", "eslint-formatter-gha": "^1.5.2", + "eslint-plugin-jest": "^29.0.1", "eslint-webpack-plugin": "^5.0.0", "file-loader": "^6.2.0", "globals": "^16.0.0", @@ -122,6 +125,7 @@ "js-yaml": "^4.1.0", "nanoid": "^3.3.6", "obscenity": "^0.4.3", + "seedrandom": "^3.0.5", "ts-node": "^10.9.2", "uuid": "^11.1.0", "winston": "^3.17.0", diff --git a/resources/images/AllianceIconWhite.svg b/resources/images/AllianceIconWhite.svg index e9f700ade..3f710dbcb 100644 --- a/resources/images/AllianceIconWhite.svg +++ b/resources/images/AllianceIconWhite.svg @@ -1,6 +1,6 @@ - + diff --git a/resources/lang/bg.json b/resources/lang/bg.json index 7eda744ae..03722d007 100644 --- a/resources/lang/bg.json +++ b/resources/lang/bg.json @@ -21,7 +21,9 @@ "instructions": "Инструкции", "how_to_play": "Как се играе", "advertise": "Рекламиране", - "wiki": "Уики" + "wiki": "Уики", + "privacy_policy": "Поверителност", + "terms_of_service": "Условия за ползване" }, "news": { "full_changelog": "Вижте пълния лист с промени", @@ -448,6 +450,7 @@ "gold": "Злато", "ports": "Пристанища", "cities": "Градове", + "factories": "Фабрики", "missile_launchers": "Ракетни силози", "sams": "Противоракетни установки земя-въздух SAM", "warships": "Бойни кораби", @@ -574,6 +577,9 @@ "grogu": "Грогу" } }, + "spawn_ad": { + "loading": "Зарежда се реклама..." + }, "auth": { "login_required": "За достъп до този уебсайт е необходим вход.", "redirecting": "Пренасочваме ви...", diff --git a/resources/lang/da.json b/resources/lang/da.json index c244aa207..55c203150 100644 --- a/resources/lang/da.json +++ b/resources/lang/da.json @@ -21,7 +21,9 @@ "instructions": "Instruktioner", "how_to_play": "Sådan spiller du", "advertise": "Annoncér", - "wiki": "Wiki" + "wiki": "Wiki", + "privacy_policy": "Privatlivspolitik", + "terms_of_service": "Servicevilkår" }, "news": { "full_changelog": "Vis hele ændringsloggen", @@ -47,7 +49,7 @@ "ui_leaderboard_desc": "Viser de bedste spillere i spillet med deres navne, procentdel af ejet land, guld og tropper. Ved at vælge 'Vis alle' vises alle spillere i spillet. Hvis du ikke ønsker at se ranglisten, klik på 'Skjul'.", "ui_control": "Betjeningspanel", "ui_control_desc": "Kontrolpanelet indeholder følgende elementer:", - "ui_pop": "Befolk — Antal enheder, maksimal befolkning og tilvækst.", + "ui_pop": "Befolkning — Antal enheder, maksimal befolkning og tilvækst.", "ui_gold": "Guld — Din beholdning og hvor hurtigt du tjener det.", "ui_troops_workers": "Tropper og arbejdere — Antallet af tildelte tropper og arbejdere. Tropper bruges til at angribe eller forsvare mod angreb. Arbejdere bruges til at generere guld. Du kan justere antallet af tropper og arbejdere med skyderen.", "ui_attack_ratio": "Angrebsforhold — Antallet af tropper, der bruges, når du angriber. Du kan justere angrebsforholdet med skyderen. Hvis du har flere angribende tropper end forsvarende, vil du tabe færre tropper under angrebet, mens færre angribende tropper øger skaden på dine tropper. Effekten gælder kun op til forholdet 2:1.", @@ -448,6 +450,7 @@ "gold": "Guld", "ports": "Havne", "cities": "Byer", + "factories": "Fabrikker", "missile_launchers": "Missilramper", "sams": "SAMs", "warships": "Krigsskibe", @@ -574,6 +577,9 @@ "grogu": "Grogu" } }, + "spawn_ad": { + "loading": "Indlæser reklame..." + }, "auth": { "login_required": "Login er påkrævet for at få adgang til denne hjemmeside.", "redirecting": "Du bliver omdirigeret...", diff --git a/resources/lang/en.json b/resources/lang/en.json index 34eec63f9..46048063f 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -453,7 +453,13 @@ "team": "Team", "owned": "Owned", "gold": "Gold", - "troops": "Troops" + "troops": "Troops", + "launchers": "Launchers", + "sams": "SAMs", + "warships": "Warships", + "cities": "Cities", + "show_control": "Show Control", + "show_units": "Show Units" }, "player_info_overlay": { "type": "Type", diff --git a/resources/lang/eo.json b/resources/lang/eo.json index 9aaad58ff..e560fa984 100644 --- a/resources/lang/eo.json +++ b/resources/lang/eo.json @@ -21,7 +21,9 @@ "instructions": "Instrukcioj", "how_to_play": "Kiel ludi", "advertise": "Reklami", - "wiki": "Vikio" + "wiki": "Vikio", + "privacy_policy": "Privateca politiko", + "terms_of_service": "Uzokondiĉoj" }, "news": { "full_changelog": "Vidi la plenan ŝanĝprotokolon", @@ -448,6 +450,7 @@ "gold": "Oro", "ports": "Havenoj", "cities": "Urboj", + "factories": "Fabrikoj", "missile_launchers": "Misillanĉiloj", "sams": "SAM-oj", "warships": "Militŝipoj", @@ -574,6 +577,9 @@ "grogu": "Grogu" } }, + "spawn_ad": { + "loading": "Ŝarĝante reklamo..." + }, "auth": { "login_required": "Ensaluto estas necesa por aliri ĉi tiun retejon.", "redirecting": "Vi estas redirektata...", diff --git a/resources/lang/fi.json b/resources/lang/fi.json index 57abee33d..84bac7cfd 100644 --- a/resources/lang/fi.json +++ b/resources/lang/fi.json @@ -21,7 +21,9 @@ "instructions": "Ohjeet", "how_to_play": "Kuinka pelata", "advertise": "Mainosta", - "wiki": "Wiki" + "wiki": "Wiki", + "privacy_policy": "Tietosuojaseloste", + "terms_of_service": "Käyttöehdot" }, "news": { "full_changelog": "Katso koko muutosloki", @@ -448,6 +450,7 @@ "gold": "Kultaa", "ports": "Satamia", "cities": "Kaupunkeja", + "factories": "Tehtaita", "missile_launchers": "Ohjussiiloja", "sams": "Ilmatorjuntaohjusjärjestelmiä", "warships": "Sota-aluksia", @@ -574,6 +577,9 @@ "grogu": "Grogu" } }, + "spawn_ad": { + "loading": "Ladataan mainosta..." + }, "auth": { "login_required": "Kirjautuminen vaaditaan tälle verkkosivustolle pääsemiseen.", "redirecting": "Sinut uudelleenohjataan...", diff --git a/resources/lang/fr.json b/resources/lang/fr.json index 4fdb679ce..745d56513 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -21,7 +21,9 @@ "instructions": "Instructions", "how_to_play": "Comment jouer ?", "advertise": "Faire de la publicité", - "wiki": "Wiki" + "wiki": "Wiki", + "privacy_policy": "Politique de confidentialité", + "terms_of_service": "Conditions d'utilisation" }, "news": { "full_changelog": "Voir le journal des modifications complet", @@ -448,6 +450,7 @@ "gold": "Or", "ports": "Ports", "cities": "Villes", + "factories": "Usines", "missile_launchers": "Lance-missiles", "sams": "SAMs", "warships": "Navires de guerre", @@ -574,6 +577,9 @@ "grogu": "Grogu" } }, + "spawn_ad": { + "loading": "Chargement de l'annonce..." + }, "auth": { "login_required": "La connexion est nécessaire pour accéder à ce site Web.", "redirecting": "Vous êtes en cours de redirection...", diff --git a/resources/lang/gl.json b/resources/lang/gl.json index 6a3448d6d..541e8543f 100644 --- a/resources/lang/gl.json +++ b/resources/lang/gl.json @@ -21,7 +21,9 @@ "instructions": "Instrucións", "how_to_play": "Como xogar", "advertise": "Promoción", - "wiki": "Wiki" + "wiki": "Wiki", + "privacy_policy": "Política de privacidade", + "terms_of_service": "Termos de servizo" }, "news": { "full_changelog": "Ve o rexistro de actualizacións completo", @@ -448,6 +450,7 @@ "gold": "Ouro", "ports": "Portos", "cities": "Cidades", + "factories": "Fábricas", "missile_launchers": "Silos", "sams": "Lanzadores SAM", "warships": "Buques", @@ -574,6 +577,9 @@ "grogu": "Grogu" } }, + "spawn_ad": { + "loading": "Cargando anuncio..." + }, "auth": { "login_required": "Debes iniciar sesión para acceder a este sitio.", "redirecting": "Estámosche a redirixir...", diff --git a/resources/lang/ja.json b/resources/lang/ja.json index 8db987012..d3a206f54 100644 --- a/resources/lang/ja.json +++ b/resources/lang/ja.json @@ -21,7 +21,9 @@ "instructions": "説明書", "how_to_play": "遊び方", "advertise": "広告", - "wiki": "ウィキ" + "wiki": "ウィキ", + "privacy_policy": "プライバシーポリシー", + "terms_of_service": "利用規約" }, "news": { "full_changelog": "完全な変更ログを見る", @@ -448,6 +450,7 @@ "gold": "資金", "ports": "港", "cities": "都市", + "factories": "工場", "missile_launchers": "ミサイル格納庫", "sams": "SAM", "warships": "戦艦", @@ -574,6 +577,9 @@ "grogu": "グローグー模様" } }, + "spawn_ad": { + "loading": "広告を読み込み中…" + }, "auth": { "login_required": "このサイトにアクセスするにはログインが必要です。", "redirecting": "リダイレクト中…", diff --git a/resources/lang/nl.json b/resources/lang/nl.json index e33a75ae1..4bec242f3 100644 --- a/resources/lang/nl.json +++ b/resources/lang/nl.json @@ -21,7 +21,9 @@ "instructions": "Instructies", "how_to_play": "Hoe spelen?", "advertise": "Adverteren", - "wiki": "Wiki" + "wiki": "Wiki", + "privacy_policy": "Privacybeleid", + "terms_of_service": "Servicevoorwaarden" }, "news": { "full_changelog": "Bekijk het volledige changelog", @@ -448,6 +450,7 @@ "gold": "Goud", "ports": "Havens", "cities": "Steden", + "factories": "Fabrieken", "missile_launchers": "Raketsilo's", "sams": "SAM-lanceerders", "warships": "Oorlogsschepen", @@ -574,6 +577,9 @@ "grogu": "Grogu" } }, + "spawn_ad": { + "loading": "Advertentie laden..." + }, "auth": { "login_required": "Login is vereist voor toegang tot deze website.", "redirecting": "Je wordt omgeleid...", diff --git a/resources/lang/ru.json b/resources/lang/ru.json index c8999445a..06ad9ea59 100644 --- a/resources/lang/ru.json +++ b/resources/lang/ru.json @@ -21,7 +21,9 @@ "instructions": "Инструкции", "how_to_play": "Как играть", "advertise": "Рекламирование", - "wiki": "Вики" + "wiki": "Вики", + "privacy_policy": "Политика конфиденциальности", + "terms_of_service": "Пользовательское соглашение" }, "news": { "full_changelog": "Смотрите весь журнал изменений", @@ -448,6 +450,7 @@ "gold": "Золото", "ports": "Порты", "cities": "Города", + "factories": "Фабрики", "missile_launchers": "Ракетные установки", "sams": "ЗРК", "warships": "Военные корабли", @@ -574,6 +577,9 @@ "grogu": "Грогу" } }, + "spawn_ad": { + "loading": "Загрузка рекламы..." + }, "auth": { "login_required": "Для доступа к этому сайту требуется войти в систему.", "redirecting": "Вы будете перенаправлены...", diff --git a/resources/lang/sk.json b/resources/lang/sk.json new file mode 100644 index 000000000..56b2a31b6 --- /dev/null +++ b/resources/lang/sk.json @@ -0,0 +1,589 @@ +{ + "lang": { + "en": "Slovak", + "native": "Slovenčina", + "svg": "sk", + "lang_code": "sk" + }, + "common": { + "close": "Zavrieť" + }, + "main": { + "title": "OpenFront (ALFA)", + "join_discord": "Pridaj sa do Discordu!", + "login_discord": "Prihlás sa cez Discord", + "checking_login": "Kontrolujem prihlásenie...", + "logged_in": "Prihlásený/á!", + "log_out": "Odhlásiť sa", + "create_lobby": "Vytvoriť miestnosť", + "join_lobby": "Pripojiť sa k miestnosti", + "single_player": "Hra jedného hráča", + "instructions": "Pokyny", + "how_to_play": "Ako hrať", + "advertise": "Inzerovať", + "wiki": "Wiki", + "privacy_policy": "Zásady ochrany osobných údajov", + "terms_of_service": "Podmienky používania" + }, + "news": { + "full_changelog": "Pozri si celý zoznam zmien", + "github_link": "na GitHube", + "title": "Zoznam zmien" + }, + "help_modal": { + "hotkeys": "Skratky", + "table_key": "Kľúč", + "table_action": "Akcia", + "action_alt_view": "Alternatívny pohľad (terén/krajiny)", + "action_attack_altclick": "Útok (keď je ľavé ťuknutie nastavené na otvorenie ponuky)", + "action_build": "Otvoriť stavebnú ponuku", + "action_emote": "Otvoriť ponuku emoji", + "action_center": "Sústrediť pohľad na hráča", + "action_zoom": "Priblížiť/oddialiť", + "action_move_camera": "Pohnúť pohľadom", + "action_ratio_change": "Znížiť/Zvýšiť pomer útoku", + "action_reset_gfx": "Obnoviť grafické nastavenia", + "ui_section": "Herné rozhranie", + "ui_leaderboard": "Rebríček", + "ui_your_team": "Tvoj tím:", + "ui_leaderboard_desc": "Ukáže vrchných hráčov a ich mená, % vlastneného územia, zlata a vojsk. Použitím „Ukázať všetkých“ ukáže všetkých hráčov v hre. Ak nechceš vidieť rebríček, ťukni „Skryť“.", + "ui_control": "Ovládací panel", + "ui_control_desc": "Ovládací panel obsahuje nasledovné prvky:", + "ui_pop": "Pop – Počet jednotiek, ktoré máš, tvoja maximálna populácia, a rýchlosť, ktorou ich získavaš.", + "ui_gold": "Zlato – Počet zlata, ktoré máš, a rýchlosť, ktorou ho získavaš.", + "ui_troops_workers": "Vojská a robotníci – Počet pridelených vojsk a robotníkov. Vojská sa používajú na útočenie alebo obranu proti útokom. Robotníci sa používajú na tvorbu zlata. Môžeš upraviť pomer vojsk a robotníkov použitím posuvníka.", + "ui_attack_ratio": "Pomer útoku – Počet vojsk, ktoré použiješ pri útoku. Pomer útoku môžeš upraviť použitím posuvníka. Čím viac útočiacich vojsk máš, tým menej ich pri útoku stratíš, zatiaľčo čím menej ich použiješ, tým rýchlejšie utrpia straty. Tento efekt neplatí nad pomer 2:1.", + "ui_events": "Panel udalostí", + "ui_events_desc": "Panel udalostí zobrazuje najnovšie udalosti, žiadosti a správy rýchleho chatu. Napríklad:", + "ui_events_alliance": "Spojenectvo – Môžeš prijať alebo odmietnuť spojenectvo. Spojenci môžu zdieľať zdroje a vojská, ale nesmú na seba útočiť. Ťuknutím na „Sústrediť“ pohne pohľad na hráča, ktorý odoslal žiadosť.", + "ui_events_attack": "Útoky - Ukazujú sa tu prichádzajúce a odoslané útoky. Ťuknutím na správu zobrazíš útok, raketu, alebo loď. Môžeš zrušiť útok ťuknutím na červené X. Bude to stáť 25% útočiacich vojsk. Ak zrušíš útok lode, loď sa vráti na pôvodné miesto a zaútoči naň, ak bolo medzitým obsadené. Rakety nemôžu byť po vystrelení zrušené.", + "ui_events_quickchat": "Rýchly čet - Vidíš tu poslané a prijaté správy. Pošli správu ťuknutím na ikonu rýchleho četu v ich ponuke informácií.", + "ui_options": "Možnosti", + "ui_options_desc": "Nájdeš tu nasledujúce prvky:", + "ui_playeroverlay": "Rozhranie hráčskych informácií", + "ui_playeroverlay_desc": "Keď posunieš myšku nad krajinu, rozhranie hráčskych informácií sa zobrazí pod možnosťami. Ukazuje typ hráča: človek, národ (múdry bot), alebo bot; postoj národa ku tebe, od nepriateľského po priateľský; a brániace vojsko, zlato, a počet bojových lodí a rôznych budov, ktoré hráč má.", + "ui_wilderness": "Divočina", + "option_pause": "Zastaviť/Pokračovať hru – Dostupné len v hre pre jedného hráča.", + "option_timer": "Časovač – Čas uplynutý od začiatku hry.", + "option_exit": "Tlačidlo ukončenia.", + "option_settings": "Nastavenia – Otvorí ponuku nastavení. V nej môžeš prepnúť alternatívny pohľad, emoji, tmavý režim, nindža (režim náhodných mien) a akciu pri ľavom stlačení.", + "radial_title": "Kruhová ponuka", + "radial_desc": "Pravým ťuknutím (alebo ťuknutím na mobile) otvorí kruhovú ponuku. Pravé ťuknutie mimo ho zatvorí. Z ponuky môžeš:", + "radial_build": "Otvoriť ponuku stavby.", + "radial_info": "Otvoriť ponuku informácií.", + "radial_boat": "Poslať (prepravnú) loď na zaútočenie na vybranú pozíciu. Dostupné len ak máš prístup k vode.", + "radial_close": "Zatvoriť ponuku.", + "info_title": "Ponuka informácií", + "info_enemy_desc": "Obsahuje informácie ako meno, zlato, a vojská vybraného hráča, či s tebou prestal obchodovať, počet rakiet vystrelených voči tebe, a či je hráč zradca. Prestať obchodovať znamená, že si nebudete navzájom posielať zlato cez obchodné lode. Dá sa to ručne („ak hráč ťukol skončiť obchodovať“, čo trvá, až obaja neťuknete „začať obchodovať“), alebo samovoľne (ak jeden z vás zradil vaše spojenectvo, čo trvá, kým sa znovu stanete spojenci alebo 5 minút). Zdradca sa ukazuje 30 sekúnd keď hráč zradil a zaútočil na hráča, s ktorým mal spojenectvo. Ikony nižšie predstavujú nasledovné interakcie:", + "info_chat": "Odošli rýchlu správu hráčovi. Vyber kategóriu, frázu, a ak fráza obsahuje [P1], vyber meno hráča, ktorým sa nahradí. Klikni na Odoslať.", + "info_target": "Umiestníš cieľ na hráča, označujúc ho pre všetkých spojencov, používané na koordináciu útoku.", + "info_alliance": "Pošleš žiadosť o spojenectvo hráčovi. Spojenci môžu zdieľať zdroje a vojská, ale nesmú na seba útočiť.", + "info_emoji": "Pošleš hráčovi emoji.", + "info_trade": "Použi „Prestať obchodovať“ na zastavenie vzájomného posielania zlata cez obchodné lode. Ak obaja ťuknete „Začať obchodovať“, znovu sa spustí.", + "info_ally_panel": "Tabuľa informácií spojenca", + "info_ally_desc": "Keď uzavrieš spojenectvo s hráčom, sprístupnia sa nasledujúce ikony:", + "ally_betray": "Zradíš svojho spojenca, čím ukončíš alianciu, zastavíš obchodovanie a oslabíš svoju obranu. Obchod medzi vami sa pozastaví na 5 minút (alebo kým sa znova nestanete spojencami) a ostatní môžu tiež prestať obchodovať. Ak druhý hráč tiež nebol zradca, budeš označený ako zradca na 30 sekúnd. Počas toho sa nad tvojím menom zobrazí ikona, a budeš mať 50% postih na obranu. Boti s tebou budú menej ochotní uzavrieť alianciu a ostatní hráči si to dvakrát rozmyslia.", + "ally_donate": "Daruj časť svojich vojsk spojencovi. Používa sa vtedy, keď má málo vojsk a je pod útokom, alebo vtedy, keď potrebuje silu navyše na zničenie nepriateľa.", + "ally_donate_gold": "Daruj časť svojho zlata svojmu spojencovi. Používa sa, keď má málo zlata a potrebuje ho na budovy, alebo keď si člen tvojho tímu šetrí na MIRV raketu.", + "build_menu_title": "Stavebná ponuka", + "build_menu_desc": "Postav ich alebo uviď, koľko z každej si už postavil:", + "build_name": "Názov", + "build_icon": "Ikona", + "build_desc": "Popis", + "build_city": "Mesto", + "build_city_desc": "Zvyšuje tvoju maximálnu populáciu. Užitočné, keď nemôžeš rozšíriť svoje územie, alebo keď si na limite populácie.", + "build_factory": "Továreň", + "build_factory_desc": "Automaticky vytvára železnice medzi blízkymi budovami a občas vytvorí vlaky.", + "build_defense": "Obranný bod", + "build_defense_desc": "Zvyšuje obranu okolo blízkych hraníc, ktoré sú zobrazené šachovnicovým vzorom. Útoky nepriateľov sú pomalšie a majú väčšie straty.", + "build_port": "Prístav", + "build_port_desc": "Dá sa postaviť len blízko vody. Umožňuje stavbu vojnových lodí. Automaticky posiela obchodné lode medzi prístavmi tvojej krajiny a iných krajín (okrem toho, keď je obchod zastavený), prinášajúc zlato obom stranám. Obchod sa automaticky zastaví, keď zaútočíš alebo si napadnutý daným hráčom. Obnoví sa po 5 minútach alebo ak sa stanete spojencami. Obchodovanie môžeš ručne prepnúť cez „Zastaviť obchodovanie“ alebo „Začať obchodovanie“.", + "build_warship": "Bojová loď", + "build_warship_desc": "Hliadkuje na území, ovláda obchodné lode, a ničí nepriateľské člny a bojové lode. Vypustí ju najbližší pristav a hliadkuje územie, ktoré ťukneš pri jej stavbe. Bojové lode môžeš ovládať ťuknutím na ne a potom ťuknutím na nové miesto, kam chceš, aby sa presunuli.", + "build_silo": "Raketové silo", + "build_silo_desc": "Umožňuje streľbu rakiet.", + "build_sam": "Systém zem-vzduch", + "build_sam_desc": "Dokáže zneškodniť nepriateľské rakety v okruhu 100 pixelov. 100% úspešnosť pre atómovú bombu, 80% pre vodíkovú bombu, a 50% pre jednotlivé hlavice MIRV. Systém zem-vzduch má 7,5 sekundový čas ochladnutia.", + "build_atom": "Atómová bomba", + "build_atom_desc": "Malá bomba, ktorá ničí územie, budovy, lode, a člny. Vypustí sa z najbližšieho raketového sila a pristane na mieste, kde klikneš na jej vybudovanie.", + "build_hydrogen": "Vodíková bomba", + "build_hydrogen_desc": "Veľká bomba. Vypustí sa z najbližšieho raketového sila a pristane na mieste, kde klikneš na jej vybudovanie.", + "build_mirv": "MIRV", + "build_mirv_desc": "Najsilnejšia bomba v hre. Rozdelí sa na menšie bomby, ktoré pokryjú obrovskú časť územia. Poškodzuje len nepriateľa, na ktorého si ťukol pri jej stavbe. Vypustí sa z najbližšieho raketového sila a pristane na mieste, kde klikneš na jej vybudovanie.", + "player_icons": "Ikony hráčov", + "icon_desc": "Príklady niektorých ikon, ktoré stretneš v hre, a čo znamenajú:", + "icon_crown": "Koruna – Toto je hráč číslo 1 v rebríčku.", + "icon_traitor": "Zlomený štít – Zradca. Tento hráč zaútočil na spojenca.", + "icon_ally": "Podanie ruky – Spojenec. Tento hráč je tvoj spojenec.", + "icon_embargo": "Prekrížený dolár – Embargo. Tento hráč prestal s tebou obchodovať.", + "icon_request": "Obálka – Žiadosť o spojenectvo. Tento hráč ti poslal žiadosť o spojenectvo.", + "info_enemy_panel": "Tabuľa informácií nepriateľa", + "exit_confirmation": "Naozaj chceš opustiť hru?" + }, + "single_modal": { + "title": "Hra jedného hráča", + "allow_alliances": "Povoliť spojenectvá", + "options_title": "Možnosti", + "bots": "Boti: ", + "bots_disabled": "Zakázané", + "disable_nations": "Zakázať krajiny", + "instant_build": "Okamžite postaviť", + "infinite_gold": "Nekonečné zlato", + "infinite_troops": "Nekonečné vojská", + "disable_nukes": "Zakázať jadrové bomby", + "enables_title": "Povoliť nastavenia", + "start": "Spustiť hru" + }, + "map": { + "map": "Mapa", + "world": "Svet", + "giantworldmap": "Obrovská svetová mapa", + "europe": "Európa", + "mena": "Blízky východ a severná Afrika", + "northamerica": "Severná Amerika", + "oceania": "Oceánia", + "blacksea": "Čierne more", + "africa": "Afrika", + "asia": "Ázia", + "mars": "Mars", + "southamerica": "Južná Amerika", + "britannia": "Británia", + "gatewaytotheatlantic": "Brána do Atlantiku", + "australia": "Austrália", + "random": "Náhodné", + "iceland": "Island", + "pangaea": "Pangea", + "eastasia": "Východná Ázia", + "betweentwoseas": "Medzi dvomi morami", + "faroeislands": "Faerské ostrovy", + "deglaciatedantarctica": "Odľadovcovaná Antarktída", + "europeclassic": "Európa (klasická)", + "falklandislands": "Falklandské ostrovy", + "baikal": "Bajkal", + "halkidiki": "Chalkidiki", + "straitofgibraltar": "Gibraltársky prieliv", + "italia": "Itália" + }, + "map_categories": { + "continental": "Kontinentálna", + "regional": "Regionálna", + "fantasy": "Ostatné" + }, + "map_component": { + "loading": "Načítavanie..." + }, + "private_lobby": { + "title": "Pripojiť sa ku súkromnej miestnosti", + "enter_id": "Zadaj ID miestnosti", + "player": "Hráč", + "players": "Hráči", + "join_lobby": "Pripojiť sa k miestnosti", + "checking": "Kontrola miestnosti...", + "not_found": "Miestnosť nenájdená. Prosím, skontroluj ID, a skús znovu.", + "error": "Nastala chyba. Prosím, skús to znova.", + "joined_waiting": "Úspešne pripojené! Čakanie na začiatok hry…" + }, + "public_lobby": { + "join": "Pripojiť sa do ďalšej hry", + "waiting": "čakajúci hráči", + "teams_Duos": "Dvojice (tímy dvoch)", + "teams_Trios": "Trojice (tímy troch)", + "teams_Quads": "Štvorice (tímy štyroch)", + "teams": "{num} tímy" + }, + "username": { + "enter_username": "Zadaj svoje používateľské meno", + "not_string": "Používateľské meno musí byť reťazec.", + "too_short": "Používateľské meno musí byť aspoň {min} znakov dlhé.", + "too_long": "Používateľské meno nesmie prekročiť {max} znakov.", + "invalid_chars": "Používateľské meno smie obsahovať iba písmená, čísla, medzery, podčiarniky, a [hranaté zátvorky]." + }, + "host_modal": { + "title": "Súkromná miestnosť", + "mode": "Režim", + "team_count": "Počet tímov", + "options_title": "Možnosti", + "bots": "Boti: ", + "bots_disabled": "Vypnuté", + "disable_nations": "Vypnúť krajiny", + "instant_build": "Okamžite postaviť", + "infinite_gold": "Nekonečné zlato", + "infinite_troops": "Nekonečné vojská", + "enables_title": "Povoliť nastavenia", + "player": "Hráč", + "players": "Hráči", + "waiting": "Čakanie na hráčov…", + "start": "Spustiť hru" + }, + "team_colors": { + "red": "Červená", + "blue": "Modrá", + "teal": "Tyrkysová", + "purple": "Fialová", + "yellow": "Žltá", + "orange": "Oranžová", + "green": "Zelená", + "bot": "Bot" + }, + "game_starting_modal": { + "title": "Hra sa začína…", + "desc": "Miestnosť sa pripravuje na spustenie. Prosím, čakaj." + }, + "difficulty": { + "difficulty": "Náročnosť", + "Relaxed": "Uvoľnená", + "Balanced": "Vyvážená", + "Intense": "Búrlivá", + "Impossible": "Nemožná" + }, + "game_mode": { + "ffa": "Každý za seba", + "teams": "Tímy" + }, + "select_lang": { + "title": "Vybrať jazyk" + }, + "unit_type": { + "city": "Mesto", + "defense_post": "Obranný bod", + "port": "Prístav", + "warship": "Bojová loď", + "missile_silo": "Raketové silo", + "sam_launcher": "Systém zem-vzduch", + "atom_bomb": "Atómová bomba", + "hydrogen_bomb": "Vodíková bomba", + "mirv": "MIRV", + "factory": "Továreň" + }, + "user_setting": { + "title": "Užívateľské nastavenia", + "tab_basic": "Základné nastavenia", + "tab_keybinds": "Skratky", + "dark_mode_label": "Tmavý režim", + "dark_mode_desc": "Prepne vzhľad stránky medzi svetlou a tmavou témou", + "dark_mode_enabled": "Tmavý režim spustený", + "light_mode_enabled": "Svetlý režim spustený", + "emojis_label": "Emoji", + "emojis_visible": "Emoji sú viditeľné", + "emojis_hidden": "Emoji sú skryté", + "emojis_desc": "Prepne, či sú v hre ukázané emoji", + "alert_frame_label": "Rámik upozornenia", + "alert_frame_desc": "Prepnúť rámik upozornenia. Pri povolení sa zobrazí rám, ak ťa niekto zradí.", + "special_effects_label": "Špeciálne efekty", + "special_effects_desc": "Prepni špeciálne efekty. Vypni pre zlepšenie výkonu", + "special_effects_enabled": "Špeciálne efekty zapnuté", + "special_effects_disabled": "Špeciálne efekty vypnuté", + "anonymous_names_label": "Skryté mená", + "anonymous_names_desc": "Skry ozajstné mená hráčov na obrazovke náhodnými.", + "anonymous_names_enabled": "Anonýmne mená povolené", + "real_names_shown": "Ukázané ozajstné mená", + "left_click_label": "Ťukni ľavým tlačidlom na otvorenie ponuky", + "left_click_desc": "Keď je toto zapnuté, ľavé ťuknutie otvorí ponuku a útočí sa cez tlačidlo mečov. Keď je toto vypnuté, ľavé ťuknutie útočí priamo.", + "left_click_menu": "Ponuka ľavého ťuknutia", + "left_click_opens_menu": "Ľavé ťuknutie otvorí ponuku", + "right_click_opens_menu": "Pravé ťuknutie otvorí ponuku", + "attack_ratio_label": "⚔️ Pomer útoku", + "attack_ratio_desc": "Percento tvojich vojsk na odoslanie do útoku (1 až 100%)", + "troop_ratio_label": "🪖🛠️ Pomer vojsk a robotníkov", + "troop_ratio_desc": "Upraví pomer medzi vojskami (na boj) a robotníkmi (na tvorbu zlata) (1 až 100%)", + "territory_patterns_label": "🏳️ Územné vzorce", + "territory_patterns_desc": "Vyber, či zobrazovať dizajny vzorcov území v hre", + "easter_writing_speed_label": "Znásobovač rýchlosti písania", + "easter_writing_speed_desc": "Upraví, ako rýchlo predstieraš, že kóduješ (x1 až x100)", + "easter_bug_count_label": "Počet chýb", + "easter_bug_count_desc": "S koľkými chybami si v pohode (0 až 1000, pocitovo)", + "view_options": "Možnosti zobrazenia", + "toggle_view": "Prepnúť zobrazenie", + "toggle_view_desc": "Alternatívny pohľad (terén/krajiny)", + "attack_ratio_controls": "Nastavenia pomeru útoku", + "attack_ratio_up": "Zýšiť pomer útoku", + "attack_ratio_up_desc": "Zýšiť pomer útoku o 10%", + "attack_ratio_down": "Znížiť pomer útoku", + "attack_ratio_down_desc": "Znížiť pomer útoku o 10%", + "attack_keybinds": "Klávesové skratky útoku", + "boat_attack": "Útok loďou", + "boat_attack_desc": "Pošli útok loďou na pixel pod tvojou myškou.", + "ground_attack": "Pozemný útok", + "ground_attack_desc": "Pošli pozemný útok na pixel pod tvojou myškou.", + "zoom_controls": "Tlačidlá priblíženia", + "zoom_out": "Oddialiť", + "zoom_out_desc": "Oddialiť mapu", + "zoom_in": "Priblížiť", + "zoom_in_desc": "Priblížiť mapu", + "camera_movement": "Pohyb pohľadu", + "center_camera": "Zaostriť pohľad na stred", + "center_camera_desc": "Sústrediť pohľad na hráča", + "move_up": "Pohnúť pohľad hore", + "move_up_desc": "Pohnúť pohľad smerom hore", + "move_left": "Pohnúť pohľad vľavo", + "move_left_desc": "Pohnúť pohľad smerom vľavo", + "move_down": "Pohnúť pohľad dolu", + "move_down_desc": "Pohnúť pohľad smerom dolu", + "move_right": "Pohnúť pohľad vpravo", + "move_right_desc": "Pohnúť pohľad smerom vpravo", + "reset": "Obnoviť", + "unbind": "Odnastaviť", + "on": "Zapnúť", + "off": "Vypnúť", + "toggle_terrain": "Prepnúť povrch", + "terrain_enabled": "Viditeľnosť povrchu zapnutá", + "terrain_disabled": "Viditeľnosť povrchu vypnutá", + "exit_game_label": "Opustiť hru", + "exit_game_info": "Vrátiť sa do hlavnej ponuky" + }, + "chat": { + "title": "Rýchly čet", + "to": "Poslané {user}: {msg}", + "from": "Od {user}: {msg}", + "category": "Kategória", + "phrase": "Fráza", + "player": "Hráč", + "send": "Poslať", + "search": "Hľadať hráča…", + "build": "Napíš svoju správu…", + "cat": { + "help": "Nápoveda", + "attack": "Útok", + "defend": "Obrana", + "greet": "Pozdravujem", + "misc": "Ostatné", + "warnings": "Upozornenia" + }, + "help": { + "troops": "Prosím, pošli mi vojská!", + "gold": "Prosím, pošli mi zlato!", + "no_attack": "Prosím, neútoč na mňa!", + "sorry_attack": "Prepáč, nechcel som zaútočiť.", + "alliance": "Spojenectvo?", + "help_defend": "Pomôž mi brániť sa pred [P1]!", + "team_up": "Zaútočme na [P1]!" + }, + "attack": { + "attack": "Zaútoč na [P1]!", + "mirv": "Vystrel MIRV na [P1]!", + "focus": "Sústreď streľbu na [P1]!", + "finish": "Dokončime [P1]!" + }, + "defend": { + "defend": "Obraňuj [P1]!", + "dont_attack": "Neútoč na [P1]!", + "ally": "[P1] je môj spojenec!" + }, + "greet": { + "hello": "Ahoj!", + "good_luck": "Veľa šťastia!", + "have_fun": "Zabav sa!", + "gg": "GG!", + "nice_to_meet": "Rád ťa spoznávam!", + "well_played": "Dobrá hra!", + "hi_again": "Vitaj späť!", + "bye": "Dovidenia!", + "thanks": "Ďakujem!", + "oops": "Ups, zlé tlačidlo!", + "trust_me": "Môžeš mi veriť. Prisahám!", + "trust_broken": "Veril som ti…" + }, + "misc": { + "go": "Poďme na to!", + "strategy": "Dobrá stratégia!", + "fun": "Táto hra bola zábavná!", + "pr": "Kedy sa moje PR konečne zlúči…?" + }, + "warnings": { + "strong": "[P1] je silný/á.", + "weak": "[P1] je slabý/a.", + "mirv_soon": "[P1] môže čoskoro vystreliť MIRV!", + "number1_warning": "Hráč č. 1 môže čoskoro vyhrať, ak sa nespojíme!", + "stalemate": "Uzatvorme mier. Toto je pat, obaja prehráme.", + "has_allies": "[P1] má veľa spojencov.", + "no_allies": "[P1] nemá spojencov.", + "betrayed": "[P1] zradil/a svojho spojenca!", + "getting_big": "[P1] rastie prirýchlo!", + "danger_base": "[P1] je nechránený/á!", + "saving_for_mirv": "[P1] šetrí na vystrelenie MIRV.", + "mirv_ready": "[P1] má dosť zlata na vystrelenie MIRV!" + } + }, + "build_menu": { + "desc": { + "atom_bomb": "Malý výbuch", + "hydrogen_bomb": "Veľký výbuch", + "mirv": "Veľký výbuch, útočí len na cieleného hráča", + "missile_silo": "Používa sa na streľbu rakíet", + "sam_launcher": "Bráni proti vystreleným raketám", + "warship": "Ovláda obchodné lode, ničí lode a člny", + "port": "Posiela obchodné lode na tvorbu zlata", + "defense_post": "Zvyšuje obranu okolitých hraníc", + "city": "Zvyšuje maximálnu populáciu", + "factory": "Tvorí železnice a vlaky" + }, + "not_enough_money": "Nedostatok zlata" + }, + "win_modal": { + "died": "Zomrel/a si", + "your_team": "Tvoj tím vyhral!", + "other_team": "Vyhral tím {team}!", + "you_won": "Vyhral/a si!", + "other_won": "Vyhral hráč {player}!", + "exit": "Opustiť hru", + "keep": "Hrať ďalej", + "wishlist": "Pridaj do zoznamu želaní na Steame!" + }, + "leaderboard": { + "title": "Rebríček", + "hide": "Skryť", + "rank": "Poradie", + "player": "Hráč", + "team": "Tím", + "owned": "Vlastnené", + "gold": "Zlato", + "troops": "Vojská" + }, + "player_info_overlay": { + "type": "Typ", + "bot": "Bot", + "nation": "Národ", + "player": "Hráč", + "team": "Tím", + "d_troops": "Brániace vojsko", + "a_troops": "Útočiace vojsko", + "gold": "Zlato", + "ports": "Prístavy", + "cities": "Mestá", + "factories": "Továrne", + "missile_launchers": "Raketomety", + "sams": "Systémy zem-vzduch", + "warships": "Bojové lode", + "health": "Život", + "attitude": "Postoj", + "levels": "Levely" + }, + "events_display": { + "retreating": "ustupuje", + "boat": "Čln", + "alliance_request_status": "{name} {status} tvoju žiadosť o spojenectvo", + "alliance_accepted": "prijaté", + "alliance_rejected": "odmietnuté", + "duration_second": "1 sekunda", + "betrayal_description": "Prerušil/a si spojenectvo s {name}, kvôli čomu si ZRADCA ({malusPercent} % obranné oslabenie po dobu {durationText})", + "duration_seconds_plural": "{seconds} sekúnd", + "betrayed_you": "{name} prerušil/a s tebou spojenectvo", + "about_to_expire": "Tvoje spojenectvo s {name} čoskoro vyprší!", + "alliance_expired": "Tvoje spojenectvo s {name} vypršala", + "attack_request": "{name} žiada o napadnutie {target}", + "sent_emoji": "Poslané {name}: {emoji}", + "renew_alliance": "Požiadať o obnovenie", + "request_alliance": "{name} žiada o spojenectvo!", + "focus": "Zaostriť", + "accept_alliance": "Prijať", + "reject_alliance": "Odmietnuť", + "alliance_renewed": "Tvoje spojenectvo s {name} bola obnovená", + "ignore": "Ignorovať" + }, + "unit_info_modal": { + "structure_info": "Informácie o štruktúre", + "unit_type_unknown": "Neznáme", + "close": "Zavrieť", + "cooldown": "Obnovenie", + "type": "Typ", + "upgrade": "Vylepšenie", + "level": "Úroveň" + }, + "relation": { + "hostile": "Nepriateľský", + "distrustful": "Nedôverčivý", + "neutral": "Neutrálny", + "friendly": "Priateľský", + "default": "Základný" + }, + "control_panel": { + "pop": "Populácia", + "gold": "Zlato", + "troops": "Vojská", + "workers": "Robotníci", + "attack_ratio": "Pomer útoku" + }, + "player_panel": { + "gold": "Zlato", + "troops": "Vojská", + "betrayals": "Počet zrád", + "traitor": "Zradca", + "alliance_time_remaining": "Spojenectvo vyprší o", + "embargo": "Prestal s tebou obchodovať", + "nuke": "Rakety vystrelené voči tebe", + "start_trade": "Začať obchodovať", + "stop_trade": "Skončiť obchodovať", + "yes": "Áno", + "no": "Nie", + "none": "Žiadne", + "alliances": "Spojenectvá" + }, + "replay_panel": { + "replay_speed": "Rýchlosť prehratia", + "game_speed": "Rýchlosť hry", + "fastest_game_speed": "max" + }, + "error_modal": { + "crashed": "Hra sa pokazila!", + "connection_error": "Chyba pripojenia!", + "paste_discord": "Prosím, prilep nasledujúci obsah v tvojom hlásení chyby v Discorde:", + "copy_clipboard": "Kopírovať do schránky", + "copied": "Skopírované!", + "failed_copy": "Nepodarilo sa skopírovať", + "desync_notice": "Tvoja hra je desynchronizovaná od ostatných hráčov. To, čo vidíš, sa môže líšiť od nich." + }, + "heads_up_message": { + "choose_spawn": "Vyber začiatočnú polohu" + }, + "territory_patterns": { + "title": "Vybrať územný vzorec", + "purchase": "Kúpiť", + "blocked": { + "login": "Musíš byť prihlásená/ý na prístup k tomuto vzorcu.", + "purchase": "Kúp si tento vzorec na jeho odomknutie." + }, + "pattern": { + "default": "Základné", + "custom": "Vlastné", + "stripes_v": "Zvislé", + "stripes_h": "Vodorovné", + "horizontal_stripes": "Vodorovné (alt)", + "vertical_bars": "Zvislé (alt)", + "checkerboard": "Šachovnica", + "choco": "Čoko", + "diagonal": "Priečne", + "cross": "Krížové", + "mini_cross": "Mini krížové", + "sword": "Meč", + "sparse_dots": "Riedko bodkované", + "evan": "Evan", + "diagonal_stripe": "Šikmé pásiky", + "mountain_ridge": "Horský hrebeň", + "scattered_dots": "Rozptýlené bodky", + "circuit_board": "Doska plošných spojov", + "shells": "Mušle", + "-w-": ".w.", + "white_rabbit": "Biely zajac", + "goat": "Koza", + "cats": "Mačky", + "cursor": "Kurzor", + "hand": "Ruka", + "radiation": "Radiácia", + "openfront_qr": "QR kód OpenFront.io", + "openfront": "OpenFront", + "t_rex": "T-rex", + "embelem": "Odznak", + "grogu_head": "Hlava Grogu", + "grogu": "Grogu" + } + }, + "spawn_ad": { + "loading": "Načítavam reklamu..." + }, + "auth": { + "login_required": "Na prístup k tejto stránke je potrebné prihlásenie.", + "redirecting": "Práve si presmerovaná/ý...", + "not_authorized": "Nemáš oprávnenie na prístup k tejto stránke.", + "contact_admin": "Ak si myslíš, že túto správu vidíš omylom, prosím kontaktuj administrátora webstránky." + } +} diff --git a/resources/lang/sl.json b/resources/lang/sl.json index c47822ae4..1bc9f894d 100644 --- a/resources/lang/sl.json +++ b/resources/lang/sl.json @@ -21,7 +21,9 @@ "instructions": "Navodila", "how_to_play": "Kako igrati", "advertise": "Oglaševanje", - "wiki": "Wiki" + "wiki": "Wiki", + "privacy_policy": "Politika zasebnosti", + "terms_of_service": "Pogoji storitve" }, "news": { "full_changelog": "Poglej celotni seznam sprememb", @@ -448,6 +450,7 @@ "gold": "Zlato", "ports": "Luka", "cities": "Mesta", + "factories": "Tovarne", "missile_launchers": "Raketni silosi", "sams": "SAM", "warships": "Bojna ladja", @@ -574,6 +577,9 @@ "grogu": "Grogu" } }, + "spawn_ad": { + "loading": "Nalaganje oglasa..." + }, "auth": { "login_required": "Za dostop do te spletne strani je potrebna prijava.", "redirecting": "Preusmerjeni ste...", diff --git a/resources/lang/sv-SE.json b/resources/lang/sv-SE.json index f6ef3ad3b..de6923f55 100644 --- a/resources/lang/sv-SE.json +++ b/resources/lang/sv-SE.json @@ -21,7 +21,9 @@ "instructions": "Instruktioner", "how_to_play": "Hur man Spelar", "advertise": "Annonsera", - "wiki": "Wiki" + "wiki": "Wiki", + "privacy_policy": "Sekretesspolicy", + "terms_of_service": "Användarvillkor" }, "news": { "full_changelog": "Se fullständig ändringslogg", @@ -448,6 +450,7 @@ "gold": "Guld", "ports": "Hamnar", "cities": "Städer", + "factories": "Fabriker", "missile_launchers": "Missilavfyringsramper", "sams": "Luftvärn", "warships": "Krigsskepp", @@ -574,6 +577,9 @@ "grogu": "Grogu" } }, + "spawn_ad": { + "loading": "Laddar annons..." + }, "auth": { "login_required": "Inloggning krävs för att komma åt den här webbplatsen.", "redirecting": "Du blir omdirigerad...", diff --git a/resources/lang/uk.json b/resources/lang/uk.json index ccf7c7ce6..62e01058e 100644 --- a/resources/lang/uk.json +++ b/resources/lang/uk.json @@ -21,7 +21,9 @@ "instructions": "Інструкції", "how_to_play": "Як грати", "advertise": "Рекламування", - "wiki": "Вікі" + "wiki": "Вікі", + "privacy_policy": "Політика конфіденційності", + "terms_of_service": "Умови користування" }, "news": { "full_changelog": "Перегляньте повний журнал змін", @@ -53,7 +55,7 @@ "ui_attack_ratio": "Коефіцієнт атаки — Кількість військ, що беруть участь в атаці. Ви можете налаштувати коефіцієнт атаки за допомогою повзунка. Якщо наступальних військ більше ніж оборонних, то буде зменшено втрати під час атаки, а якщо менше — буде збільшено шкоду, що буде завдано вашим наступальним військам. Ефективність не збільшується після коефіцієнту 2:1.", "ui_events": "Панель подій", "ui_events_desc": "На панелі подій показуються останні події, запити та повідомлення швидкого чату. Деякими прикладами є:", - "ui_events_alliance": "Союз — Запити на укладення союзів можуть бути прийняті або відхилені. Союзники можуть ділитися ресурсами та військами, але не можуть атакувати один одного. Клацання на кнопку «Оглянути» переміщає камеру на гравця, який надіслав запит.", + "ui_events_alliance": "Союз — Запрошення до союзів можуть бути прийняті або відхилені. Союзники можуть ділитися ресурсами та військами, але не можуть атакувати один одного. Клацання на кнопку «Оглянути» переміщає камеру на гравця, який надіслав запит.", "ui_events_attack": "Атаки — Відображення вхідних та вихідних атак. Натисніть на повідомлення, щоб центрувати камеру на наступ, ракету або човен (транспортний корабель). Ви можете відкликати війська, натиснувши на червону кнопку «X». Це коштуватиме життя 25% ваших військ, що атакують. Якщо відкликати човен, то він повернеться до свого початкового розташування та атакуватиме, якщо територію було захоплено. Ракети неможливо відкликати після запуску.", "ui_events_quickchat": "Швидкий чат — Тут ви можете переглядати надіслані та отримані повідомлення. Надішліть повідомлення гравцю клацанням на значок швидкого чату в його меню інформації.", "ui_options": "Налаштування", @@ -72,14 +74,14 @@ "radial_boat": "Відправити човен (транспортний корабель) атакувати вибране розташування. Доступно лише якщо ви маєте доступ до води.", "radial_close": "Закрити меню.", "info_title": "Меню інформації", - "info_enemy_desc": "Містить таку інформацію про вибраного гравця, як його ім'я, кількість золото, війська, стан торгувілі з вами, кількість запущених на вас ракет і мітку зрадника. Припинення торгівля означає, що ви не отримуватиме золото від гравця, і він не надсилатиме вам золото торговельними кораблями. Свідомо (якщо гравець натиснув «Припинити торгівлю», що триває, поки ви обидва не натиснете «Розпочати торгівлю») або автоматично (якщо ви зрадили союз, що триває, поки ви знову не станете союзниками або через 5 хвилин). Поле «Зрадник» показує стан «Так» протягом 30 секунд після того, як гравець зрадив й атакував гравця, який перебував у союзні з ним. Значки нижче позначають такі взаємодії:", + "info_enemy_desc": "Містить таку інформацію про вибраного гравця, як його ім'я, кількість золота, військ, стан торгувілі з вами, кількість запущених на вас ракет і мітку зрадника. Припинення торгівля означає, що ви не отримуватиме золото від гравця, а він не надсилатиме вам золото торговельними кораблями. Свідомо (якщо гравець натиснув «Припинити торгівлю», що триває, поки ви обидва не натиснете «Розпочати торгівлю») або автоматично (якщо ви зрадили союз, що триває, поки ви знову не станете союзниками або через 5 хвилин). Поле «Зрадник» показує стан «Так» протягом 30 секунд після того, як гравець зрадив й атакував гравця, який перебував у союзні з ним. Значки нижче позначають такі взаємодії:", "info_chat": "Надсилає швидке повідомлення гравцю. Виберіть категорію, фразу та, якщо фраза містить слово «[P1]», оберіть ім'я гравця, котрим бажаєте заміни його. Тицьніть «Надіслати».", "info_target": "Розмістити мітку цілі на гравці, позначивши його для всіх союзників. Використовується для координації атак.", "info_alliance": "Надіслати гравцю запит на союз. Союзники можуть ділитися ресурсами та військами, але не можуть атакувати один одного.", "info_emoji": "Надіслати емоджі гравцю.", "info_trade": "Використайте «Припинити торгівлю», щоб припинити давати гравцеві золото та отримувати золото від нього через торгові кораблі. Якщо ви обидва натиснете «Розпочати торгівлю», вона розпочнеться знову.", "info_ally_panel": "Панель інформації союзника", - "info_ally_desc": "Коли ви укладете альянс із гравцем, стануть доступними наступні значки:", + "info_ally_desc": "Коли ви укладете союз із гравцем, буде розблоковано наступні значки:", "ally_betray": "Зрадьте свого союзника, розірвавши союз, припинивши торгівлю та послабивши свою оборону. Торгівля між вами призупиняється на 5 хвилин (або до відновлення союзу), і інші також можуть припинити торгівлю з вами. Якщо інший гравець сам не був зрадником, ви отримаєте мітку зрадника на 30 секунд. У цей час над вашим ім'ям з'явиться особливий значок, а ваша оборона знизиться на 50%. Боти рідше укладатимуть із вами союзи, а гравці двічі подумають, перш ніж мати з вами справу.", "ally_donate": "Пожертвувати частину своїх військ союзнику. Використовується, коли в нього мало військ і його атакують, або коли йому необхідна додаткова сила для знищення ворога.", "ally_donate_gold": "Пожертвувати частину свого золота союзнику. Використовуйте, коли в нього мало золота, яке він потребує для будівель, або коли член команди заощаджує на РГЧ ІН.", @@ -95,7 +97,7 @@ "build_defense": "Пункт оборони", "build_defense_desc": "Підсилює оборону навколо найближчих кордонів, що показано візерунком у клітинку. Атаки ворогів уповільнені та несуть більше жертв.", "build_port": "Порт", - "build_port_desc": "Можуть бути збудовані лише біля води. Дозволяє будувати військові кораблі. Автоматично відправляє торгові кораблі між портами вашої та інших країн (крім випадків, коли торгівлю припинено), даючи золото обом сторонам. Торгівля автоматично припиняється коли ви атакуєте гравця або він атакує вас. Вона відновлюється через 5 хвилин або якщо ви укладаєте альянс. Ви можете вручну керувати торгівлею кнопками «Припинити торгівлю» та «Розпочати торгівлю».", + "build_port_desc": "Можуть бути збудовані лише біля води. Дозволяє будувати військові кораблі. Автоматично відправляє торгові кораблі між портами вашої та інших країн (крім випадків, коли торгівлю припинено), даючи золото обом сторонам. Торгівля автоматично припиняється, коли ви атакуєте гравця або він атакує вас. Її буде відновлено через 5 хвилин або при укладанні союзу. Можна керувати торгівлею вручну за допомогою кнопок «Припинити торгівлю» та «Розпочати торгівлю».", "build_warship": "Військовий корабель", "build_warship_desc": "Розвідує територію, захоплюючи ворожі торгові кораблі й знищуючи їхні човни (транспортні кораблі) та військові кораблі. З'являється з найближчого порту та розвідує ділянку, вибрану клацанням при створенні. Військовими кораблями можна керувати кнопкою атаки (див. дія «Атака» в розділі «Гарячі клавіші»): спочатку клацніть на корабель, а потім — на ділянку, до якої бажаєте його перемістити.", "build_silo": "Ракетна шахта", @@ -261,7 +263,7 @@ "dark_mode_desc": "Перемикання зовнішнього вигляду сайту між світлою та темною темою", "dark_mode_enabled": "Увімкнено темний режим", "light_mode_enabled": "Увімкнено світлий режим", - "emojis_label": "Емодзі", + "emojis_label": "Емоджі", "emojis_visible": "Емоджі показані", "emojis_hidden": "Емоджі приховані", "emojis_desc": "Увімкнення/вимкнення видимості емоджі під час гри", @@ -448,6 +450,7 @@ "gold": "Золото", "ports": "Порти", "cities": "Міста", + "factories": "Фабрики", "missile_launchers": "Ракетні установки", "sams": "ЗРК", "warships": "Військові кораблі", @@ -458,7 +461,7 @@ "events_display": { "retreating": "відступає", "boat": "Човен", - "alliance_request_status": "{name} {status} ваш запит альянсу", + "alliance_request_status": "{name} {status} запрошення до союзу", "alliance_accepted": "прийняв", "alliance_rejected": "відхилив", "duration_second": "1 сек", @@ -574,6 +577,9 @@ "grogu": "Ґроґу" } }, + "spawn_ad": { + "loading": "Завантаження реклами..." + }, "auth": { "login_required": "Для доступу до цього сайту потрібно ввійти в систему.", "redirecting": "Вас буде переспрямовано...", diff --git a/resources/lang/zh-CN.json b/resources/lang/zh-CN.json index d8d2b932a..803b9ffe6 100644 --- a/resources/lang/zh-CN.json +++ b/resources/lang/zh-CN.json @@ -21,7 +21,9 @@ "instructions": "操作说明", "how_to_play": "如何游玩", "advertise": "广告", - "wiki": "游戏百科" + "wiki": "游戏百科", + "privacy_policy": "隐私政策", + "terms_of_service": "服务条款" }, "news": { "full_changelog": "查看完整的更新日志", @@ -448,6 +450,7 @@ "gold": "黄金", "ports": "港口", "cities": "城市", + "factories": "工厂", "missile_launchers": "导弹发射井", "sams": "防空塔", "warships": "军舰", @@ -574,6 +577,9 @@ "grogu": "格罗古" } }, + "spawn_ad": { + "loading": "正在加载广告……" + }, "auth": { "login_required": "需要登录才能访问此网站。", "redirecting": "正在将您重新定向……", diff --git a/resources/maps/world/manifest.json b/resources/maps/world/manifest.json index 809cc192c..abd1ceb9c 100644 --- a/resources/maps/world/manifest.json +++ b/resources/maps/world/manifest.json @@ -57,7 +57,7 @@ "coordinates": [637, 567], "flag": "br", "name": "Brazil", - "strength": 1 + "strength": 2 }, { "coordinates": [1280, 975], @@ -201,7 +201,7 @@ "coordinates": [1178, 351], "flag": "sa", "name": "Saudi Arabia", - "strength": 1 + "strength": 1.5 }, { "coordinates": [1679, 657], @@ -213,7 +213,7 @@ "coordinates": [1890, 775], "flag": "nz", "name": "New Zealand", - "strength": 0.5 + "strength": 1 }, { "coordinates": [918, 342], @@ -261,7 +261,7 @@ "coordinates": [1075, 707], "flag": "za", "name": "South Africa", - "strength": 1 + "strength": 1.5 }, { "coordinates": [1194, 627], @@ -274,6 +274,108 @@ "flag": "td", "name": "Chad", "strength": 1 + }, + { + "coordinates": [1030, 665], + "flag": "na", + "name": "Namibia", + "strength": 0.5 + }, + { + "coordinates": [1632, 465], + "flag": "ph", + "name": "Philippines", + "strength": 1 + }, + { + "coordinates": [1537, 426], + "flag": "th", + "name": "Thailand", + "strength": 1 + }, + { + "coordinates": [1610, 364], + "flag": "tw", + "name": "Taiwan", + "strength": 0.5 + }, + { + "coordinates": [1710, 290], + "flag": "jp", + "name": "Japan", + "strength": 1 + }, + { + "coordinates": [1869, 119], + "flag": "ru", + "name": "Siberia", + "strength": 1 + }, + { + "coordinates": [74, 117], + "flag": "polar_bears", + "name": "Polar Bears", + "strength": 2 + }, + { + "coordinates": [419, 975], + "flag": "aq", + "name": "West Antarctica", + "strength": 2 + }, + { + "coordinates": [542, 603], + "flag": "pe", + "name": "Peru", + "strength": 1 + }, + { + "coordinates": [1075, 615], + "flag": "zm", + "name": "Zambia", + "strength": 1 + }, + { + "coordinates": [1099, 165], + "flag": "lv", + "name": "Latvia", + "strength": 0.5 + }, + { + "coordinates": [1427, 336], + "flag": "bt", + "name": "Bhutan", + "strength": 0.5 + }, + { + "coordinates": [1511, 524], + "flag": "id", + "name": "Indonesia", + "strength": 1.5 + }, + { + "coordinates": [1809, 977], + "flag": "aq", + "name": "East Antarctica", + "strength": 2 + }, + { + "coordinates": [1255, 382], + "flag": "om", + "name": "Oman", + "strength": 0.75 + }, + { + "coordinates": [853, 373], + "flag": "ma", + "name": "Morocco", + "strength": 1 + }, + { + "coordinates": [656, 678], + "flag": "uy", + "name": "Uruguay", + "strength": 1 } ] } diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index b96cd8257..c5204a532 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -1,30 +1,3 @@ -import { translateText } from "../client/Utils"; -import { EventBus } from "../core/EventBus"; -import { - ClientID, - GameID, - GameRecord, - GameStartInfo, - PlayerRecord, - ServerMessage, -} from "../core/Schemas"; -import { createGameRecord } from "../core/Util"; -import { ServerConfig } from "../core/configuration/Config"; -import { getConfig } from "../core/configuration/ConfigLoader"; -import { PlayerActions, UnitType } from "../core/game/Game"; -import { TileRef } from "../core/game/GameMap"; -import { GameMapLoader } from "../core/game/GameMapLoader"; -import { - ErrorUpdate, - GameUpdateType, - GameUpdateViewData, - HashUpdate, - WinUpdate, -} from "../core/game/GameUpdates"; -import { GameView, PlayerView } from "../core/game/GameView"; -import { loadTerrainMap, TerrainMapData } from "../core/game/TerrainMapLoader"; -import { UserSettings } from "../core/game/UserSettings"; -import { WorkerClient } from "../core/worker/WorkerClient"; import { AutoUpgradeEvent, DoBoatAttackEvent, @@ -33,9 +6,24 @@ import { MouseMoveEvent, MouseUpEvent, } from "./InputHandler"; -import { endGame, startGame, startTime } from "./LocalPersistantStats"; -import { getPersistentID } from "./Main"; -import { terrainMapFileLoader } from "./TerrainMapFileLoader"; +import { + ClientID, + GameID, + GameRecord, + GameStartInfo, + PlayerRecord, + ServerMessage, +} from "../core/Schemas"; +import { + ErrorUpdate, + GameUpdateType, + GameUpdateViewData, + HashUpdate, + WinUpdate, +} from "../core/game/GameUpdates"; +import { GameRenderer, createRenderer } from "./graphics/GameRenderer"; +import { GameView, PlayerView } from "../core/game/GameView"; +import { PlayerActions, UnitType } from "../core/game/Game"; import { SendAttackIntentEvent, SendBoatAttackIntentEvent, @@ -44,8 +32,20 @@ import { SendUpgradeStructureIntentEvent, Transport, } from "./Transport"; +import { TerrainMapData, loadTerrainMap } from "../core/game/TerrainMapLoader"; +import { endGame, startGame, startTime } from "./LocalPersistantStats"; +import { EventBus } from "../core/EventBus"; +import { GameMapLoader } from "../core/game/GameMapLoader"; +import { ServerConfig } from "../core/configuration/Config"; +import { TileRef } from "../core/game/GameMap"; +import { UserSettings } from "../core/game/UserSettings"; +import { WorkerClient } from "../core/worker/WorkerClient"; import { createCanvas } from "./Utils"; -import { createRenderer, GameRenderer } from "./graphics/GameRenderer"; +import { createGameRecord } from "../core/Util"; +import { getConfig } from "../core/configuration/ConfigLoader"; +import { getPersistentID } from "./Main"; +import { terrainMapFileLoader } from "./TerrainMapFileLoader"; +import { translateText } from "../client/Utils"; export type LobbyConfig = { serverConfig: ServerConfig; @@ -193,16 +193,16 @@ export class ClientGameRunner { private lastMousePosition: { x: number; y: number } | null = null; private lastMessageTime = 0; - private connectionCheckInterval: NodeJS.Timeout | null = null; + private connectionCheckInterval: ReturnType | null = null; constructor( - private lobby: LobbyConfig, - private eventBus: EventBus, - private renderer: GameRenderer, - private input: InputHandler, - private transport: Transport, - private worker: WorkerClient, - private gameView: GameView, + private readonly lobby: LobbyConfig, + private readonly eventBus: EventBus, + private readonly renderer: GameRenderer, + private readonly input: InputHandler, + private readonly transport: Transport, + private readonly worker: WorkerClient, + private readonly gameView: GameView, ) { this.lastMessageTime = Date.now(); } @@ -288,7 +288,7 @@ export class ClientGameRunner { this.saveGame(gu.updates[GameUpdateType.Win][0]); } }); - const worker = this.worker; + const { worker } = this; const keepWorkerAlive = () => { if (this.isActive) { worker.sendHeartbeat(); @@ -477,7 +477,7 @@ export class ClientGameRunner { upgradeUnits.push({ unitId: bu.canUpgrade, unitType: bu.type, - distance: distance, + distance, }); } } @@ -563,7 +563,7 @@ export class ClientGameRunner { (bu) => bu.type === UnitType.TransportShip, ); if (bu === undefined) { - console.warn(`no transport ship buildable units`); + console.warn("no transport ship buildable units"); return false; } return ( diff --git a/src/client/Cosmetics.ts b/src/client/Cosmetics.ts index 1d5389f57..be185cee4 100644 --- a/src/client/Cosmetics.ts +++ b/src/client/Cosmetics.ts @@ -1,10 +1,10 @@ -import { z } from "zod"; +import { Cosmetics, CosmeticsSchema, Pattern } from "../core/CosmeticSchemas"; import { StripeCreateCheckoutSessionResponseSchema, UserMeResponse, } from "../core/ApiSchemas"; -import { Cosmetics, CosmeticsSchema, Pattern } from "../core/CosmeticSchemas"; import { getApiBase, getAuthHeader } from "./jwt"; +import { z } from "zod"; export async function patterns( userMe: UserMeResponse | null, @@ -44,7 +44,7 @@ export async function handlePurchase(priceId: string) { "authorization": getAuthHeader(), }, body: JSON.stringify({ - priceId: priceId, + priceId, successUrl: `${window.location.origin}#purchase-completed=true`, cancelUrl: `${window.location.origin}#purchase-completed=false`, }), diff --git a/src/client/DarkModeButton.ts b/src/client/DarkModeButton.ts index 5284ca323..e3544f8ff 100644 --- a/src/client/DarkModeButton.ts +++ b/src/client/DarkModeButton.ts @@ -4,7 +4,7 @@ import { UserSettings } from "../core/game/UserSettings"; @customElement("dark-mode-button") export class DarkModeButton extends LitElement { - private userSettings: UserSettings = new UserSettings(); + private readonly userSettings: UserSettings = new UserSettings(); @state() private darkMode: boolean = this.userSettings.darkMode(); createRenderRoot() { @@ -21,7 +21,7 @@ export class DarkModeButton extends LitElement { window.removeEventListener("dark-mode-changed", this.handleDarkModeChanged); } - private handleDarkModeChanged = (e: Event) => { + private readonly handleDarkModeChanged = (e: Event) => { const event = e as CustomEvent<{ darkMode: boolean }>; this.darkMode = event.detail.darkMode; }; diff --git a/src/client/FlagInput.ts b/src/client/FlagInput.ts index aeb1f0493..e5aeabc21 100644 --- a/src/client/FlagInput.ts +++ b/src/client/FlagInput.ts @@ -1,6 +1,8 @@ import { LitElement, css, html } from "lit"; import { customElement, state } from "lit/decorators.js"; +import { FlagSchema } from "../core/Schemas"; import { renderPlayerFlag } from "../core/CustomFlag"; + const flagKey = "flag"; @customElement("flag-input") @@ -41,10 +43,24 @@ export class FlagInput extends LitElement { ); } + private readonly updateFlag = (ev: Event) => { + const e = ev as CustomEvent<{ flag: string }>; + if (!FlagSchema.safeParse(e.detail.flag).success) return; + if (this.flag !== e.detail.flag) { + this.flag = e.detail.flag; + } + }; + connectedCallback() { super.connectedCallback(); this.flag = this.getStoredFlag(); this.dispatchFlagEvent(); + window.addEventListener("flag-change", this.updateFlag as EventListener); + } + + disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener("flag-change", this.updateFlag as EventListener); } createRenderRoot() { @@ -56,12 +72,15 @@ export class FlagInput extends LitElement {
@@ -80,7 +99,7 @@ export class FlagInput extends LitElement { renderPlayerFlag(this.flag, preview); } else { const img = document.createElement("img"); - img.src = this.flag ? `/flags/${this.flag}.svg` : `/flags/xx.svg`; + img.src = this.flag ? `/flags/${this.flag}.svg` : "/flags/xx.svg"; img.style.width = "100%"; img.style.height = "100%"; img.style.objectFit = "contain"; diff --git a/src/client/FlagInputModal.ts b/src/client/FlagInputModal.ts index eb4b65ad3..c207383ed 100644 --- a/src/client/FlagInputModal.ts +++ b/src/client/FlagInputModal.ts @@ -4,12 +4,13 @@ import Countries from "./data/countries.json"; @customElement("flag-input-modal") export class FlagInputModal extends LitElement { - @query("o-modal") private modalEl!: HTMLElement & { + @query("o-modal") private readonly modalEl!: HTMLElement & { open: () => void; close: () => void; }; @state() private search = ""; + @state() private isModalOpen = false; createRenderRoot() { return this; @@ -19,7 +20,11 @@ export class FlagInputModal extends LitElement { return html` - ${Countries.filter( - (country) => - country.name.toLowerCase().includes(this.search.toLowerCase()) || - country.code.toLowerCase().includes(this.search.toLowerCase()), + ${this.isModalOpen ? Countries.filter( + (country) => !country.restricted && this.includedInSearch(country), ).map( (country) => html` `, - )} + ) : html``} `; } + private includedInSearch(country: { name: string; code: string }): boolean { + return ( + country.name.toLowerCase().includes(this.search.toLowerCase()) || + country.code.toLowerCase().includes(this.search.toLowerCase()) + ); + } + private handleSearch(event: Event) { this.search = (event.target as HTMLInputElement).value; } @@ -80,9 +90,11 @@ export class FlagInputModal extends LitElement { } public open() { + this.isModalOpen = true; this.modalEl?.open(); } public close() { + this.isModalOpen = false; this.modalEl?.close(); } @@ -96,7 +108,7 @@ export class FlagInputModal extends LitElement { super.disconnectedCallback(); } - private handleKeyDown = (e: KeyboardEvent) => { + private readonly handleKeyDown = (e: KeyboardEvent) => { if (e.code === "Escape") { e.preventDefault(); this.close(); diff --git a/src/client/HelpModal.ts b/src/client/HelpModal.ts index 2c9e33cbb..e7ec2d181 100644 --- a/src/client/HelpModal.ts +++ b/src/client/HelpModal.ts @@ -1,12 +1,12 @@ +import "./components/Difficulties"; +import "./components/Maps"; import { LitElement, html } from "lit"; import { customElement, query } from "lit/decorators.js"; import { getAltKey, getModifierKey, translateText } from "../client/Utils"; -import "./components/Difficulties"; -import "./components/Maps"; @customElement("help-modal") export class HelpModal extends LitElement { - @query("o-modal") private modalEl!: HTMLElement & { + @query("o-modal") private readonly modalEl!: HTMLElement & { open: () => void; close: () => void; }; @@ -25,7 +25,7 @@ export class HelpModal extends LitElement { super.disconnectedCallback(); } - private handleKeyDown = (e: KeyboardEvent) => { + private readonly handleKeyDown = (e: KeyboardEvent) => { if (e.code === "Escape") { e.preventDefault(); this.close(); diff --git a/src/client/HostLobbyModal.ts b/src/client/HostLobbyModal.ts index d684939b6..b4d2b4179 100644 --- a/src/client/HostLobbyModal.ts +++ b/src/client/HostLobbyModal.ts @@ -1,8 +1,14 @@ -import { LitElement, html } from "lit"; -import { customElement, query, state } from "lit/decorators.js"; -import randomMap from "../../resources/images/RandomMap.webp"; -import { translateText } from "../client/Utils"; -import { getServerConfigFromClient } from "../core/configuration/ConfigLoader"; +/* eslint-disable max-lines */ +import "./components/Difficulties"; +import "./components/Maps"; +import "./components/baseComponents/Modal"; +import { + ClientInfo, + GameConfig, + GameInfo, + GameInfoSchema, + TeamCountConfig, +} from "../core/Schemas"; import { Difficulty, Duos, @@ -13,25 +19,20 @@ import { UnitType, mapCategories, } from "../core/game/Game"; -import { UserSettings } from "../core/game/UserSettings"; -import { - ClientInfo, - GameConfig, - GameInfo, - GameInfoSchema, - TeamCountConfig, -} from "../core/Schemas"; -import { generateID } from "../core/Util"; -import "./components/baseComponents/Modal"; -import "./components/Difficulties"; +import { LitElement, html } from "lit"; +import { customElement, query, state } from "lit/decorators.js"; import { DifficultyDescription } from "./components/Difficulties"; -import "./components/Maps"; import { JoinLobbyEvent } from "./Main"; +import { UserSettings } from "../core/game/UserSettings"; +import { generateID } from "../core/Util"; +import { getServerConfigFromClient } from "../core/configuration/ConfigLoader"; +import randomMap from "../../resources/images/RandomMap.webp"; import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions"; +import { translateText } from "../client/Utils"; @customElement("host-lobby-modal") export class HostLobbyModal extends LitElement { - @query("o-modal") private modalEl!: HTMLElement & { + @query("o-modal") private readonly modalEl!: HTMLElement & { open: () => void; close: () => void; }; @@ -54,10 +55,10 @@ export class HostLobbyModal extends LitElement { @state() private lobbyCreatorClientID = ""; @state() private lobbyIdVisible = true; - private playersInterval: NodeJS.Timeout | null = null; + private playersInterval: ReturnType | null = null; // Add a new timer for debouncing bot changes private botsUpdateTimer: number | null = null; - private userSettings: UserSettings = new UserSettings(); + private readonly userSettings: UserSettings = new UserSettings(); connectedCallback() { super.connectedCallback(); @@ -69,7 +70,7 @@ export class HostLobbyModal extends LitElement { super.disconnectedCallback(); } - private handleKeyDown = (e: KeyboardEvent) => { + private readonly handleKeyDown = (e: KeyboardEvent) => { if (e.code === "Escape") { e.preventDefault(); this.close(); @@ -100,7 +101,13 @@ export class HostLobbyModal extends LitElement { xmlns="http://www.w3.org/2000/svg" > ` : html` ` @@ -678,7 +694,9 @@ export class HostLobbyModal extends LitElement { await this.putGameConfig(); console.log( - `Starting private game with map: ${GameMapType[this.selectedMap as keyof typeof GameMapType]} ${this.useRandomMap ? " (Randomly selected)" : ""}`, + `Starting private game with map: ${ + GameMapType[this.selectedMap as keyof typeof GameMapType]} ${ + this.useRandomMap ? " (Randomly selected)" : ""}`, ); this.close(); const config = await getServerConfigFromClient(); diff --git a/src/client/InputHandler.ts b/src/client/InputHandler.ts index 5f0ab44e6..8ff2db2f0 100644 --- a/src/client/InputHandler.ts +++ b/src/client/InputHandler.ts @@ -1,8 +1,8 @@ import { EventBus, GameEvent } from "../core/EventBus"; +import { ReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier"; import { UnitType } from "../core/game/Game"; import { UnitView } from "../core/game/GameView"; import { UserSettings } from "../core/game/UserSettings"; -import { ReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier"; export class MouseUpEvent implements GameEvent { constructor( @@ -70,7 +70,7 @@ export class AlternateViewEvent implements GameEvent { export class CloseViewEvent implements GameEvent {} -export class RefreshGraphicsEvent implements GameEvent {} +export class RedrawGraphicsEvent implements GameEvent {} export class TogglePerformanceOverlayEvent implements GameEvent {} @@ -121,7 +121,7 @@ export class InputHandler { private lastPointerDownX = 0; private lastPointerDownY = 0; - private pointers: Map = new Map(); + private readonly pointers: Map = new Map(); private lastPinchDistance = 0; @@ -129,18 +129,18 @@ export class InputHandler { private alternateView = false; - private moveInterval: NodeJS.Timeout | null = null; - private activeKeys = new Set(); + private moveInterval: ReturnType | null = null; + private readonly activeKeys = new Set(); private keybinds: Record = {}; private readonly PAN_SPEED = 5; private readonly ZOOM_SPEED = 10; - private userSettings: UserSettings = new UserSettings(); + private readonly userSettings: UserSettings = new UserSettings(); constructor( - private canvas: HTMLCanvasElement, - private eventBus: EventBus, + private readonly canvas: HTMLCanvasElement, + private readonly eventBus: EventBus, ) {} initialize() { @@ -302,7 +302,7 @@ export class InputHandler { if (e.key.toLowerCase() === "r" && e.altKey && !e.ctrlKey) { e.preventDefault(); - this.eventBus.emit(new RefreshGraphicsEvent()); + this.eventBus.emit(new RedrawGraphicsEvent()); } if (e.code === this.keybinds.boatAttack) { diff --git a/src/client/JoinPrivateLobbyModal.ts b/src/client/JoinPrivateLobbyModal.ts index d67697836..4f4aa2989 100644 --- a/src/client/JoinPrivateLobbyModal.ts +++ b/src/client/JoinPrivateLobbyModal.ts @@ -1,29 +1,29 @@ -import { LitElement, html } from "lit"; -import { customElement, query, state } from "lit/decorators.js"; -import { translateText } from "../client/Utils"; +import "./components/baseComponents/Button"; +import "./components/baseComponents/Modal"; import { GameInfo, GameInfoSchema } from "../core/Schemas"; -import { generateID } from "../core/Util"; +import { LitElement, html } from "lit"; import { WorkerApiArchivedGameLobbySchema, WorkerApiGameIdExistsSchema, } from "../core/WorkerSchemas"; -import { getServerConfigFromClient } from "../core/configuration/ConfigLoader"; +import { customElement, query, state } from "lit/decorators.js"; import { JoinLobbyEvent } from "./Main"; -import "./components/baseComponents/Button"; -import "./components/baseComponents/Modal"; +import { generateID } from "../core/Util"; +import { getServerConfigFromClient } from "../core/configuration/ConfigLoader"; +import { translateText } from "../client/Utils"; @customElement("join-private-lobby-modal") export class JoinPrivateLobbyModal extends LitElement { - @query("o-modal") private modalEl!: HTMLElement & { + @query("o-modal") private readonly modalEl!: HTMLElement & { open: () => void; close: () => void; }; - @query("#lobbyIdInput") private lobbyIdInput!: HTMLInputElement; + @query("#lobbyIdInput") private readonly lobbyIdInput!: HTMLInputElement; @state() private message = ""; @state() private hasJoined = false; @state() private players: string[] = []; - private playersInterval: NodeJS.Timeout | null = null; + private playersInterval: ReturnType | null = null; connectedCallback() { super.connectedCallback(); @@ -35,7 +35,7 @@ export class JoinPrivateLobbyModal extends LitElement { super.disconnectedCallback(); } - private handleKeyDown = (e: KeyboardEvent) => { + private readonly handleKeyDown = (e: KeyboardEvent) => { if (e.code === "Escape") { e.preventDefault(); this.close(); @@ -67,7 +67,13 @@ export class JoinPrivateLobbyModal extends LitElement { xmlns="http://www.w3.org/2000/svg" > diff --git a/src/client/LangSelector.ts b/src/client/LangSelector.ts index ed27a1328..445479717 100644 --- a/src/client/LangSelector.ts +++ b/src/client/LangSelector.ts @@ -1,6 +1,8 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +import "./LanguageModal"; import { LitElement, html } from "lit"; import { customElement, state } from "lit/decorators.js"; -import "./LanguageModal"; import ar from "../../resources/lang/ar.json"; import bg from "../../resources/lang/bg.json"; @@ -24,6 +26,7 @@ import pl from "../../resources/lang/pl.json"; import pt_BR from "../../resources/lang/pt-BR.json"; import ru from "../../resources/lang/ru.json"; import sh from "../../resources/lang/sh.json"; +import sk from "../../resources/lang/sk.json"; import sl from "../../resources/lang/sl.json"; import sv_SE from "../../resources/lang/sv-SE.json"; import tp from "../../resources/lang/tp.json"; @@ -36,13 +39,15 @@ export class LangSelector extends LitElement { @state() public translations: Record | undefined; @state() public defaultTranslations: Record | undefined; @state() public currentLang = "en"; + // eslint-disable-next-line @typescript-eslint/no-explicit-any @state() private languageList: any[] = []; @state() private showModal = false; @state() private debugMode = false; private debugKeyPressed = false; - private languageMap: Record = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private readonly languageMap: Record = { ar, bg, bn, @@ -71,6 +76,7 @@ export class LangSelector extends LitElement { ko, gl, sl, + sk, }; createRenderRoot() { @@ -123,6 +129,7 @@ export class LangSelector extends LitElement { private loadLanguage(lang: string): Record { const language = this.languageMap[lang] ?? {}; + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const flat = flattenTranslations(language); return flat; } @@ -130,6 +137,7 @@ export class LangSelector extends LitElement { private async loadLanguageList() { try { const data = this.languageMap; + // eslint-disable-next-line @typescript-eslint/no-explicit-any let list: any[] = []; const browserLang = new Intl.Locale(navigator.language).language; @@ -146,6 +154,7 @@ export class LangSelector extends LitElement { }); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any let debugLang: any = null; if (this.debugKeyPressed) { debugLang = { @@ -177,10 +186,12 @@ export class LangSelector extends LitElement { list.sort((a, b) => a.en.localeCompare(b.en)); + // eslint-disable-next-line @typescript-eslint/no-explicit-any const finalList: any[] = []; if (currentLangEntry) finalList.push(currentLangEntry); if (englishEntry) finalList.push(englishEntry); if (browserLangEntry) finalList.push(browserLangEntry); + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument finalList.push(...list); if (debugLang) finalList.push(debugLang); @@ -236,7 +247,9 @@ export class LangSelector extends LitElement { components.forEach((tag) => { document.querySelectorAll(tag).forEach((el) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any if (typeof (el as any).requestUpdate === "function") { + // eslint-disable-next-line @typescript-eslint/no-explicit-any (el as any).requestUpdate(); } }); @@ -292,7 +305,12 @@ export class LangSelector extends LitElement {