Skip to content

Commit

Permalink
feat(ui): Rebuild l10n for numeric values
Browse files Browse the repository at this point in the history
  • Loading branch information
oliversalzburg committed Dec 9, 2024
1 parent 824267c commit 73d73fc
Show file tree
Hide file tree
Showing 48 changed files with 351 additions and 422 deletions.
93 changes: 92 additions & 1 deletion packages/kitten-scientists/source/KittenScientists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ReleaseChannel, ReleaseInfoSchema } from "@kitten-science/action-releas
import { isNil } from "@oliversalzburg/js-utils/data/nil.js";
import { redirectErrorsToConsole } from "@oliversalzburg/js-utils/errors/console.js";
import gt from "semver/functions/gt.js";
import { Engine, EngineState, GameLanguage } from "./Engine.js";
import { Engine, EngineState, GameLanguage, SupportedLocale } from "./Engine.js";
import { ScienceSettings } from "./settings/ScienceSettings.js";
import { SpaceSettings } from "./settings/SpaceSettings.js";
import { WorkshopSettings } from "./settings/WorkshopSettings.js";
Expand Down Expand Up @@ -202,6 +202,95 @@ export class KittenScientists {
this._userInterface.refreshUi();
}

/**
* Turns a string like 52.7 into the number 52.7
* @param value - String representation of an absolute value.
* @returns A number between 0 and Infinity, where Infinity is represented as -1.
*/
parseFloat(value: string | null): number | null {
if (value === null || value === "") {
return null;
}

const hasSuffix = /[KMGTP]$/i.test(value);
const baseValue = value.substring(0, value.length - (hasSuffix ? 1 : 0));

let numericValue =
value.includes("e") || hasSuffix ? parseFloat(baseValue) : parseInt(baseValue);
if (hasSuffix) {
const suffix = value.substring(value.length - 1).toUpperCase();
numericValue = numericValue * Math.pow(1000, ["", "K", "M", "G", "T", "P"].indexOf(suffix));
}
if (numericValue === Number.POSITIVE_INFINITY || numericValue < 0) {
numericValue = -1;
}

return numericValue;
}

parseAbsolute(value: string | null): number | null {
const floatValue = this.parseFloat(value);
return floatValue !== null ? Math.round(floatValue) : null;
}

/**
* Turns a string like 52.7 into the number 0.527
* @param value - String representation of a percentage.
* @returns A number between 0 and 1 representing the described percentage.
*/
parsePercentage(value: string): number {
const cleanedValue = value.trim().replace(/%$/, "");
return Math.max(0, Math.min(1, parseFloat(cleanedValue) / 100));
}

/**
* Turns a number into a game-native string representation.
* Infinity, either by actual value or by -1 representation, is rendered as a symbol.
* @param value - The number to render as a string.
* @param host - The host instance which we can use to let the game render values for us.
* @returns A string representing the given number.
*/
renderAbsolute(value: number, locale: SupportedLocale | "invariant" = "invariant") {
if (value < 0 || value === Number.POSITIVE_INFINITY) {
return "∞";
}

return locale !== "invariant"
? new Intl.NumberFormat(locale, { style: "decimal", maximumFractionDigits: 0 }).format(value)
: this.game.getDisplayValueExt(value, false, false);
}

/**
* Turns a number like 0.527 into a string like 52.7
* @param value - The number to render as a string.
* @param locale - The locale in which to render the percentage.
* @param withUnit - Should the percentage sign be included in the output?
* @returns A string representing the given percentage.
*/
renderPercentage(
value: number,
locale: SupportedLocale | "invariant" = "invariant",
withUnit?: boolean,
): string {
if (value < 0 || value === Number.POSITIVE_INFINITY) {
return "∞";
}

return locale !== "invariant"
? new Intl.NumberFormat(locale, { style: "percent" }).format(value)
: `${this.game.getDisplayValueExt(value * 100, false, false)}${withUnit ? "%" : ""}`;
}

renderFloat(value: number, locale: SupportedLocale | "invariant" = "invariant"): string {
if (value < 0 || value === Number.POSITIVE_INFINITY) {
return "∞";
}

return locale !== "invariant"
? new Intl.NumberFormat(locale, { style: "decimal" }).format(value)
: this.game.getDisplayValueExt(value, false, false);
}

