Initial support for translations

pull/33/head
tobspr 4 years ago
parent 13c6fc7598
commit a70a937302

@ -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"],

@ -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",

@ -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,
};

@ -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==

@ -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 {

@ -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) {

@ -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");

1
src/js/.gitignore vendored

@ -0,0 +1 @@
translations-built

@ -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 */
},

@ -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);

@ -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("<x>", "" + 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("<x>", "" + 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("<x>", "" + hours);
} else {
if (days <= 1) {
return "one day ago";
return T.global.time.oneDayAgo;
}
return days + " days ago";
return T.global.time.xDaysAgo.replace("<x>", "" + 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("<seconds>", "" + secs);
} else if (secs < 60 * 60) {
const minutes = Math_floor(secs / 60);
const seconds = secs % 60;
return trans.minutesAndSecondsShort
.replace("<seconds>", "" + seconds)
.replace("<minutes>", "" + minutes);
} else {
const hours = Math_floor(secs / 3600);
const minutes = Math_floor(secs / 60) % 60;
return trans.hoursAndMinutesShort.replace("<minutes>", "" + minutes).replace("<hours>", "" + 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();
}

@ -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: {

@ -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. <strong>If you use only one part, be sure to destroy the other part or it will stall!</strong>";
}
getAvailableVariants(root) {
return [defaultBuildingVariant, enumCutterVariants.quad];
}

@ -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;
}

@ -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];
}

@ -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";
}

@ -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";
}

@ -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";
}

@ -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];
}

@ -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);
}

@ -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;
}

@ -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;
}

@ -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);
}
}

@ -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 { DrawParameters } from "../../../core/draw_parameters";
import { drawRotatedSprite } from "../../../core/draw_utils";
import { Loader } from "../../../core/loader";
import { STOP_PROPAGATION } from "../../../core/signal";
import { TrackedState } from "../../../core/tracked_state";
import { makeDiv, removeAllChildren } from "../../../core/utils";
import {
Vector,
enumDirectionToAngle,
enumInvertedDirections,
enumDirectionToVector,
enumInvertedDirections,
Vector,
} 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 { drawRotatedSprite } from "../../../core/draw_utils";
import { Entity } from "../../entity";
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(
"<key>",
"<code class='keybinding'>" + binding.getKeyCodeString() + "</code>"
);
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 <code class='keybinding'>${this.root.gameState.keyActionMapper
.getBinding("cycle_variants")
.getKeyCodeString()}</code> to cycle variants.
`
T.ingame.buildingPlacement.cycleBuildingVariants.replace(
"<key>",
"<code class='keybinding'>" +
this.root.gameState.keyActionMapper.getBinding("cycle_variants").getKeyCodeString() +
"</code>"
)
);
for (let i = 0; i < availableVariants.length; ++i) {

@ -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,
]),
},

@ -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 {
`
<div class="binding">
<code class="keybinding">${getKeycode("center_map")}</code>
<label>Center</label>
<label>${T.ingame.keybindingsOverlay.centerMap}</label>
</div>
<div class="binding">
@ -41,48 +42,48 @@ export class HUDKeybindingOverlay extends BaseHUDPart {
<code class="keybinding">${getKeycode("map_move_left")}</code>
<code class="keybinding">${getKeycode("map_move_down")}</code>
<code class="keybinding">${getKeycode("map_move_right")}</code>
<label>Move</label>
<label>${T.ingame.keybindingsOverlay.moveMap}</label>
</div>
<div class="binding noPlacementOnly">
<code class="keybinding rightMouse"></code><i></i>
<code class="keybinding shift">CTRL</code>+
<code class="keybinding builtinKey">${T.global.keys.control}</code>+
<code class="keybinding leftMouse"></code>
<label>Delete</label>
<label>${T.ingame.keybindingsOverlay.removeBuildings}</label>
</div>
<div class="binding placementOnly">
<code class="keybinding rightMouse"></code> <i></i>
<code class="keybinding rightMouse"></code><i></i>
<code class="keybinding">${getKeycode("building_abort_placement")}</code>
<label>Stop placement</label>
<label>${T.ingame.keybindingsOverlay.stopPlacement}</label>
</div>
<div class="binding placementOnly">
<code class="keybinding">${getKeycode("rotate_while_placing")}</code>
<label>Rotate Building</label>
<label>${T.ingame.keybindingsOverlay.rotateBuilding}</label>
</div>
<div class="binding placementOnly">
<code class="keybinding shift"> SHIFT</code>
<label>Place Multiple</label>
<code class="keybinding builtinKey shift"> ${T.global.keys.shift}</code>
<label>${T.ingame.keybindingsOverlay.placeMultiple}</label>
</div>
<div class="binding placementOnly">
<code class="keybinding shift">ALT</code>
<label>Reverse orientation</label>
<code class="keybinding builtinKey">${T.global.keys.alt}</code>
<label>${T.ingame.keybindingsOverlay.reverseOrientation}</label>
</div>
<div class="binding placementOnly">
<code class="keybinding shift">CTRL</code>
<label>Disable auto orientation</label>
<code class="keybinding builtinKey">${T.global.keys.control}</code>
<label>${T.ingame.keybindingsOverlay.disableAutoOrientation}</label>
</div>
` +
(queryParamOptions.betaMode
? `
<div class="binding hudToggle">
<code class="keybinding">F2</code>
<label>Toggle HUD</label>
<label>${T.ingame.keybindingsOverlay.toggleHud}</label>
</div>
`
: "")

@ -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 <code class="keybinding">${removalKeybinding}</code> to remove selected buildings
and <code class="keybinding">${abortKeybinding}</code> to cancel.
`
T.ingame.massDelete.infoText
.replace("<keyDelete>", removalKeybinding)
.replace("<keyCancel>", abortKeybinding)
);
}

