diff --git a/gulp/gulpfile.js b/gulp/gulpfile.js index a4b63dec..945af62a 100644 --- a/gulp/gulpfile.js +++ b/gulp/gulpfile.js @@ -67,6 +67,9 @@ docs.gulptasksDocs($, gulp, buildFolder); const standalone = require("./standalone"); standalone.gulptasksStandalone($, gulp, buildFolder); +const translations = require("./translations"); +translations.gulptasksTranslations($, gulp, buildFolder); + // FIXME // const cordova = require("./cordova"); // cordova.gulptasksCordova($, gulp, buildFolder); @@ -74,10 +77,12 @@ standalone.gulptasksStandalone($, gulp, buildFolder); ///////////////////// BUILD TASKS ///////////////////// // Cleans up everything -gulp.task("utils.cleanup", () => { +gulp.task("utils.cleanBuildFolder", () => { return gulp.src(buildFolder, { read: false }).pipe($.clean({ force: true })); }); +gulp.task("utils.cleanup", $.sequence("utils.cleanBuildFolder", "translations.clear")); + // Requires no uncomitted files gulp.task("utils.requireCleanWorkingTree", cb => { const output = $.trim(execSync("git status -su").toString("ascii")); @@ -142,6 +147,9 @@ function serve({ standalone }) { // Watch sound files // gulp.watch(["../res_raw/sounds/**/*.mp3", "../res_raw/sounds/**/*.wav"], ["sounds.dev"]); + // Watch translations + gulp.watch("../translations/**/*.yaml", ["translations.convertToJson"]); + gulp.watch( ["../res_raw/sounds/ui/*.mp3", "../res_raw/sounds/ui/*.wav"], $.sequence("sounds.encodeUi", "sounds.copy") @@ -163,11 +171,15 @@ function serve({ standalone }) { gulp.watch("../res_built/atlas/*.json", ["imgres.atlas"]); // Watch the build folder and reload when anything changed - const extensions = ["html", "js", "png", "jpg", "svg", "mp3", "ico", "woff2"]; + const extensions = ["html", "js", "png", "jpg", "svg", "mp3", "ico", "woff2", "json"]; gulp.watch(extensions.map(ext => path.join(buildFolder, "**", "*." + ext))).on("change", function (e) { return gulp.src(e.path).pipe(browserSync.reload({ stream: true })); }); + gulp.watch("../src/js/translations-built/*.json").on("change", function (e) { + return gulp.src(e.path).pipe(browserSync.reload({ stream: true })); + }); + // Start the webpack watching server (Will never return) if (standalone) { $.sequence("js.standalone-dev.watch")(() => true); @@ -202,7 +214,7 @@ gulp.task("build.dev", cb => { "sounds.dev", "imgres.copyImageResources", "imgres.copyNonImageResources", - "js.dev", + "translations.fullBuild", "css.dev", "html.dev" )(cb); @@ -216,6 +228,7 @@ gulp.task("build.standalone.dev", cb => { "sounds.dev", "imgres.copyImageResources", "imgres.copyNonImageResources", + "translations.fullBuild", "js.standalone-dev", "css.dev", "html.standalone-dev" @@ -223,7 +236,7 @@ gulp.task("build.standalone.dev", cb => { }); // Builds everything (staging) -gulp.task("step.staging.code", $.sequence("js.staging")); +gulp.task("step.staging.code", $.sequence("translations.fullBuild", "js.staging")); gulp.task("step.staging.mainbuild", cb => $.multiProcess(["utils.copyAdditionalBuildFiles", "step.baseResources", "step.staging.code"], cb, false) ); @@ -231,7 +244,7 @@ gulp.task("step.staging.all", $.sequence("step.staging.mainbuild", "css.prod", " gulp.task("build.staging", $.sequence("utils.cleanup", "step.staging.all", "step.postbuild")); // Builds everything (prod) -gulp.task("step.prod.code", $.sequence("js.prod")); +gulp.task("step.prod.code", $.sequence("translations.fullBuild", "js.prod")); gulp.task("step.prod.mainbuild", cb => $.multiProcess(["utils.copyAdditionalBuildFiles", "step.baseResources", "step.prod.code"], cb, false) ); @@ -239,7 +252,7 @@ gulp.task("step.prod.all", $.sequence("step.prod.mainbuild", "css.prod", "html.p gulp.task("build.prod", $.sequence("utils.cleanup", "step.prod.all", "step.postbuild")); // Builds everything (standalone-beta) -gulp.task("step.standalone-beta.code", $.sequence("js.standalone-beta")); +gulp.task("step.standalone-beta.code", $.sequence("translations.fullBuild", "js.standalone-beta")); gulp.task("step.standalone-beta.mainbuild", cb => $.multiProcess( ["utils.copyAdditionalBuildFiles", "step.baseResources", "step.standalone-beta.code"], @@ -254,7 +267,7 @@ gulp.task( gulp.task("build.standalone-beta", $.sequence("utils.cleanup", "step.standalone-beta.all", "step.postbuild")); // Builds everything (standalone-prod) -gulp.task("step.standalone-prod.code", $.sequence("js.standalone-prod")); +gulp.task("step.standalone-prod.code", $.sequence("translations.fullBuild", "js.standalone-prod")); gulp.task("step.standalone-prod.mainbuild", cb => $.multiProcess( ["utils.copyAdditionalBuildFiles", "step.baseResources", "step.standalone-prod.code"], diff --git a/gulp/package.json b/gulp/package.json index 9e39962a..d84c4cd2 100644 --- a/gulp/package.json +++ b/gulp/package.json @@ -97,6 +97,7 @@ "gulp-sftp": "^0.1.5", "gulp-terser": "^1.2.0", "gulp-webserver": "^0.9.1", + "gulp-yaml": "^2.0.4", "imagemin-mozjpeg": "^8.0.0", "imagemin-pngquant": "^8.0.0", "jimp": "^0.6.1", diff --git a/gulp/translations.js b/gulp/translations.js new file mode 100644 index 00000000..07eabcd4 --- /dev/null +++ b/gulp/translations.js @@ -0,0 +1,26 @@ +const path = require("path"); + +const yaml = require("gulp-yaml"); + +const translationsSourceDir = path.join(__dirname, "..", "translations"); +const translationsJsonDir = path.join(__dirname, "..", "src", "js", "translations-built"); + +function gulptasksTranslations($, gulp, buildFolder) { + gulp.task("translations.clear", () => { + return gulp.src(translationsJsonDir, { read: false }).pipe($.clean({ force: true })); + }); + + gulp.task("translations.convertToJson", () => { + return gulp + .src(path.join(translationsSourceDir, "*.yaml")) + .pipe($.plumber()) + .pipe(yaml({ space: 2, safe: true })) + .pipe(gulp.dest(translationsJsonDir)); + }); + + gulp.task("translations.fullBuild", $.sequence("translations.convertToJson")); +} + +module.exports = { + gulptasksTranslations, +}; diff --git a/gulp/yarn.lock b/gulp/yarn.lock index 093c8d54..42591945 100644 --- a/gulp/yarn.lock +++ b/gulp/yarn.lock @@ -2477,6 +2477,13 @@ buffer@^5.2.0, buffer@^5.2.1: base64-js "^1.0.2" ieee754 "^1.1.4" +bufferstreams@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/bufferstreams/-/bufferstreams-2.0.1.tgz#441b267c2fc3fee02bb1d929289da113903bd5ef" + integrity sha512-ZswyIoBfFb3cVDsnZLLj2IDJ/0ppYdil/v2EGlZXvoefO689FokEmFEldhN5dV7R2QBxFneqTJOMIpfqhj+n0g== + dependencies: + readable-stream "^2.3.6" + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -6452,6 +6459,18 @@ gulp-webserver@^0.9.1: tiny-lr "0.1.4" watch "^0.11.0" +gulp-yaml@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/gulp-yaml/-/gulp-yaml-2.0.4.tgz#86569e2becc9f5dfc95dc92db5a71a237f4b6ab4" + integrity sha512-S/9Ib8PO+jGkCvWDwBUkmFkeW7QM0pp4PO8NNrMEfWo5Sk30P+KqpyXc4055L/vOX326T/b9MhM4nw5EenyX9g== + dependencies: + bufferstreams "^2.0.1" + js-yaml "^3.13.1" + object-assign "^4.1.1" + plugin-error "^1.0.1" + replace-ext "^1.0.0" + through2 "^3.0.0" + gulp@^3.9.1: version "3.9.1" resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" @@ -12889,7 +12908,7 @@ through2@2.0.3: readable-stream "^2.1.5" xtend "~4.0.1" -through2@3.0.1, through2@^3.0.1: +through2@3.0.1, through2@^3.0.0, through2@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.1.tgz#39276e713c3302edf9e388dd9c812dd3b825bd5a" integrity sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww== diff --git a/src/css/ingame_hud/building_placer.scss b/src/css/ingame_hud/building_placer.scss index c69f69d5..ab7c8da9 100644 --- a/src/css/ingame_hud/building_placer.scss +++ b/src/css/ingame_hud/building_placer.scss @@ -39,6 +39,13 @@ .hotkey { color: lighten($colorGreenBright, 10); font-weight: bold; + display: flex; + flex-direction: row; + align-items: center; + .keybinding { + position: relative; + @include S(margin-left, 5px); + } } .buildingImage { diff --git a/src/css/ingame_hud/keybindings_overlay.scss b/src/css/ingame_hud/keybindings_overlay.scss index 14f28b2b..1c9aec84 100644 --- a/src/css/ingame_hud/keybindings_overlay.scss +++ b/src/css/ingame_hud/keybindings_overlay.scss @@ -66,17 +66,13 @@ } } - .keybinding.shift { + .keybinding.builtinKey { transition: all 0.1s ease-in-out; transition-property: background-color, color, border-color; background: $colorRedBright; border-color: $colorRedBright; color: #fff; } - - &.shiftDown .keybinding.shift { - border-color: darken($colorRedBright, 40); - } } body.uiHidden #ingame_HUD_KeybindingOverlay .binding:not(.hudToggle) { diff --git a/src/css/states/main_menu.scss b/src/css/states/main_menu.scss index 1a4147f6..af6878e7 100644 --- a/src/css/states/main_menu.scss +++ b/src/css/states/main_menu.scss @@ -251,9 +251,9 @@ @include S(padding, 15px); > a { - display: flex; - flex-direction: row; + display: grid; align-items: center; + grid-template-columns: 1fr auto; justify-content: center; background: #fafafa; @@ -280,8 +280,8 @@ .thirdpartyLogo { display: inline-block; - width: 80%; - height: 80%; + @include S(width, 50px); + @include S(height, 50px); background: center center / 80% no-repeat; &.githubLogo { background-image: uiResource("main_menu/github.png"); diff --git a/src/js/.gitignore b/src/js/.gitignore new file mode 100644 index 00000000..442b9d20 --- /dev/null +++ b/src/js/.gitignore @@ -0,0 +1 @@ +translations-built diff --git a/src/js/core/config.js b/src/js/core/config.js index 36821b58..2ce258f9 100644 --- a/src/js/core/config.js +++ b/src/js/core/config.js @@ -76,7 +76,7 @@ export const globalConfig = { debug: { /* dev:start */ - // fastGameEnter: true, + fastGameEnter: true, noArtificialDelays: true, // disableSavegameWrite: true, showEntityBounds: false, @@ -90,6 +90,7 @@ export const globalConfig = { allBuildingsUnlocked: true, upgradesNoCost: true, disableUnlockDialog: false, + testTranslations: true, /* dev:end */ }, diff --git a/src/js/core/modal_dialog_elements.js b/src/js/core/modal_dialog_elements.js index 701fdf1d..c6549131 100644 --- a/src/js/core/modal_dialog_elements.js +++ b/src/js/core/modal_dialog_elements.js @@ -11,6 +11,7 @@ import { FormElement } from "./modal_dialog_forms"; import { globalConfig } from "./config"; import { getStringForKeyCode } from "../game/key_action_mapper"; import { createLogger } from "./logging"; +import { T } from "../translations"; const kbEnter = 13; const kbCancel = 27; @@ -146,8 +147,7 @@ export class Dialog { button.classList.add("button"); button.classList.add("styledButton"); button.classList.add(buttonStyle); - // button.innerText = T.dialog_buttons[buttonId]; - button.innerText = buttonId; + button.innerText = T.dialogs.buttons[buttonId]; const params = (rawParams || "").split("/"); const useTimeout = params.indexOf("timeout") >= 0; @@ -277,7 +277,7 @@ export class DialogLoading extends Dialog { const loader = document.createElement("div"); loader.classList.add("prefab_LoadingTextWithAnim"); loader.classList.add("loadingIndicator"); - loader.innerText = "Loading"; + loader.innerText = T.global.loading; elem.appendChild(loader); this.app.inputMgr.pushReciever(this.inputReciever); diff --git a/src/js/core/utils.js b/src/js/core/utils.js index 61e72890..6cb74348 100644 --- a/src/js/core/utils.js +++ b/src/js/core/utils.js @@ -15,6 +15,7 @@ import { performanceNow, } from "./builtins"; import { Vector } from "./vector"; +import { T } from "../translations"; // Constants export const TOP = new Vector(0, -1); @@ -421,7 +422,7 @@ export function formatBigNumber(num, divider = ".") { num = Math_abs(num); if (num > 1e54) { - return sign + "inf"; + return sign + T.global.infinite; } if (num < 10 && !Number.isInteger(num)) { @@ -459,7 +460,7 @@ export function formatBigNumberFull(num, divider = ",") { return num + ""; } if (num > 1e54) { - return "infinite"; + return T.global.infinite; } let rest = num; let out = ""; @@ -831,24 +832,47 @@ export function formatSecondsToTimeAgo(secs) { if (seconds <= 60) { if (seconds <= 1) { - return "one second ago"; + return T.global.time.oneSecondAgo; } - return seconds + " seconds ago"; + return T.global.time.xSecondsAgo.replace("", "" + seconds); } else if (minutes <= 60) { if (minutes <= 1) { - return "one minute ago"; + return T.global.time.oneMinuteAgo; } - return minutes + " minutes ago"; + return T.global.time.xMinutesAgo.replace("", "" + minutes); } else if (hours <= 60) { if (hours <= 1) { - return "one hour ago"; + return T.global.time.oneHourAgo; } - return hours + " hours ago"; + return T.global.time.xHoursAgo.replace("", "" + hours); } else { if (days <= 1) { - return "one day ago"; + return T.global.time.oneDayAgo; } - return days + " days ago"; + return T.global.time.xDaysAgo.replace("", "" + days); + } +} + +/** + * Formats seconds into a readable string like "5h 23m" + * @param {number} secs Seconds + * @returns {string} + */ +export function formatSeconds(secs) { + const trans = T.global.time; + secs = Math_ceil(secs); + if (secs < 60) { + return trans.secondsShort.replace("", "" + secs); + } else if (secs < 60 * 60) { + const minutes = Math_floor(secs / 60); + const seconds = secs % 60; + return trans.minutesAndSecondsShort + .replace("", "" + seconds) + .replace("", "" + minutes); + } else { + const hours = Math_floor(secs / 3600); + const minutes = Math_floor(secs / 60) % 60; + return trans.hoursAndMinutesShort.replace("", "" + minutes).replace("", "" + hours); } } @@ -868,3 +892,11 @@ export function generateFileDownload(filename, text) { element.click(); document.body.removeChild(element); } + +/** + * Capitalizes the first letter + * @param {string} str + */ +export function capitalizeFirstLetter(str) { + return str.substr(0, 1).toUpperCase() + str.substr(1).toLowerCase(); +} diff --git a/src/js/game/buildings/belt_base.js b/src/js/game/buildings/belt_base.js index cfd1f299..354ed3e5 100644 --- a/src/js/game/buildings/belt_base.js +++ b/src/js/game/buildings/belt_base.js @@ -20,14 +20,6 @@ export class MetaBeltBaseBuilding extends MetaBuilding { return "#777"; } - getName() { - return "Belt"; - } - - getDescription() { - return "Transports items, hold and drag to place multiple."; - } - getPreviewSprite(rotationVariant) { switch (arrayBeltVariantToRotation[rotationVariant]) { case enumDirection.top: { diff --git a/src/js/game/buildings/cutter.js b/src/js/game/buildings/cutter.js index 0fc7c16c..ce83b8f5 100644 --- a/src/js/game/buildings/cutter.js +++ b/src/js/game/buildings/cutter.js @@ -30,14 +30,6 @@ export class MetaCutterBuilding extends MetaBuilding { } } - getName() { - return "Cut Half"; - } - - getDescription() { - return "Cuts shapes from top to bottom and outputs both halfs. If you use only one part, be sure to destroy the other part or it will stall!"; - } - getAvailableVariants(root) { return [defaultBuildingVariant, enumCutterVariants.quad]; } diff --git a/src/js/game/buildings/hub.js b/src/js/game/buildings/hub.js index 218144ee..b7b960de 100644 --- a/src/js/game/buildings/hub.js +++ b/src/js/game/buildings/hub.js @@ -20,14 +20,6 @@ export class MetaHubBuilding extends MetaBuilding { return "#eb5555"; } - getName() { - return "Hub"; - } - - getDescription() { - return "Your central hub, deliver shapes to it to unlock new buildings."; - } - isRotateable() { return false; } diff --git a/src/js/game/buildings/miner.js b/src/js/game/buildings/miner.js index f2855744..14d41ed5 100644 --- a/src/js/game/buildings/miner.js +++ b/src/js/game/buildings/miner.js @@ -13,18 +13,10 @@ export class MetaMinerBuilding extends MetaBuilding { super("miner"); } - getName() { - return "Extract"; - } - getSilhouetteColor() { return "#b37dcd"; } - getDescription() { - return "Place over a shape or color to extract it. Six extractors fill exactly one belt."; - } - getAvailableVariants(root) { return [defaultBuildingVariant, enumMinerVariants.chainable]; } diff --git a/src/js/game/buildings/mixer.js b/src/js/game/buildings/mixer.js index 15c7d699..b923e735 100644 --- a/src/js/game/buildings/mixer.js +++ b/src/js/game/buildings/mixer.js @@ -17,14 +17,6 @@ export class MetaMixerBuilding extends MetaBuilding { return new Vector(2, 1); } - getName() { - return "Mix Colors"; - } - - getDescription() { - return "Mixes two colors using additive blending."; - } - getSilhouetteColor() { return "#cdbb7d"; } diff --git a/src/js/game/buildings/painter.js b/src/js/game/buildings/painter.js index e085d051..420b4de1 100644 --- a/src/js/game/buildings/painter.js +++ b/src/js/game/buildings/painter.js @@ -29,14 +29,6 @@ export class MetaPainterBuilding extends MetaBuilding { } } - getName() { - return "Dye"; - } - - getDescription() { - return "Colors the whole shape on the left input with the color from the right input."; - } - getSilhouetteColor() { return "#cd9b7d"; } diff --git a/src/js/game/buildings/rotater.js b/src/js/game/buildings/rotater.js index e54dc3cb..95d3dc7c 100644 --- a/src/js/game/buildings/rotater.js +++ b/src/js/game/buildings/rotater.js @@ -16,14 +16,6 @@ export class MetaRotaterBuilding extends MetaBuilding { super("rotater"); } - getName() { - return "Rotate"; - } - - getDescription() { - return "Rotates shapes clockwise by 90 degrees."; - } - getSilhouetteColor() { return "#7dc6cd"; } diff --git a/src/js/game/buildings/splitter.js b/src/js/game/buildings/splitter.js index d83375f5..a0ef2c35 100644 --- a/src/js/game/buildings/splitter.js +++ b/src/js/game/buildings/splitter.js @@ -27,18 +27,10 @@ export class MetaSplitterBuilding extends MetaBuilding { } } - getName() { - return "Balancer"; - } - getSilhouetteColor() { return "#444"; } - getDescription() { - return "Multifunctional - Evenly distributes all inputs onto all outputs."; - } - getAvailableVariants(root) { return [defaultBuildingVariant, enumSplitterVariants.compact]; } diff --git a/src/js/game/buildings/stacker.js b/src/js/game/buildings/stacker.js index 5edfb050..58b6cc98 100644 --- a/src/js/game/buildings/stacker.js +++ b/src/js/game/buildings/stacker.js @@ -13,18 +13,10 @@ export class MetaStackerBuilding extends MetaBuilding { super("stacker"); } - getName() { - return "Combine"; - } - getSilhouetteColor() { return "#9fcd7d"; } - getDescription() { - return "Combines both items. If they can not be merged, the right item is placed above the left item."; - } - getDimensions() { return new Vector(2, 1); } diff --git a/src/js/game/buildings/trash.js b/src/js/game/buildings/trash.js index e630255c..897687e1 100644 --- a/src/js/game/buildings/trash.js +++ b/src/js/game/buildings/trash.js @@ -12,14 +12,6 @@ export class MetaTrashBuilding extends MetaBuilding { super("trash"); } - getName() { - return "Destroyer"; - } - - getDescription() { - return "Accepts inputs from all sides and destroys them. Forever."; - } - isRotateable() { return false; } diff --git a/src/js/game/buildings/underground_belt.js b/src/js/game/buildings/underground_belt.js index 6aa9ab5d..6512245e 100644 --- a/src/js/game/buildings/underground_belt.js +++ b/src/js/game/buildings/underground_belt.js @@ -28,18 +28,10 @@ export class MetaUndergroundBeltBuilding extends MetaBuilding { super("underground_belt"); } - getName() { - return "Tunnel"; - } - getSilhouetteColor() { return "#555"; } - getDescription() { - return "Allows to tunnel resources under buildings and belts."; - } - getFlipOrientationAfterPlacement() { return true; } diff --git a/src/js/game/game_loading_overlay.js b/src/js/game/game_loading_overlay.js index 63aadb02..f1e9d6ce 100644 --- a/src/js/game/game_loading_overlay.js +++ b/src/js/game/game_loading_overlay.js @@ -1,6 +1,7 @@ /* typehints:start */ import { Application } from "../application"; /* typehints:end */ +import { T } from "../translations"; export class GameLoadingOverlay { /** @@ -51,7 +52,7 @@ export class GameLoadingOverlay { internalAddSpinnerAndText(element) { const inner = document.createElement("span"); inner.classList.add("prefab_LoadingTextWithAnim"); - inner.innerText = "Loading"; + inner.innerText = T.global.loading; element.appendChild(inner); } } diff --git a/src/js/game/hud/parts/building_placer.js b/src/js/game/hud/parts/building_placer.js index d33cf559..0f49571e 100644 --- a/src/js/game/hud/parts/building_placer.js +++ b/src/js/game/hud/parts/building_placer.js @@ -1,23 +1,24 @@ -import { BaseHUDPart } from "../base_hud_part"; -import { MetaBuilding, defaultBuildingVariant } from "../../meta_building"; -import { DrawParameters } from "../../../core/draw_parameters"; +import { Math_abs, Math_degrees, Math_radians } from "../../../core/builtins"; import { globalConfig } from "../../../core/config"; -import { StaticMapEntityComponent } from "../../components/static_map_entity"; -import { STOP_PROPAGATION, Signal } from "../../../core/signal"; -import { - Vector, - enumDirectionToAngle, - enumInvertedDirections, - enumDirectionToVector, -} from "../../../core/vector"; -import { pulseAnimation, makeDiv, removeAllChildren } from "../../../core/utils"; -import { DynamicDomAttach } from "../dynamic_dom_attach"; -import { TrackedState } from "../../../core/tracked_state"; -import { Math_abs, Math_radians, Math_degrees } from "../../../core/builtins"; -import { Loader } from "../../../core/loader"; +import { DrawParameters } from "../../../core/draw_parameters"; import { drawRotatedSprite } from "../../../core/draw_utils"; -import { Entity } from "../../entity"; +import { Loader } from "../../../core/loader"; +import { STOP_PROPAGATION } from "../../../core/signal"; +import { TrackedState } from "../../../core/tracked_state"; +import { makeDiv, removeAllChildren } from "../../../core/utils"; +import { + enumDirectionToAngle, + enumDirectionToVector, + enumInvertedDirections, + Vector, +} from "../../../core/vector"; import { enumMouseButton } from "../../camera"; +import { StaticMapEntityComponent } from "../../components/static_map_entity"; +import { Entity } from "../../entity"; +import { defaultBuildingVariant, MetaBuilding } from "../../meta_building"; +import { BaseHUDPart } from "../base_hud_part"; +import { DynamicDomAttach } from "../dynamic_dom_attach"; +import { T } from "../../../translations"; export class HUDBuildingPlacer extends BaseHUDPart { initialize() { @@ -231,13 +232,16 @@ export class HUDBuildingPlacer extends BaseHUDPart { this.abortDragging(); this.root.hud.signals.selectedPlacementBuildingChanged.dispatch(metaBuilding); if (metaBuilding) { - this.buildingInfoElements.label.innerHTML = metaBuilding.getName(); - this.buildingInfoElements.descText.innerHTML = metaBuilding.getDescription(); + this.buildingInfoElements.label.innerHTML = T.buildings[metaBuilding.id].name; + this.buildingInfoElements.descText.innerHTML = T.buildings[metaBuilding.id].description; const binding = this.root.gameState.keyActionMapper.getBinding( "building_" + metaBuilding.getId() ); - this.buildingInfoElements.hotkey.innerHTML = "Hotkey: " + binding.getKeyCodeString(); + this.buildingInfoElements.hotkey.innerHTML = T.ingame.buildingPlacement.hotkeyLabel.replace( + "", + "" + binding.getKeyCodeString() + "" + ); const variant = this.preferredVariants[metaBuilding.getId()] || defaultBuildingVariant; this.currentVariant.set(variant); @@ -283,11 +287,12 @@ export class HUDBuildingPlacer extends BaseHUDPart { this.variantsElement, null, ["explanation"], - ` - Press ${this.root.gameState.keyActionMapper - .getBinding("cycle_variants") - .getKeyCodeString()} to cycle variants. - ` + T.ingame.buildingPlacement.cycleBuildingVariants.replace( + "", + "" + + this.root.gameState.keyActionMapper.getBinding("cycle_variants").getKeyCodeString() + + "" + ) ); for (let i = 0; i < availableVariants.length; ++i) { diff --git a/src/js/game/hud/parts/game_menu.js b/src/js/game/hud/parts/game_menu.js index f024de3b..e7772a39 100644 --- a/src/js/game/hud/parts/game_menu.js +++ b/src/js/game/hud/parts/game_menu.js @@ -2,6 +2,7 @@ import { BaseHUDPart } from "../base_hud_part"; import { makeDiv, randomInt } from "../../../core/utils"; import { SOUNDS } from "../../../platform/sound"; import { enumNotificationType } from "./notifications"; +import { T } from "../../../translations"; export class HUDGameMenu extends BaseHUDPart { initialize() {} @@ -16,7 +17,7 @@ export class HUDGameMenu extends BaseHUDPart { keybinding: "menu_open_shop", badge: () => this.root.hubGoals.getAvailableUpgradeCount(), notification: /** @type {[string, enumNotificationType]} */ ([ - "A new upgrade is available!", + T.ingame.notifications.newUpgrade, enumNotificationType.upgrade, ]), }, diff --git a/src/js/game/hud/parts/keybinding_overlay.js b/src/js/game/hud/parts/keybinding_overlay.js index cb9e8b20..20bbb5b4 100644 --- a/src/js/game/hud/parts/keybinding_overlay.js +++ b/src/js/game/hud/parts/keybinding_overlay.js @@ -3,6 +3,7 @@ import { makeDiv } from "../../../core/utils"; import { getStringForKeyCode } from "../../key_action_mapper"; import { TrackedState } from "../../../core/tracked_state"; import { queryParamOptions } from "../../../core/query_parameters"; +import { T } from "../../../translations"; export class HUDKeybindingOverlay extends BaseHUDPart { initialize() { @@ -32,7 +33,7 @@ export class HUDKeybindingOverlay extends BaseHUDPart { `
${getKeycode("center_map")} - +
@@ -41,48 +42,48 @@ export class HUDKeybindingOverlay extends BaseHUDPart { ${getKeycode("map_move_left")} ${getKeycode("map_move_down")} ${getKeycode("map_move_right")} - +
- CTRL+ + ${T.global.keys.control}+ - +
- + ${getKeycode("building_abort_placement")} - +
${getKeycode("rotate_while_placing")} - +
- ⇧ SHIFT - + ⇧ ${T.global.keys.shift} +
- ALT - + ${T.global.keys.alt} +
- CTRL - + ${T.global.keys.control} +
` + (queryParamOptions.betaMode ? `
F2 - +
` : "") diff --git a/src/js/game/hud/parts/mass_selector.js b/src/js/game/hud/parts/mass_selector.js index 33ade806..8ad27acc 100644 --- a/src/js/game/hud/parts/mass_selector.js +++ b/src/js/game/hud/parts/mass_selector.js @@ -9,6 +9,7 @@ import { makeDiv } from "../../../core/utils"; import { DynamicDomAttach } from "../dynamic_dom_attach"; import { createLogger } from "../../../core/logging"; import { enumMouseButton } from "../../camera"; +import { T } from "../../../translations"; const logger = createLogger("hud/mass_selector"); @@ -23,10 +24,9 @@ export class HUDMassSelector extends BaseHUDPart { parent, "ingame_HUD_MassSelector", [], - ` - Press ${removalKeybinding} to remove selected buildings - and ${abortKeybinding} to cancel. - ` + T.ingame.massDelete.infoText + .replace("", removalKeybinding) + .replace("", abortKeybinding) ); } diff --git a/src/js/game/hud/parts/modal_dialogs.js b/src/js/game/hud/parts/modal_dialogs.js index 6b2cf587..67f8f1a2 100644 --- a/src/js/game/hud/parts/modal_dialogs.js +++ b/src/js/game/hud/parts/modal_dialogs.js @@ -80,22 +80,6 @@ export class HUDModalDialogs extends BaseHUDPart { return dialog.buttonSignals; } - showVideoTutorial(title, text, videoUrl) { - const dialog = new DialogVideoTutorial({ - app: this.app, - title: title, - contentHTML: text, - videoUrl, - }); - this.internalShowDialog(dialog); - - if (this.app) { - this.app.sound.playUiSound(SOUNDS.dialogOk); - } - - return dialog.buttonSignals; - } - showOptionChooser(title, options) { const dialog = new DialogOptionChooser({ app: this.app, diff --git a/src/js/game/hud/parts/notifications.js b/src/js/game/hud/parts/notifications.js index 5916d6b8..95817f43 100644 --- a/src/js/game/hud/parts/notifications.js +++ b/src/js/game/hud/parts/notifications.js @@ -1,5 +1,6 @@ import { BaseHUDPart } from "../base_hud_part"; import { makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; /** @enum {string} */ export const enumNotificationType = { @@ -23,7 +24,7 @@ export class HUDNotifications extends BaseHUDPart { // Automatic notifications this.root.signals.gameSaved.add(() => - this.onNotification("Your game has been saved.", enumNotificationType.saved) + this.onNotification(T.ingame.notifications.gameSaved, enumNotificationType.saved) ); } diff --git a/src/js/game/hud/parts/settings_menu.js b/src/js/game/hud/parts/settings_menu.js index dc547e32..c022ce93 100644 --- a/src/js/game/hud/parts/settings_menu.js +++ b/src/js/game/hud/parts/settings_menu.js @@ -1,8 +1,9 @@ import { BaseHUDPart } from "../base_hud_part"; -import { makeDiv } from "../../../core/utils"; +import { makeDiv, formatSeconds } from "../../../core/utils"; import { DynamicDomAttach } from "../dynamic_dom_attach"; import { InputReceiver } from "../../../core/input_receiver"; import { KeyActionMapper } from "../../key_action_mapper"; +import { T } from "../../../translations"; export class HUDSettingsMenu extends BaseHUDPart { createElements(parent) { @@ -14,18 +15,18 @@ export class HUDSettingsMenu extends BaseHUDPart { this.background, null, ["timePlayed"], - `Playtime` + `${T.ingame.settingsMenu.playtime}` ); this.buttonContainer = makeDiv(this.menuElement, null, ["buttons"]); const buttons = [ { - title: "Continue", + title: T.ingame.settingsMenu.buttons.continue, action: () => this.close(), }, { - title: "Return to menu", + title: T.ingame.settingsMenu.buttons.menu, action: () => this.returnToMenu(), }, ]; @@ -79,9 +80,8 @@ export class HUDSettingsMenu extends BaseHUDPart { // this.background.classList.add("visible"); this.root.app.inputMgr.makeSureAttachedAndOnTop(this.inputReciever); - const totalMinutesPlayed = Math.ceil(this.root.time.now() / 60.0); - const playtimeString = totalMinutesPlayed === 1 ? "1 minute" : totalMinutesPlayed + " minutes"; - this.timePlayed.querySelector(".playtime").innerText = playtimeString; + const totalSecondsPlayed = Math.ceil(this.root.time.now()); + this.timePlayed.querySelector(".playtime").innerText = formatSeconds(totalSecondsPlayed); } close() { diff --git a/src/js/game/hud/parts/shop.js b/src/js/game/hud/parts/shop.js index 5b1cfff5..04c053cb 100644 --- a/src/js/game/hud/parts/shop.js +++ b/src/js/game/hud/parts/shop.js @@ -1,12 +1,12 @@ -import { BaseHUDPart } from "../base_hud_part"; -import { makeDiv, removeAllChildren, formatBigNumber } from "../../../core/utils"; -import { UPGRADES, TIER_LABELS } from "../../upgrades"; -import { ShapeDefinition } from "../../shape_definition"; -import { DynamicDomAttach } from "../dynamic_dom_attach"; -import { InputReceiver } from "../../../core/input_receiver"; -import { KeyActionMapper } from "../../key_action_mapper"; import { Math_min } from "../../../core/builtins"; import { ClickDetector } from "../../../core/click_detector"; +import { InputReceiver } from "../../../core/input_receiver"; +import { formatBigNumber, makeDiv } from "../../../core/utils"; +import { T } from "../../../translations"; +import { KeyActionMapper } from "../../key_action_mapper"; +import { UPGRADES } from "../../upgrades"; +import { BaseHUDPart } from "../base_hud_part"; +import { DynamicDomAttach } from "../dynamic_dom_attach"; export class HUDShop extends BaseHUDPart { createElements(parent) { @@ -14,7 +14,7 @@ export class HUDShop extends BaseHUDPart { // DIALOG Inner / Wrapper this.dialogInner = makeDiv(this.background, null, ["dialogInner"]); - this.title = makeDiv(this.dialogInner, null, ["title"], `Upgrades`); + this.title = makeDiv(this.dialogInner, null, ["title"], T.ingame.shop.title); this.closeButton = makeDiv(this.title, null, ["closeButton"]); this.trackClicks(this.closeButton, this.close); this.contentDiv = makeDiv(this.dialogInner, null, ["content"]); @@ -23,7 +23,6 @@ export class HUDShop extends BaseHUDPart { // Upgrades for (const upgradeId in UPGRADES) { - const { label } = UPGRADES[upgradeId]; const handle = {}; handle.requireIndexToElement = []; @@ -32,10 +31,10 @@ export class HUDShop extends BaseHUDPart { handle.elem.setAttribute("data-upgrade-id", upgradeId); // Title - const title = makeDiv(handle.elem, null, ["title"], label); + const title = makeDiv(handle.elem, null, ["title"], T.shopUpgrades[upgradeId].name); // Title > Tier - handle.elemTierLabel = makeDiv(title, null, ["tier"], "Tier ?"); + handle.elemTierLabel = makeDiv(title, null, ["tier"]); // Icon handle.icon = makeDiv(handle.elem, null, ["icon"]); @@ -48,7 +47,7 @@ export class HUDShop extends BaseHUDPart { // Buy button handle.buyButton = document.createElement("button"); handle.buyButton.classList.add("buy", "styledButton"); - handle.buyButton.innerText = "Upgrade"; + handle.buyButton.innerText = T.ingame.shop.buttonUnlock; handle.elem.appendChild(handle.buyButton); this.trackClicks(handle.buyButton, () => this.tryUnlockNextTier(upgradeId)); @@ -61,13 +60,17 @@ export class HUDShop extends BaseHUDPart { rerenderFull() { for (const upgradeId in this.upgradeToElements) { const handle = this.upgradeToElements[upgradeId]; - const { description, tiers } = UPGRADES[upgradeId]; + const { tiers } = UPGRADES[upgradeId]; const currentTier = this.root.hubGoals.getUpgradeLevel(upgradeId); const tierHandle = tiers[currentTier]; // Set tier - handle.elemTierLabel.innerText = "Tier " + TIER_LABELS[currentTier]; + handle.elemTierLabel.innerText = T.ingame.shop.tier.replace( + "", + "" + T.ingame.shop.tierLabels[currentTier] + ); + handle.elemTierLabel.setAttribute("data-tier", currentTier); // Cleanup detectors @@ -84,12 +87,15 @@ export class HUDShop extends BaseHUDPart { if (!tierHandle) { // Max level - handle.elemDescription.innerText = "Maximum level"; + handle.elemDescription.innerText = T.ingame.shop.maximumLevel; continue; } // Set description - handle.elemDescription.innerText = description(tierHandle.improvement); + handle.elemDescription.innerText = T.shopUpgrades[upgradeId].description.replace( + "", + Math.floor(tierHandle.improvement * 100.0) + ); tierHandle.required.forEach(({ shape, amount }) => { const container = makeDiv(handle.elemRequirements, null, ["requirement"]); diff --git a/src/js/game/hud/parts/statistics.js b/src/js/game/hud/parts/statistics.js index 688ac63c..010d923f 100644 --- a/src/js/game/hud/parts/statistics.js +++ b/src/js/game/hud/parts/statistics.js @@ -1,18 +1,12 @@ import { Math_min } from "../../../core/builtins"; import { InputReceiver } from "../../../core/input_receiver"; -import { makeButton, makeDiv, removeAllChildren } from "../../../core/utils"; +import { makeButton, makeDiv, removeAllChildren, capitalizeFirstLetter } from "../../../core/utils"; import { KeyActionMapper } from "../../key_action_mapper"; import { enumAnalyticsDataSource } from "../../production_analytics"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; import { enumDisplayMode, HUDShapeStatisticsHandle } from "./statistics_handle"; - -const enumDataSourceToText = { - [enumAnalyticsDataSource.stored]: "Displaying amount of stored shapes in your central building.", - [enumAnalyticsDataSource.produced]: - "Displaying all shapes your whole factory produces, including intermediate products.", - [enumAnalyticsDataSource.delivered]: "Displaying shapes which are delivered to your central building.", -}; +import { T } from "../../../translations"; export class HUDStatistics extends BaseHUDPart { createElements(parent) { @@ -20,7 +14,7 @@ export class HUDStatistics extends BaseHUDPart { // DIALOG Inner / Wrapper this.dialogInner = makeDiv(this.background, null, ["dialogInner"]); - this.title = makeDiv(this.dialogInner, null, ["title"], `statistics`); + this.title = makeDiv(this.dialogInner, null, ["title"], T.ingame.statistics.title); this.closeButton = makeDiv(this.title, null, ["closeButton"]); this.trackClicks(this.closeButton, this.close); @@ -30,13 +24,21 @@ export class HUDStatistics extends BaseHUDPart { this.filtersDataSource = makeDiv(this.filterHeader, null, ["filtersDataSource"]); this.filtersDisplayMode = makeDiv(this.filterHeader, null, ["filtersDisplayMode"]); - const buttonModeProduced = makeButton(this.filtersDataSource, ["modeProduced"], "Produced"); - const buttonModeDelivered = makeButton(this.filtersDataSource, ["modeDelivered"], "Delivered"); - const buttonModeStored = makeButton(this.filtersDataSource, ["modeStored"], "Stored"); + const dataSources = [ + enumAnalyticsDataSource.produced, + enumAnalyticsDataSource.delivered, + enumAnalyticsDataSource.stored, + ]; - this.trackClicks(buttonModeProduced, () => this.setDataSource(enumAnalyticsDataSource.produced)); - this.trackClicks(buttonModeStored, () => this.setDataSource(enumAnalyticsDataSource.stored)); - this.trackClicks(buttonModeDelivered, () => this.setDataSource(enumAnalyticsDataSource.delivered)); + for (let i = 0; i < dataSources.length; ++i) { + const dataSource = dataSources[i]; + const button = makeButton( + this.filtersDataSource, + ["mode" + capitalizeFirstLetter(dataSource)], + T.ingame.statistics.dataSources[dataSource].title + ); + this.trackClicks(button, () => this.setDataSource(dataSource)); + } const buttonDisplayDetailed = makeButton(this.filtersDisplayMode, ["displayDetailed"]); const buttonDisplayIcons = makeButton(this.filtersDisplayMode, ["displayIcons"]); @@ -54,7 +56,7 @@ export class HUDStatistics extends BaseHUDPart { this.dataSource = source; this.dialogInner.setAttribute("data-datasource", source); - this.sourceExplanation.innerText = enumDataSourceToText[source]; + this.sourceExplanation.innerText = T.ingame.statistics.dataSources[source].title; if (this.visible) { this.rerenderFull(); } @@ -204,7 +206,7 @@ export class HUDStatistics extends BaseHUDPart { if (entries.length === 0) { this.contentDiv.innerHTML = ` - No shapes have been produced so far.`; + ${T.ingame.statistics.noShapesProduced}`; } this.contentDiv.classList.toggle("hasEntries", entries.length > 0); diff --git a/src/js/game/hud/parts/statistics_handle.js b/src/js/game/hud/parts/statistics_handle.js index 85d5b00f..d3612d92 100644 --- a/src/js/game/hud/parts/statistics_handle.js +++ b/src/js/game/hud/parts/statistics_handle.js @@ -4,6 +4,7 @@ import { enumAnalyticsDataSource } from "../../production_analytics"; import { formatBigNumber, clamp } from "../../../core/utils"; import { globalConfig } from "../../../core/config"; import { makeOffscreenBuffer } from "../../../core/buffer_utils"; +import { T } from "../../../translations"; /** @enum {string} */ export const enumDisplayMode = { @@ -86,7 +87,10 @@ export class HUDShapeStatisticsHandle { (this.root.productionAnalytics.getCurrentShapeRate(dataSource, this.definition) / globalConfig.analyticsSliceDurationSeconds) * 60; - this.counter.innerText = formatBigNumber(rate) + " / m"; + this.counter.innerText = T.ingame.statistics.shapesPerMinute.replace( + "", + formatBigNumber(rate) + ); break; } } diff --git a/src/js/game/hud/parts/unlock_notification.js b/src/js/game/hud/parts/unlock_notification.js index 8a8dae7a..13c0cd76 100644 --- a/src/js/game/hud/parts/unlock_notification.js +++ b/src/js/game/hud/parts/unlock_notification.js @@ -10,9 +10,10 @@ import { MetaSplitterBuilding } from "../../buildings/splitter"; import { MetaStackerBuilding } from "../../buildings/stacker"; import { MetaTrashBuilding } from "../../buildings/trash"; import { MetaUndergroundBeltBuilding } from "../../buildings/underground_belt"; -import { enumHubGoalRewards, enumHubGoalRewardToString } from "../../tutorial_goals"; +import { enumHubGoalRewards } from "../../tutorial_goals"; import { BaseHUDPart } from "../base_hud_part"; import { DynamicDomAttach } from "../dynamic_dom_attach"; +import { T } from "../../../translations"; export class HUDUnlockNotification extends BaseHUDPart { initialize() { @@ -36,17 +37,10 @@ export class HUDUnlockNotification extends BaseHUDPart { const dialog = makeDiv(this.element, null, ["dialog"]); - this.elemTitle = makeDiv(dialog, null, ["title"], ``); - this.elemSubTitle = makeDiv(dialog, null, ["subTitle"], `Completed`); + this.elemTitle = makeDiv(dialog, null, ["title"]); + this.elemSubTitle = makeDiv(dialog, null, ["subTitle"], T.ingame.levelCompleteNotification.completed); - this.elemContents = makeDiv( - dialog, - null, - ["contents"], - ` - Ready for the next one? - ` - ); + this.elemContents = makeDiv(dialog, null, ["contents"]); this.btnClose = document.createElement("button"); this.btnClose.classList.add("close", "styledButton"); @@ -61,9 +55,17 @@ export class HUDUnlockNotification extends BaseHUDPart { * @param {enumHubGoalRewards} reward */ showForLevel(level, reward) { - this.elemTitle.innerText = "Level " + ("" + level).padStart(2, "0"); + this.elemTitle.innerText = T.ingame.levelCompleteNotification.levelTitle.replace( + "", + ("" + level).padStart(2, "0") + ); - let html = `Unlocked ${enumHubGoalRewardToString[reward]}!`; + const rewardText = T.storyRewards[reward]; + + let html = + "" + + T.ingame.levelCompleteNotification.unlockText.replace("", rewardText) + + ""; const addBuildingExplanation = metaBuildingClass => { const metaBuilding = gMetaBuildingRegistry.findByClass(metaBuildingClass); diff --git a/src/js/game/key_action_mapper.js b/src/js/game/key_action_mapper.js index 19bc5b2a..00e4db5a 100644 --- a/src/js/game/key_action_mapper.js +++ b/src/js/game/key_action_mapper.js @@ -6,6 +6,7 @@ import { Application } from "../application"; import { Signal, STOP_PROPAGATION } from "../core/signal"; import { IS_MOBILE } from "../core/config"; +import { T } from "../translations"; function key(str) { return str.toUpperCase().charCodeAt(0); @@ -65,23 +66,23 @@ export function getStringForKeyCode(code) { case 8: return "⌫"; case 9: - return "TAB"; + return T.global.keys.tab; case 13: return "⏎"; case 16: return "⇪"; case 17: - return "CTRL"; + return T.global.keys.control; case 18: - return "ALT"; + return T.global.keys.alt; case 19: return "PAUSE"; case 20: return "CAPS"; case 27: - return "ESC"; + return T.global.keys.escape; case 32: - return "SPACE"; + return T.global.keys.space; case 33: return "PGUP"; case 34: diff --git a/src/js/game/meta_building.js b/src/js/game/meta_building.js index feb1cb0d..c011dbad 100644 --- a/src/js/game/meta_building.js +++ b/src/js/game/meta_building.js @@ -31,20 +31,6 @@ export class MetaBuilding { return new Vector(1, 1); } - /** - * Should return the name of this building - */ - getName() { - return this.id; - } - - /** - * Should return the description of this building - */ - getDescription() { - return "No Description"; - } - /** * Whether to stay in placement mode after having placed a building */ diff --git a/src/js/game/systems/hub.js b/src/js/game/systems/hub.js index ee26b71b..539a0981 100644 --- a/src/js/game/systems/hub.js +++ b/src/js/game/systems/hub.js @@ -3,8 +3,8 @@ import { HubComponent } from "../components/hub"; import { DrawParameters } from "../../core/draw_parameters"; import { Entity } from "../entity"; import { formatBigNumber } from "../../core/utils"; -import { enumHubGoalRewardToString } from "../tutorial_goals"; import { Loader } from "../../core/loader"; +import { T } from "../../translations"; export class HubSystem extends GameSystemWithFilter { constructor(root) { @@ -84,7 +84,7 @@ export class HubSystem extends GameSystemWithFilter { context.font = "bold 11px GameFont"; context.fillStyle = "#fd0752"; context.textAlign = "center"; - context.fillText(enumHubGoalRewardToString[goals.reward].toUpperCase(), pos.x, pos.y + 46); + context.fillText(T.storyRewards[goals.reward].toUpperCase(), pos.x, pos.y + 46); // Level context.font = "bold 11px GameFont"; diff --git a/src/js/game/tutorial_goals.js b/src/js/game/tutorial_goals.js index 88c0e967..42cc2e83 100644 --- a/src/js/game/tutorial_goals.js +++ b/src/js/game/tutorial_goals.js @@ -15,21 +15,6 @@ export const enumHubGoalRewards = { no_reward: "no_reward", }; -/** - * @enum {string} - */ -export const enumHubGoalRewardToString = { - [enumHubGoalRewards.reward_cutter_and_trash]: "Cutting Shapes", - [enumHubGoalRewards.reward_rotater]: "Rotating", - [enumHubGoalRewards.reward_painter]: "Painting", - [enumHubGoalRewards.reward_mixer]: "Color Mixing", - [enumHubGoalRewards.reward_stacker]: "Combiner", - [enumHubGoalRewards.reward_splitter]: "Splitter/Merger", - [enumHubGoalRewards.reward_tunnel]: "Tunnel", - - [enumHubGoalRewards.no_reward]: "Next level", -}; - export const tutorialGoals = [ // Circle { diff --git a/src/js/game/upgrades.js b/src/js/game/upgrades.js index f9accb79..958c917e 100644 --- a/src/js/game/upgrades.js +++ b/src/js/game/upgrades.js @@ -1,33 +1,8 @@ import { findNiceIntegerValue } from "../core/utils"; import { ShapeDefinition } from "./shape_definition"; -export const TIER_LABELS = [ - "I", - "II", - "III", - "IV", - "V", - "VI", - "VII", - "VIII", - "IX", - "X", - "XI", - "XII", - "XIII", - "XIV", - "XV", - "XVI", - "XVII", - "XVIII", - "XIX", - "XX", -]; - export const UPGRADES = { belt: { - label: "Belts, Distributer & Tunnels", - description: improvement => "Speed +" + Math.floor(improvement * 100.0) + "%", tiers: [ { required: [{ shape: "CuCuCuCu", amount: 80 }], @@ -49,8 +24,6 @@ export const UPGRADES = { }, miner: { - label: "Extraction", - description: improvement => "Speed +" + Math.floor(improvement * 100.0) + "%", tiers: [ { required: [{ shape: "RuRuRuRu", amount: 200 }], @@ -72,8 +45,6 @@ export const UPGRADES = { }, processors: { - label: "Shape Processing", - description: improvement => "Speed +" + Math.floor(improvement * 100.0) + "%", tiers: [ { required: [{ shape: "SuSuSuSu", amount: 200 }], @@ -95,8 +66,6 @@ export const UPGRADES = { }, painting: { - label: "Mixing & Painting", - description: improvement => "Speed +" + Math.floor(improvement * 100.0) + "%", tiers: [ { required: [{ shape: "WuWuWuWu", amount: 200 }], diff --git a/src/js/platform/ad_providers/adinplay.js b/src/js/platform/ad_providers/adinplay.js index a5731dfa..efad0574 100644 --- a/src/js/platform/ad_providers/adinplay.js +++ b/src/js/platform/ad_providers/adinplay.js @@ -7,6 +7,7 @@ import { createLogger } from "../../core/logging"; import { ClickDetector } from "../../core/click_detector"; import { performanceNow } from "../../core/builtins"; import { clamp } from "../../core/utils"; +import { T } from "../../translations"; const logger = createLogger("adprovider/adinplay"); @@ -111,7 +112,7 @@ export class AdinplayAdProvider extends AdProviderInterface { AD_HEIGHT: h, AD_FULLSCREEN: false, AD_CENTERPLAYER: false, - LOADING_TEXT: "Loading", + LOADING_TEXT: T.global.loading, PREROLL_ELEM: function () { return videoElement; }, diff --git a/src/js/states/main_menu.js b/src/js/states/main_menu.js index 8ed310e2..ccdf32ca 100644 --- a/src/js/states/main_menu.js +++ b/src/js/states/main_menu.js @@ -10,6 +10,7 @@ import { } from "../core/utils"; import { ReadWriteProxy } from "../core/read_write_proxy"; import { HUDModalDialogs } from "../game/hud/parts/modal_dialogs"; +import { T } from "../translations"; export class MainMenuState extends GameState { constructor() { @@ -18,14 +19,12 @@ export class MainMenuState extends GameState { getInnerHTML() { const bannerHtml = ` -

This is a Demo Version

+

${T.demoBanners.title}

-

Get shapez.io on steam for:

+

${T.demoBanners.intro}

    -
  • No advertisements and demo banners.
  • -
  • Unlimited savegame slots.
  • -
  • Supporting the developer ❤️
  • + ${T.demoBanners.advantages.map(advantage => `
  • ${advantage}
  • `).join("")}
Get shapez.io on steam! @@ -65,12 +64,12 @@ export class MainMenuState extends GameState { isSupportedBrowser() ? "" : ` -
This game is optimized for Google Chrome. Your browser is not supported or slow!
+
${T.mainMenu.browserWarning}
` } - - + + ${ @@ -86,13 +85,13 @@ export class MainMenuState extends GameState { @@ -112,7 +111,6 @@ export class MainMenuState extends GameState { const reader = new FileReader(); reader.addEventListener("load", event => { const contents = event.target.result; - let realContent; try { @@ -120,8 +118,8 @@ export class MainMenuState extends GameState { } catch (err) { closeLoader(); this.dialogs.showWarning( - "Import error", - "Failed to import your savegame:

" + err + T.dialogs.importSavegameError.title, + T.dialogs.importSavegameError.text + "

" + err ); return; } @@ -129,22 +127,27 @@ export class MainMenuState extends GameState { this.app.savegameMgr.importSavegame(realContent).then( () => { closeLoader(); - this.dialogs.showWarning("Imported", "Your savegame has been imported."); + this.dialogs.showWarning( + T.dialogs.importSavegameSuccess.title, + T.dialogs.importSavegameSuccess.text + ); this.renderSavegames(); }, err => { closeLoader(); this.dialogs.showWarning( - "Import error", - "Failed to import savegame. Please check the console output." + T.dialogs.importSavegameError.title, + T.dialogs.importSavegameError.text + ":

" + err ); } ); }); reader.addEventListener("error", error => { - console.error(error); - alert("Failed to read file: " + error); + this.dialogs.showWarning( + T.dialogs.importSavegameError.title, + T.dialogs.importSavegameError.text + ":

" + error + ); }); reader.readAsText(file, "utf-8"); }); @@ -159,7 +162,10 @@ export class MainMenuState extends GameState { onEnter(payload) { if (payload.loadError) { - alert("Error while loading game: " + payload.loadError); + this.dialogs.showWarning( + T.dialogs.gameLoadFailure.title, + T.dialogs.gameLoadFailure.text + "

" + payload.loadError + ); } this.dialogs = new HUDModalDialogs(null, this.app); @@ -244,8 +250,8 @@ export class MainMenuState extends GameState { */ deleteGame(game) { const signals = this.dialogs.showWarning( - "Confirm Deletion", - "Are you sure you want to delete the game?", + T.dialogs.confirmSavegameDelete.title, + T.dialogs.confirmSavegameDelete.text, ["delete:bad", "cancel:good"] ); @@ -255,7 +261,10 @@ export class MainMenuState extends GameState { this.renderSavegames(); }, err => { - this.dialogs.showWarning("Failed to delete", "Error: " + err); + this.dialogs.showWarning( + T.dialogs.savegameDeletionError.title, + T.dialogs.savegameDeletionError.text + "

" + err + ); } ); }); diff --git a/src/js/translations.js b/src/js/translations.js new file mode 100644 index 00000000..1d87145e --- /dev/null +++ b/src/js/translations.js @@ -0,0 +1,20 @@ +import { globalConfig } from "./core/config"; + +const baseTranslations = require("./translations-built/base-en.json"); + +export const T = baseTranslations; + +if (G_IS_DEV && globalConfig.debug.testTranslations) { + // Replaces all translations by fake translations to see whats translated and what not + const mapTranslations = obj => { + for (const key in obj) { + const value = obj[key]; + if (typeof value === "string") { + obj[key] = value.replace(/[a-z]/gi, "x"); + } else { + mapTranslations(value); + } + } + }; + mapTranslations(T); +} diff --git a/src/js/translations/en.yaml b/src/js/translations/en.yaml deleted file mode 100644 index e69de29b..00000000 diff --git a/translations/base-en.yaml b/translations/base-en.yaml new file mode 100644 index 00000000..228d1f32 --- /dev/null +++ b/translations/base-en.yaml @@ -0,0 +1,259 @@ +# +# GAME TRANSLATIONS +# +# Contributing: +# +# If you want to contribute, please make a pull request on this respository +# and I will have a look. +# +# Placeholders: +# +# Do *not* replace placeholders! Placeholders have a special syntax like +# `Hotkey: `. They are encapsulated within angle brackets. The correct +# translation for this one in German for example would be: `Taste: ` (notice +# how the placeholder stayed '' and was not replaced!) +# +# Adding a new language: +# +# If you want to add a new language, ask me in the discord and I will setup +# the basic structure so the game also detects it. +# + +global: + loading: Loading + + # How big numbers are rendered, e.g. "10,000" + thousandsDivider: "," + + # Shown for infinitely big numbers + infinite: inf + + time: + # Used for formatting past time dates + oneSecondAgo: one second ago + xSecondsAgo: seconds ago + oneMinuteAgo: one minute ago + xMinutesAgo: minutes ago + oneHourAgo: one hour ago + xHoursAgo: hours ago + oneDayAgo: one day ago + xDaysAgo: days ago + + # Short formats for times, e.g. '5h 23m' + secondsShort: s + minutesAndSecondsShort: m s + hoursAndMinutesShort: h s + + keys: + tab: TAB + control: CTRL + alt: ALT + escape: ESC + shift: SHIFT + space: SPACE + +demoBanners: + # This is the "advertisement" shown in the main menu and other various places + title: This is a demo version + intro: >- + Get shapez.io on steam for: + advantages: + - No advertisements. + - Unlimited savegame slots. + - Supporting the developer ❤️ + +mainMenu: + play: Play + importSavegame: Import Savegame + openSourceHint: This game is open source! + discordLink: Official Discord Server + + # This is shown when using firefox and other browsers which are not supported. + browserWarning: >- + This game is optimized for Google Chrome. Your browser is not supported or slow! + +dialogs: + buttons: + ok: OK + delete: Delete + cancel: Cancel + + importSavegameError: + title: Import Error + text: >- + Failed to import your savegame: + + importSavegameSuccess: + title: Savegame Imported + text: >- + Your savegame has been successfully imported. + + gameLoadFailure: + title: Game is broken + text: >- + Failed to load your savegame: + + confirmSavegameDelete: + title: Confirm deletion + text: >- + Are you sure you want to delete the game? + + savegameDeletionError: + title: Failed to delete + text: >- + Failed to delete the savegame: + +ingame: + # This is shown in the top left corner and displays useful keybindings in + # every situation + keybindingsOverlay: + centerMap: Center + moveMap: Move + removeBuildings: Delete + stopPlacement: Stop placement + rotateBuilding: Rotate building + placeMultiple: Place multiple + reverseOrientation: Reverse orientation + disableAutoOrientation: Disable auto orientation + toggleHud: Toggle HUD + + # Everything related to placing buildings (I.e. as soon as you selected a building + # from the toolbar) + buildingPlacement: + # Buildings can have different variants which are unlocked at later levels, + # and this is the hint shown when there are multiple variants available. + cycleBuildingVariants: Press to cycle variants. + + # Shows the hotkey in the ui, e.g. "Hotkey: Q" + hotkeyLabel: >- + Hotkey: + + # The notification when completing a level + levelCompleteNotification: + # is replaced by the actual level, so this gets 'Level 03' for example. + levelTitle: Level + completed: Completed + unlockText: Unlocked ! + buttonNextLevel: Next Level + + # Notifications on the lower right + notifications: + newUpgrade: A new upgrade is available! + gameSaved: Your game has been saved. + + # Mass delete information, this is when you hold CTRL and then drag with your mouse + # to select multiple buildings to delete + massDelete: + infoText: Press to remove selected buildings and to cancel. + + # The "Upgrades" window + shop: + title: Upgrades + buttonUnlock: Upgrade + + # Gets replaced to e.g. "Tier IX" + tier: Tier + + # The roman number for each tier + tierLabels: [I, II, III, IV, V, VI, VII, VIII, IX, X] + + maximumLevel: Maximum level + + # The "Statistics" window + statistics: + title: Statistics + dataSources: + stored: + title: Stored + description: Displaying amount of stored shapes in your central building. + produced: + title: Produced + description: Displaying all shapes your whole factory produces, including intermediate products. + delivered: + title: Delivered + description: Displaying shapes which are delivered to your central building. + noShapesProduced: No shapes have been produced so far. + + # Displays the shapes per minute, e.g. '523 / m' + shapesPerMinute: / m + + # Settings menu, when you press "ESC" + settingsMenu: + playtime: Playtime + + playtime1Minute: 1 minutes + playtimeXMinutes: minutes + + buttons: + continue: Continue + menu: Return to menu + +# All shop upgrades +shopUpgrades: + belt: + name: Belts, Distributor & Tunnels + description: Speed +% + miner: + name: Extraction + description: Speed +% + processors: + name: Shape Processing + description: Speed +% + painting: + name: Mixing & Painting + description: Speed +% + +# Buildings and their name / description +buildings: + belt: + name: Belt + description: Transports items, hold and drag to place multiple. + + miner: # Internal name for the Extractor + name: Extractor + description: Place over a shape or color to extract it. Six extractors fill exactly one belt. + + underground_belt: # Internal name for the Tunnel + name: Tunnel + description: Allows to tunnel resources under buildings and belts. + + splitter: # Internal name for the Balancer + name: Balancer + description: Multifunctional - Evenly distributes all inputs onto all outputs. + + cutter: + name: Cut Half + description: Cuts shapes from top to bottom and outputs both halfs. If you use only one part, be sure to destroy the other part or it will stall! + + rotater: + name: Rotate + description: Rotates shapes clockwise by 90 degrees. + + stacker: # Internal name for the Combiner + name: Combine + description: Combines both items. If they can not be merged, the right item is placed above the left item. + + mixer: + name: Mix Colors + description: Mixes two colors using additive blending. + + painter: + name: Dye + description: Colors the whole shape on the left input with the color from the right input. + + trash: # Internal name for the destroyer + name: Destroyed + description: Accepts inputs from all sides and destroys them. Forever. + +storyRewards: + # Those are the rewards gained from completing the store + reward_cutter_and_trash: Cutting Shapes + reward_rotater: Rotating + reward_painter: Painting + reward_mixer: Color Mixing + reward_stacker: Combiner + reward_splitter: Splitter/Merger + reward_tunnel: Tunnel + + # Special reward, which is shown when there is no reward actually + no_reward: Next level