//#region Settings
/**
* Encodes an engine states into a string.
Expand Down Expand Up @@ -304,6 +393,7 @@ export class KittenScientists {
}
//#endregion

//#region SaveManager
installSaveManager() {
cinfo("Installing save game manager...");
this.game.managers.push(this._saveManager);
Expand Down Expand Up @@ -332,4 +422,5 @@ export class KittenScientists {
// `game/beforesave` event, which is intended for external consumers.
},
};
//#endregion
}
18 changes: 10 additions & 8 deletions packages/kitten-scientists/source/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
"resources.consume.titleZero": "Not consuming {0}",
"resources.consume": "Consume: {0}",
"resources.stock.prompt": "Enter how much of the resource you always want to keep in stock.",
"resources.stock.promptExplainer": "You can enter large values with SI notation, like \"15.7T\". If you submit an empty value, or a negative value, the limit will be set to infinity. If you set the limit to 0, this option will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"resources.stock.promptExplainer": "All notations that the game supports are accepted. If you submit an empty value, or a negative value, the limit will be set to infinity. If you set the limit to 0, this option will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"resources.stock.promptTitle": "Stock value for {0} (Current: {1})",
"resources.stock.title": "Keeping {0} {1} in stock",
"resources.stock.titleInfinite": "Keeping all {0} in stock",
Expand Down Expand Up @@ -244,7 +244,7 @@
"ui.filter": "Log Filters",
"ui.infinity": "",
"ui.internals.interval.prompt": "Enter the new amount of milliseconds as an absolute value between 0 and Infinity.",
"ui.internals.interval.promptExplainer": "You can enter large values with SI notation, like \"15.7T\". If you set the limit to 0, Kitten Scientists will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"ui.internals.interval.promptExplainer": "All notations that the game supports are accepted. If you set the limit to 0, Kitten Scientists will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"ui.internals.interval.promptTitle": "Kitten Scientists Execution Interval (Current: {0})",
"ui.internals.interval": "Interval: {0}",
"ui.internals": "Internals",
Expand All @@ -256,29 +256,30 @@
"ui.limited.off": "Unlimited",
"ui.limited.on": "Eco Mode",
"ui.max.build.prompt": "Limit for {0} (Current: {1})",
"ui.max.build.promptExplainer": "You can enter large values with SI notation, like \"15.7T\". If you submit an empty value, or a negative value, the limit will be set to infinity. If you set the limit to 0, this option will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"ui.max.build.promptExplainer": "All notations that the game supports are accepted. If you submit an empty value, or a negative value, the limit will be set to infinity. If you set the limit to 0, this option will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"ui.max.build.title": "Build {0} {1}",
"ui.max.build.titleInfinite": "Never stop building {0}",
"ui.max.build.titleZero": "Don't build {0}",
"ui.max.craft.prompt": "Enter the new limit of how many {0} to craft as an absolute value between 0 and Infinity.",
"ui.max.craft.promptExplainer": "You can enter large values with SI notation, like \"15.7T\". If you submit an empty value, or a negative value, the limit will be set to infinity. If you set the limit to 0, this option will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"ui.max.craft.promptExplainer": "All notations that the game supports are accepted. If you submit an empty value, or a negative value, the limit will be set to infinity. If you set the limit to 0, this option will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"ui.max.craft.promptTitle": "Limit for Crafting {0} (Current: {1})",
"ui.max.craft.title": "Craft {0} {1}",
"ui.max.craft.titleInfinite": "Never stop crafting {0}",
"ui.max.craft.titleZero": "Don't craft {0}",
"ui.max.distribute.prompt": "Enter the new amount of kittens to assign as {0} as an absolute value between 0 and Infinity.",
"ui.max.distribute.promptExplainer": "You can enter large values with SI notation, like \"15.7T\". If you submit an empty value, or a negative value, the limit will be set to infinity. If you set the limit to 0, this option will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"ui.max.distribute.promptExplainer": "All notations that the game supports are accepted. If you submit an empty value, or a negative value, the limit will be set to infinity. If you set the limit to 0, this option will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"ui.max.distribute.promptTitle": "Assign Max. Kittens as {0} (Current: {1})",
"ui.max.distribute.title": "Assign {0} kittens as {1}",
"ui.max.distribute.titleInfinite": "Assign as many kittens as possible as {0}",
"ui.max.distribute.titleZero": "Don't assign any kittens as {0}",
"ui.max.embassy.title": "Build {0} embassy for {1}",
"ui.max.embassy.titleInfinite": "Never stop building embassies for {0}",
"ui.max.embassy.titleZero": "Don't build embassies for {0}",
"ui.max.prompt.absolute": "Enter the new limit as an absolute value between 0.0 and Infinity.",
"ui.max.prompt.absolute": "Enter the new limit as an absolute value between 0 and Infinity.",
"ui.max.prompt.float": "Enter the new limit as an absolute value between 0.0 and Infinity.",
"ui.max.set": "Maximum for {0}",
"ui.max.timeSkip.prompt": "Enter the new amount of years as an absolute value between 0 and Infinity.",
"ui.max.timeSkip.promptExplainer": "You can enter large values with SI notation, like \"15.7T\". If you submit an empty value, or a negative value, the limit will be set to infinity. If you set the limit to 0, this option will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"ui.max.timeSkip.promptExplainer": "All notations that the game supports are accepted. If you submit an empty value, or a negative value, the limit will be set to infinity. If you set the limit to 0, this option will be automatically disabled. If you submit an invalid value, it will be treated as if you hit Cancel.",
"ui.max.timeSkip.promptTitle": "Max. Years to skip (Current: {0})",
"ui.max.timeSkip.title": "Skip {0} years at once",
"ui.max.timeSkip.titleInfinite": "Skip as many years as possible",
Expand Down Expand Up @@ -315,7 +316,8 @@
"ui.trigger.promoteKittens.prompt": "Enter the new gold storage level at which to promote kittens as a percentage between 0.0 and 100.0.",
"ui.trigger.promoteKittens.promptExplainer": "If you submit an empty value, or an invalid value, it will be treated as if you hit Cancel.",
"ui.trigger.promoteKittens.promptTitle": "Trigger to Promote Kittens (Current: {0})",
"ui.trigger.prompt.absolute": "Enter the new trigger value as an absolute value between 0.0 and Infinity.",
"ui.trigger.prompt.absolute": "Enter the new trigger value as an absolute value between 0 and Infinity.",
"ui.trigger.prompt.float": "Enter the new trigger value as an absolute value between 0.0 and Infinity.",
"ui.trigger.prompt.percentage": "Enter the new trigger value as a percentage between 0.0 and 100.0.",
"ui.trigger.reset.promptExplainer": "If you submit a negative value, the item will be disabled. If you submit an empty value, or an invalid value, it will be treated as if you hit Cancel.",
"ui.trigger.section.inactive": "∞ (nothing is built automatically)",
Expand Down
17 changes: 8 additions & 9 deletions packages/kitten-scientists/source/ui/BonfireSettingsUi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { SettingListItem } from "./components/SettingListItem.js";
import { SettingTriggerListItem } from "./components/SettingTriggerListItem.js";
import { SettingsList } from "./components/SettingsList.js";
import { SettingsPanel } from "./components/SettingsPanel.js";
import { UiComponent } from "./components/UiComponent.js";

export class BonfireSettingsUi extends SettingsPanel<BonfireSettings> {
constructor(
Expand All @@ -41,7 +40,7 @@ export class BonfireSettingsUi extends SettingsPanel<BonfireSettings> {
item.triggerButton.element[0].title = host.engine.i18n("ui.trigger.section", [
settings.trigger < 0
? host.engine.i18n("ui.trigger.section.inactive")
: UiComponent.renderPercentage(settings.trigger, locale.selected, true),
: host.renderPercentage(settings.trigger, locale.selected, true),
]);
},
onSetTrigger: () => {
Expand All @@ -51,10 +50,10 @@ export class BonfireSettingsUi extends SettingsPanel<BonfireSettings> {
host.engine.i18n("ui.trigger.section.prompt", [
label,
settings.trigger !== -1
? UiComponent.renderPercentage(settings.trigger, locale.selected, true)
? host.renderPercentage(settings.trigger, locale.selected, true)
: host.engine.i18n("ui.infinity"),
]),
settings.trigger !== -1 ? UiComponent.renderPercentage(settings.trigger) : "",
settings.trigger !== -1 ? host.renderPercentage(settings.trigger) : "",
host.engine.i18n("ui.trigger.section.promptExplainer"),
)
.then(value => {
Expand All @@ -67,7 +66,7 @@ export class BonfireSettingsUi extends SettingsPanel<BonfireSettings> {
return;
}

settings.trigger = UiComponent.parsePercentage(value);
settings.trigger = host.parsePercentage(value);
})
.then(() => {
this.refreshUi();
Expand Down Expand Up @@ -101,7 +100,7 @@ export class BonfireSettingsUi extends SettingsPanel<BonfireSettings> {
new SettingsList(host, {
children: [
new HeaderListItem(host, host.engine.i18n("ui.additional")),
new SettingListItem(host, host.engine.i18n("option.catnip"), settings.gatherCatnip, {
new SettingListItem(host, settings.gatherCatnip, host.engine.i18n("option.catnip"), {
onCheck: () => {
host.engine.imessage("status.sub.enable", [host.engine.i18n("option.catnip")]);
},
Expand All @@ -111,8 +110,8 @@ export class BonfireSettingsUi extends SettingsPanel<BonfireSettings> {
}),
new SettingListItem(
host,
host.engine.i18n("option.steamworks"),
settings.turnOnSteamworks,
host.engine.i18n("option.steamworks"),
{
onCheck: () => {
host.engine.imessage("status.sub.enable", [host.engine.i18n("option.steamworks")]);
Expand All @@ -122,15 +121,15 @@ export class BonfireSettingsUi extends SettingsPanel<BonfireSettings> {
},
},
),
new SettingListItem(host, host.engine.i18n("option.magnetos"), settings.turnOnMagnetos, {
new SettingListItem(host, settings.turnOnMagnetos, host.engine.i18n("option.magnetos"), {
onCheck: () => {
host.engine.imessage("status.sub.enable", [host.engine.i18n("option.magnetos")]);
},
onUnCheck: () => {
host.engine.imessage("status.sub.disable", [host.engine.i18n("option.magnetos")]);
},
}),
new SettingListItem(host, host.engine.i18n("option.reactors"), settings.turnOnReactors, {
new SettingListItem(host, settings.turnOnReactors, host.engine.i18n("option.reactors"), {
onCheck: () => {
host.engine.imessage("status.sub.enable", [host.engine.i18n("option.reactors")]);
},
Expand Down
24 changes: 10 additions & 14 deletions packages/kitten-scientists/source/ui/BuildSectionTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { KittenScientists } from "../KittenScientists.js";
import { SettingOptions, SettingTrigger, SettingTriggerMax } from "../settings/Settings.js";
import { Dialog } from "./components/Dialog.js";
import { SettingMaxTriggerListItem } from "./components/SettingMaxTriggerListItem.js";
import { UiComponent } from "./components/UiComponent.js";

export const BuildSectionTools = {
getBuildOption: (
Expand All @@ -30,24 +29,21 @@ export const BuildSectionTools = {
buildOption.triggerButton.inactive = !option.enabled || option.trigger === -1;
},
onRefreshMax: () => {
buildOption.maxButton.updateLabel(UiComponent.renderAbsolute(option.max, host));
buildOption.maxButton.updateLabel(host.renderAbsolute(option.max));
buildOption.maxButton.element[0].title =
option.max < 0
? host.engine.i18n("ui.max.build.titleInfinite", [label])
: option.max === 0
? host.engine.i18n("ui.max.build.titleZero", [label])
: host.engine.i18n("ui.max.build.title", [
UiComponent.renderAbsolute(option.max, host),
label,
]);
: host.engine.i18n("ui.max.build.title", [host.renderAbsolute(option.max), label]);
},
onRefreshTrigger: () => {
buildOption.triggerButton.element[0].title = host.engine.i18n("ui.trigger", [
option.trigger < 0
? sectionSetting.trigger < 0
? host.engine.i18n("ui.trigger.build.blocked", [sectionLabel])
: `${UiComponent.renderPercentage(sectionSetting.trigger, locale.selected, true)} (${host.engine.i18n("ui.trigger.build.inherited")})`
: UiComponent.renderPercentage(option.trigger, locale.selected, true),
: `${host.renderPercentage(sectionSetting.trigger, locale.selected, true)} (${host.engine.i18n("ui.trigger.build.inherited")})`
: host.renderPercentage(option.trigger, locale.selected, true),
]);
},
onSetMax: () => {
Expand All @@ -56,9 +52,9 @@ export const BuildSectionTools = {
host.engine.i18n("ui.max.prompt.absolute"),
host.engine.i18n("ui.max.build.prompt", [
label,
UiComponent.renderAbsolute(option.max, host),
host.renderAbsolute(option.max, locale.selected),
]),
option.max.toString(),
host.renderAbsolute(option.max),
host.engine.i18n("ui.max.build.promptExplainer"),
)
.then(value => {
Expand All @@ -75,7 +71,7 @@ export const BuildSectionTools = {
option.enabled = false;
}

option.max = UiComponent.parseAbsolute(value) ?? option.max;
option.max = host.parseAbsolute(value) ?? option.max;
})
.then(() => {
buildOption.refreshUi();
Expand All @@ -89,10 +85,10 @@ export const BuildSectionTools = {
host.engine.i18n("ui.trigger.build.prompt", [
label,
option.trigger !== -1
? UiComponent.renderPercentage(option.trigger, locale.selected, true)
? host.renderPercentage(option.trigger, locale.selected, true)
: host.engine.i18n("ui.trigger.build.inherited"),
]),
option.trigger !== -1 ? UiComponent.renderPercentage(option.trigger) : "",
option.trigger !== -1 ? host.renderPercentage(option.trigger) : "",
host.engine.i18n("ui.trigger.build.promptExplainer"),
)
.then(value => {
Expand All @@ -105,7 +101,7 @@ export const BuildSectionTools = {
return;
}

option.trigger = UiComponent.parsePercentage(value);
option.trigger = host.parsePercentage(value);
})
.then(() => {
buildOption.refreshUi();
Expand Down
Loading

0 comments on commit 73d73fc

Please sign in to comment.