@ -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,

@ -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)
);
}

@ -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"],
`<strong>Playtime</strong><span class="playtime"></span>`
`<strong>${T.ingame.settingsMenu.playtime}</strong><span class="playtime"></span>`
);
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() {

@ -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(
"<x>",
"" + 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(
"<gain>",
Math.floor(tierHandle.improvement * 100.0)
);
tierHandle.required.forEach(({ shape, amount }) => {
const container = makeDiv(handle.elemRequirements, null, ["requirement"]);

@ -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");
this.trackClicks(buttonModeProduced, () => this.setDataSource(enumAnalyticsDataSource.produced));
this.trackClicks(buttonModeStored, () => this.setDataSource(enumAnalyticsDataSource.stored));
this.trackClicks(buttonModeDelivered, () => this.setDataSource(enumAnalyticsDataSource.delivered));
const dataSources = [
enumAnalyticsDataSource.produced,
enumAnalyticsDataSource.delivered,
enumAnalyticsDataSource.stored,
];
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 = `
<strong class="noEntries">No shapes have been produced so far.</strong>`;
<strong class="noEntries">${T.ingame.statistics.noShapesProduced}</strong>`;
}
this.contentDiv.classList.toggle("hasEntries", entries.length > 0);

@ -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(
"<shapes>",
formatBigNumber(rate)
);
break;
}
}

@ -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>",
("" + level).padStart(2, "0")
);
const rewardText = T.storyRewards[reward];
let html = `<span class='reward'>Unlocked ${enumHubGoalRewardToString[reward]}!</span>`;
let html =
"<span class='reward'>" +
T.ingame.levelCompleteNotification.unlockText.replace("<reward>", rewardText) +
"</span>";
const addBuildingExplanation = metaBuildingClass => {
const metaBuilding = gMetaBuildingRegistry.findByClass(metaBuildingClass);

@ -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:

@ -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
*/

@ -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";

@ -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
{

@ -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 }],

@ -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;
},

@ -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 = `
<h3>This is a Demo Version</h3>
<h3>${T.demoBanners.title}</h3>
<p>Get <strong>shapez.io on steam</strong> for:</p>
<p>${T.demoBanners.intro}</p>
<ul>
<li>No advertisements and demo banners.</li>
<li>Unlimited savegame slots.</li>
<li>Supporting the developer </li>
${T.demoBanners.advantages.map(advantage => `<li>${advantage}</li>`).join("")}
</ul>
<a href="https://steam.shapez.io" class="steamLink" target="_blank">Get shapez.io on steam!</a>
@ -65,12 +64,12 @@ export class MainMenuState extends GameState {
isSupportedBrowser()
? ""
: `
<div class="browserWarning">This game is optimized for Google Chrome. Your browser is not supported or slow!</div>
<div class="browserWarning">${T.mainMenu.browserWarning}</div>
`
}
<button class="playButton styledButton">Play</button>
<button class="importButton styledButton">Import savegame</button>
<button class="playButton styledButton">${T.mainMenu.play}</button>
<button class="importButton styledButton">${T.mainMenu.importSavegame}</button>
</div>
${
@ -86,13 +85,13 @@ export class MainMenuState extends GameState {
<div class="footer">
<a href="https://github.com/tobspr/shapez.io" target="_blank">
This game is open source!
${T.mainMenu.openSourceHint}
<span class="thirdpartyLogo githubLogo"></span>
</a>
</a>
<a href="https://discord.gg/HN7EVzV" target="_blank">
Official discord server
<span class="thirdpartyLogo discordLogo"></span>
${T.mainMenu.discordLink}
<span class="thirdpartyLogo discordLogo"></span>
</a>
</div>
@ -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:<br><br>" + err
T.dialogs.importSavegameError.title,
T.dialogs.importSavegameError.text + "<br><br>" + 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 + ":<br><br>" + 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 + ":<br><br>" + 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 + "<br><br>" + 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 + "<br><br>" + err
);
}
);
});

@ -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);
}

@ -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: <key>`. They are encapsulated within angle brackets. The correct
# translation for this one in German for example would be: `Taste: <key>` (notice
# how the placeholder stayed '<key>' 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: <x> seconds ago
oneMinuteAgo: one minute ago
xMinutesAgo: <x> minutes ago
oneHourAgo: one hour ago
xHoursAgo: <x> hours ago
oneDayAgo: one day ago
xDaysAgo: <x> days ago
# Short formats for times, e.g. '5h 23m'
secondsShort: <seconds>s
minutesAndSecondsShort: <minutes>m <seconds>s
hoursAndMinutesShort: <hours>h <minutes>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 <strong>shapez.io on steam</strong> 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 <key> to cycle variants.
# Shows the hotkey in the ui, e.g. "Hotkey: Q"
hotkeyLabel: >-
Hotkey: <key>
# The notification when completing a level
levelCompleteNotification:
# <level> is replaced by the actual level, so this gets 'Level 03' for example.
levelTitle: Level <level>
completed: Completed
unlockText: Unlocked <reward>!
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 <keyDelete> to remove selected buildings and <keyCancel> to cancel.
# The "Upgrades" window
shop:
title: Upgrades
buttonUnlock: Upgrade
# Gets replaced to e.g. "Tier IX"
tier: Tier <x>
# 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: <shapes> / m
# Settings menu, when you press "ESC"
settingsMenu:
playtime: Playtime
playtime1Minute: 1 minutes
playtimeXMinutes: <x> minutes
buttons:
continue: Continue
menu: Return to menu
# All shop upgrades
shopUpgrades:
belt:
name: Belts, Distributor & Tunnels
description: Speed +<gain>%
miner:
name: Extraction
description: Speed +<gain>%
processors:
name: Shape Processing
description: Speed +<gain>%
painting:
name: Mixing & Painting
description: Speed +<gain>%
# 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. <strong>If you use only one part, be sure to destroy the other part or it will stall!</strong>
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
Loading…
Cancel
Save