From ed9d0a229744502b045d7c31b8d643ff5262240b Mon Sep 17 00:00:00 2001 From: Lukas Obermann Date: Sun, 6 Oct 2024 20:44:48 +0200 Subject: [PATCH] refactor: restructure activatable skill partial helpers --- package.json | 2 +- src/entities/liturgicalChant.ts | 126 ++-- .../partial/rated/activatable/castingTime.ts | 300 ++++---- .../activatable/checkResultBased.test.ts | 31 - .../rated/activatable/checkResultBased.ts | 14 +- .../partial/rated/activatable/cost.ts | 663 +++++++++--------- .../partial/rated/activatable/duration.ts | 350 ++++----- .../partial/rated/activatable/effect.ts | 26 +- .../partial/rated/activatable/index.ts | 229 +++--- .../partial/rated/activatable/isMaximum.ts | 21 - .../rated/activatable/isMinimumMaximum.ts | 54 ++ .../rated/activatable/modifiableParameter.ts | 8 - .../rated/activatable/nonModifiable.ts | 107 --- .../rated/activatable/nonModifiableSuffix.ts | 91 +++ .../partial/rated/activatable/parensIf.ts | 10 +- .../partial/rated/activatable/range.ts | 431 ++++++------ .../partial/rated/activatable/speed.ts | 6 +- .../rated/activatable/targetCategory.ts | 117 ++-- .../partial/rated/activatable/units.ts | 127 ---- src/entities/partial/responsiveText.ts | 32 +- src/entities/partial/units/energy.ts | 48 ++ src/entities/partial/units/length.ts | 30 + src/entities/partial/units/timeSpan.ts | 52 ++ src/entities/skill.ts | 2 +- src/entities/spell.ts | 126 ++-- src/helpers/locale.ts | 9 + src/libraryEntry.ts | 11 +- src/tsconfig.json | 1 + .../rated/activatable/checkResultBased.ts | 35 + test/tsconfig.json | 28 + tsconfig.json | 1 + 31 files changed, 1632 insertions(+), 1456 deletions(-) delete mode 100644 src/entities/partial/rated/activatable/checkResultBased.test.ts delete mode 100644 src/entities/partial/rated/activatable/isMaximum.ts create mode 100644 src/entities/partial/rated/activatable/isMinimumMaximum.ts delete mode 100644 src/entities/partial/rated/activatable/modifiableParameter.ts delete mode 100644 src/entities/partial/rated/activatable/nonModifiable.ts create mode 100644 src/entities/partial/rated/activatable/nonModifiableSuffix.ts delete mode 100644 src/entities/partial/rated/activatable/units.ts create mode 100644 src/entities/partial/units/energy.ts create mode 100644 src/entities/partial/units/length.ts create mode 100644 src/entities/partial/units/timeSpan.ts create mode 100644 src/helpers/locale.ts create mode 100644 test/entities/partial/rated/activatable/checkResultBased.ts create mode 100644 test/tsconfig.json diff --git a/package.json b/package.json index b027fd0..8ea6a2b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "build": "tsc -b", "watch": "tsc -b -w", "release": "commit-and-tag-version", - "test": "glob -c \"node --import tsx --test\" \"./src/**/*.test.ts\"" + "test": "glob -c \"node --import tsx --test\" \"./test/**/*.ts\"" }, "repository": { "type": "git", diff --git a/src/entities/liturgicalChant.ts b/src/entities/liturgicalChant.ts index 4d2e47d..5f10b6f 100644 --- a/src/entities/liturgicalChant.ts +++ b/src/entities/liturgicalChant.ts @@ -13,17 +13,17 @@ import { createLibraryEntryCreator, LibraryEntryContent, } from "../libraryEntry.js" -import { getTextForBlessingDuration } from "./partial/rated/activatable/duration.js" +import { getDurationTranslationForBlessing } from "./partial/rated/activatable/duration.js" import { getTextForEffect } from "./partial/rated/activatable/effect.js" import { Entity } from "./partial/rated/activatable/entity.js" import { - getTextForFastOneTimePerformanceParameters, - getTextForFastSustainedPerformanceParameters, - getTextForSlowOneTimePerformanceParameters, - getTextForSlowSustainedPerformanceParameters, + getFastOneTimePerformanceParametersTranslations, + getFastSustainedPerformanceParametersTranslations, + getSlowOneTimePerformanceParametersTranslations, + getSlowSustainedPerformanceParametersTranslations, } from "./partial/rated/activatable/index.js" import { getTextForBlessingRange } from "./partial/rated/activatable/range.js" -import { getTextForTargetCategory } from "./partial/rated/activatable/targetCategory.js" +import { getTargetCategoryTranslation } from "./partial/rated/activatable/targetCategory.js" import { createImprovementCost } from "./partial/rated/improvementCost.js" import { getTextForCheck } from "./partial/rated/skillCheck.js" import { ResponsiveTextSize } from "./partial/responsiveText.js" @@ -92,23 +92,24 @@ export const getBlessingLibraryEntry = createLibraryEntryCreator< { getTargetCategoryById: GetById.Static.TargetCategory } ->((entry, { getTargetCategoryById }) => ({ translate, translateMap }) => { +>((entry, { getTargetCategoryById }) => (locale) => { + const { translate, translateMap } = locale const translation = translateMap(entry.translations) if (translation === undefined) { return undefined } - const range = getTextForBlessingRange({ translate }, entry.parameters.range, { - responsiveText: ResponsiveTextSize.Full, - }) + const range = getTextForBlessingRange( + locale, + ResponsiveTextSize.Full, + entry.parameters.range + ) - const duration = getTextForBlessingDuration( - { translate, translateMap }, - entry.parameters.duration, - { - responsiveText: ResponsiveTextSize.Full, - } + const duration = getDurationTranslationForBlessing( + locale, + ResponsiveTextSize.Full, + entry.parameters.duration ) return { @@ -133,10 +134,7 @@ export const getBlessingLibraryEntry = createLibraryEntryCreator< ? `***${duration}*** (${translation.duration})` : duration, }, - getTextForTargetCategory( - { translate, translateMap, getTargetCategoryById }, - entry.target - ), + getTargetCategoryTranslation(getTargetCategoryById, locale, entry.target), ], src: entry.src, } @@ -169,7 +167,8 @@ export const getLiturgicalChantLibraryEntry = createLibraryEntryCreator< getAspectById, } ) => - ({ translate, translateMap, localeCompare }) => { + (locale) => { + const { translate, translateMap, compare: localeCompare } = locale const translation = translateMap(entry.translations) if (translation === undefined) { @@ -179,31 +178,21 @@ export const getLiturgicalChantLibraryEntry = createLibraryEntryCreator< const { castingTime, cost, range, duration } = (() => { switch (entry.parameters.tag) { case "OneTime": - return getTextForFastOneTimePerformanceParameters( - { - getSkillModificationLevelById, - translate, - translateMap, - }, - entry.parameters.one_time, - { - entity: Entity.LiturgicalChant, - responsiveText: ResponsiveTextSize.Full, - } + return getFastOneTimePerformanceParametersTranslations( + getSkillModificationLevelById, + locale, + Entity.LiturgicalChant, + ResponsiveTextSize.Full, + entry.parameters.one_time ) case "Sustained": - return getTextForFastSustainedPerformanceParameters( - { - getSkillModificationLevelById, - translate, - translateMap, - }, - entry.parameters.sustained, - { - entity: Entity.LiturgicalChant, - responsiveText: ResponsiveTextSize.Full, - } + return getFastSustainedPerformanceParametersTranslations( + getSkillModificationLevelById, + locale, + Entity.LiturgicalChant, + ResponsiveTextSize.Full, + entry.parameters.sustained ) default: @@ -225,7 +214,7 @@ export const getLiturgicalChantLibraryEntry = createLibraryEntryCreator< getToughness, } ), - ...getTextForEffect(translation.effect, translate), + ...getTextForEffect(locale, translation.effect), { label: translate("Liturgical Time"), value: @@ -254,8 +243,9 @@ export const getLiturgicalChantLibraryEntry = createLibraryEntryCreator< ? `***${duration}*** (${translation.duration.full})` : duration, }, - getTextForTargetCategory( - { translate, translateMap, getTargetCategoryById }, + getTargetCategoryTranslation( + getTargetCategoryById, + locale, entry.target ), getTextForTraditions( @@ -302,7 +292,8 @@ export const getCeremonyLibraryEntry = createLibraryEntryCreator< getAspectById, } ) => - ({ translate, translateMap, localeCompare }) => { + (locale) => { + const { translate, translateMap, compare: localeCompare } = locale const translation = translateMap(entry.translations) if (translation === undefined) { @@ -312,31 +303,21 @@ export const getCeremonyLibraryEntry = createLibraryEntryCreator< const { castingTime, cost, range, duration } = (() => { switch (entry.parameters.tag) { case "OneTime": - return getTextForSlowOneTimePerformanceParameters( - { - getSkillModificationLevelById, - translate, - translateMap, - }, - entry.parameters.one_time, - { - entity: Entity.Ceremony, - responsiveText: ResponsiveTextSize.Full, - } + return getSlowOneTimePerformanceParametersTranslations( + getSkillModificationLevelById, + locale, + Entity.Ritual, + ResponsiveTextSize.Full, + entry.parameters.one_time ) case "Sustained": - return getTextForSlowSustainedPerformanceParameters( - { - getSkillModificationLevelById, - translate, - translateMap, - }, - entry.parameters.sustained, - { - entity: Entity.Ceremony, - responsiveText: ResponsiveTextSize.Full, - } + return getSlowSustainedPerformanceParametersTranslations( + getSkillModificationLevelById, + locale, + Entity.Ceremony, + ResponsiveTextSize.Full, + entry.parameters.sustained ) default: @@ -358,7 +339,7 @@ export const getCeremonyLibraryEntry = createLibraryEntryCreator< getToughness, } ), - ...getTextForEffect(translation.effect, translate), + ...getTextForEffect(locale, translation.effect), { label: translate("Ceremonial Time"), value: @@ -387,8 +368,9 @@ export const getCeremonyLibraryEntry = createLibraryEntryCreator< ? `***${duration}*** (${translation.duration.full})` : duration, }, - getTextForTargetCategory( - { translate, translateMap, getTargetCategoryById }, + getTargetCategoryTranslation( + getTargetCategoryById, + locale, entry.target ), getTextForTraditions( diff --git a/src/entities/partial/rated/activatable/castingTime.ts b/src/entities/partial/rated/activatable/castingTime.ts index c3dc877..eb44d6c 100644 --- a/src/entities/partial/rated/activatable/castingTime.ts +++ b/src/entities/partial/rated/activatable/castingTime.ts @@ -3,6 +3,7 @@ import { assertExhaustive } from "@optolith/helpers/typeSafety" import { CastingTime, CastingTimeDuringLovemaking, + CastingTimeIncludingLovemaking, FastCastingTime, FastSkillNonModifiableCastingTime, ModifiableCastingTime, @@ -10,188 +11,179 @@ import { SlowSkillNonModifiableCastingTime, } from "optolith-database-schema/types/_ActivatableSkillCastingTime" import { GetById } from "../../../../helpers/getTypes.js" -import { Translate } from "../../../../helpers/translate.js" +import { LocaleEnvironment } from "../../../../helpers/locale.js" import { ResponsiveTextSize } from "../../responsiveText.js" +import { formatTimeSpan } from "../../units/timeSpan.js" import { MISSING_VALUE } from "../../unknown.js" import { Entity } from "./entity.js" -import { ModifiableParameter } from "./modifiableParameter.js" -import { getTextForNonModifiableSuffix } from "./nonModifiable.js" -import { Speed } from "./speed.js" -import { formatTimeSpan } from "./units.js" +import { + getNonModifiableSuffixTranslation, + ModifiableParameter, +} from "./nonModifiableSuffix.js" +import { getModifiableBySpeed, Speed } from "./speed.js" -const getTextForModifiableCastingTime = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - }, - value: ModifiableCastingTime, - env: { - speed: Speed - responsiveText: ResponsiveTextSize - } +const getModifiableCastingTimeTranslation = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + responsiveTextSize: ResponsiveTextSize, + value: ModifiableCastingTime ): string => mapNullable( - deps.getSkillModificationLevelById(value.initial_modification_level), - ({ - fast: { casting_time: fastTime }, - slow: { casting_time: slowTime }, - }) => { - switch (env.speed) { - case Speed.Fast: - return formatTimeSpan( - deps.translate, - env.responsiveText, + getSkillModificationLevelById(value.initial_modification_level), + (modificationLevel) => + getModifiableBySpeed( + (config) => + formatTimeSpan( + locale, + responsiveTextSize, "Actions", - fastTime - ) - case Speed.Slow: - return formatTimeSpan( - deps.translate, - env.responsiveText, - slowTime.unit, - slowTime.value - ) - default: - return assertExhaustive(env.speed) - } - } + config.casting_time + ), + (config) => + formatTimeSpan( + locale, + responsiveTextSize, + config.casting_time.unit, + config.casting_time.value + ), + speed, + modificationLevel + ) ) ?? MISSING_VALUE -const getTextForFastSkillNonModifiableCastingTime = ( - deps: { - translate: Translate - }, - value: FastSkillNonModifiableCastingTime, - env: { - entity: Entity - responsiveText: ResponsiveTextSize - } +const getFastSkillNonModifiableCastingTimeTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: FastSkillNonModifiableCastingTime ): string => - formatTimeSpan(deps.translate, env.responsiveText, "Actions", value.actions) + - getTextForNonModifiableSuffix( - deps.translate, - env.entity, - ModifiableParameter.CastingTime, - env.responsiveText - ) + formatTimeSpan(locale, responsiveTextSize, "Actions", value.actions) -const getTextForSlowSkillNonModifiableCastingTime = ( - deps: { - translate: Translate - }, - value: SlowSkillNonModifiableCastingTime, - env: { - entity: Entity - responsiveText: ResponsiveTextSize - } -): string => - formatTimeSpan(deps.translate, env.responsiveText, value.unit, value.value) + - getTextForNonModifiableSuffix( - deps.translate, - env.entity, - ModifiableParameter.CastingTime, - env.responsiveText - ) +const getSlowSkillNonModifiableCastingTimeTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: SlowSkillNonModifiableCastingTime +): string => formatTimeSpan(locale, responsiveTextSize, value.unit, value.value) -const getTextForCastingTime = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - }, - value: CastingTime, - env: { - speed: Speed - responsiveText: ResponsiveTextSize - }, - getTextForNonModifiableCastingTime: (value: NonModifiable) => string +const getCastingTimeTranslation = ( + getNonModifiableCastingTimeTranslation: (value: NonModifiable) => string, + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: CastingTime ): string => { switch (value.tag) { case "Modifiable": - return getTextForModifiableCastingTime(deps, value.modifiable, env) + return getModifiableCastingTimeTranslation( + getSkillModificationLevelById, + locale, + speed, + responsiveTextSize, + value.modifiable + ) case "NonModifiable": - return getTextForNonModifiableCastingTime(value.non_modifiable) + return ( + getNonModifiableCastingTimeTranslation(value.non_modifiable) + + getNonModifiableSuffixTranslation( + locale, + entity, + ModifiableParameter.CastingTime, + responsiveTextSize + ) + ) default: return assertExhaustive(value) } } -const getTextForCastingTimeDuringLovemaking = ( - deps: { - translate: Translate - }, - value: CastingTimeDuringLovemaking, - env: { - responsiveText: ResponsiveTextSize - } -): string => - formatTimeSpan(deps.translate, env.responsiveText, value.unit, value.value) +const getCastingTimeDuringLovemakingTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: CastingTimeDuringLovemaking +): string => formatTimeSpan(locale, responsiveTextSize, value.unit, value.value) + +const getCastingTimeIncludingLovemakingTranslation = < + NonModifiable extends object +>( + getNonModifiableCastingTimeTranslation: (value: NonModifiable) => string, + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: CastingTimeIncludingLovemaking +) => + [ + mapNullable(value.default, (def) => + getCastingTimeTranslation( + getNonModifiableCastingTimeTranslation, + getSkillModificationLevelById, + locale, + speed, + entity, + responsiveTextSize, + def + ) + ), + mapNullable(value.during_lovemaking, (duringLovemaking) => + getCastingTimeDuringLovemakingTranslation( + locale, + responsiveTextSize, + duringLovemaking + ) + ), + ] + .filter(isNotNullish) + .join(" / ") /** * Get the text for the casting time of a fast activatable skill. */ -export const getTextForFastCastingTime = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - }, - value: FastCastingTime, - env: { - entity: Entity - responsiveText: ResponsiveTextSize - } +export const getFastCastingTimeTranslation = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: FastCastingTime ): string => - [ - mapNullable(value.default, (def) => - getTextForCastingTime( - deps, - def, - { ...env, speed: Speed.Fast }, - (nonModifiableValue) => - getTextForFastSkillNonModifiableCastingTime( - deps, - nonModifiableValue, - env - ) - ) - ), - mapNullable(value.during_lovemaking, (duringLovemaking) => - getTextForCastingTimeDuringLovemaking(deps, duringLovemaking, env) - ), - ] - .filter(isNotNullish) - .join(" / ") + getCastingTimeIncludingLovemakingTranslation( + (nonModifiableValue) => + getFastSkillNonModifiableCastingTimeTranslation( + locale, + responsiveTextSize, + nonModifiableValue + ), + getSkillModificationLevelById, + locale, + Speed.Fast, + entity, + responsiveTextSize, + value + ) /** * Get the text for the casting time of a slow activatable skill. */ -export const getTextForSlowCastingTime = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - }, - value: SlowCastingTime, - env: { - entity: Entity - responsiveText: ResponsiveTextSize - } +export const getSlowCastingTimeTranslation = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: SlowCastingTime ): string => - [ - mapNullable(value.default, (def) => - getTextForCastingTime( - deps, - def, - { ...env, speed: Speed.Slow }, - (nonModifiableValue) => - getTextForSlowSkillNonModifiableCastingTime( - deps, - nonModifiableValue, - env - ) - ) - ), - mapNullable(value.during_lovemaking, (duringLovemaking) => - getTextForCastingTimeDuringLovemaking(deps, duringLovemaking, env) - ), - ] - .filter(isNotNullish) - .join(" / ") + getCastingTimeIncludingLovemakingTranslation( + (nonModifiableValue) => + getSlowSkillNonModifiableCastingTimeTranslation( + locale, + responsiveTextSize, + nonModifiableValue + ), + getSkillModificationLevelById, + locale, + Speed.Slow, + entity, + responsiveTextSize, + value + ) diff --git a/src/entities/partial/rated/activatable/checkResultBased.test.ts b/src/entities/partial/rated/activatable/checkResultBased.test.ts deleted file mode 100644 index a1e8851..0000000 --- a/src/entities/partial/rated/activatable/checkResultBased.test.ts +++ /dev/null @@ -1,31 +0,0 @@ -import assert from "node:assert/strict" -import { describe, it } from "node:test" -import { translateMock } from "../../../../helpers/translate.js" -import { getTextForCheckResultBased } from "./checkResultBased.js" - -describe("getTextForCheckResultBased", () => { - it("should return the value text for a check-result-based parameter of an activatable skill", () => { - assert.equal( - getTextForCheckResultBased({ base: "QualityLevels" }, translateMock), - "QL" - ) - assert.equal( - getTextForCheckResultBased({ base: "SkillPoints" }, translateMock), - "SP" - ) - assert.equal( - getTextForCheckResultBased( - { base: "QualityLevels", modifier: { arithmetic: "Divide", value: 2 } }, - translateMock - ), - "QL / 2" - ) - assert.equal( - getTextForCheckResultBased( - { base: "SkillPoints", modifier: { arithmetic: "Multiply", value: 3 } }, - translateMock - ), - "SP × 3" - ) - }) -}) diff --git a/src/entities/partial/rated/activatable/checkResultBased.ts b/src/entities/partial/rated/activatable/checkResultBased.ts index bda8503..5933651 100644 --- a/src/entities/partial/rated/activatable/checkResultBased.ts +++ b/src/entities/partial/rated/activatable/checkResultBased.ts @@ -7,9 +7,9 @@ import { } from "optolith-database-schema/types/_ActivatableSkillCheckResultBased" import { Translate } from "../../../../helpers/translate.js" -const getCheckResultBaseValue = ( - baseValue: CheckResultValue, - translate: Translate +const getCheckResultBaseValueTranslation = ( + translate: Translate, + baseValue: CheckResultValue ) => { switch (baseValue) { case "QualityLevels": @@ -36,11 +36,11 @@ const getArithmeticSymbol = (arithmetic: CheckResultArithmetic) => { * Returns the value text for a check-result-based parameter of an activatable * skill. */ -export const getTextForCheckResultBased = ( - value: CheckResultBased, - translate: Translate +export const getCheckResultBasedValueTranslation = ( + translate: Translate, + value: CheckResultBased ): string => - getCheckResultBaseValue(value.base, translate) + + getCheckResultBaseValueTranslation(translate, value.base) + mapNullableDefault( value.modifier, (modifier) => getArithmeticSymbol(modifier.arithmetic) + modifier.value, diff --git a/src/entities/partial/rated/activatable/cost.ts b/src/entities/partial/rated/activatable/cost.ts index 31809b3..ab9f290 100644 --- a/src/entities/partial/rated/activatable/cost.ts +++ b/src/entities/partial/rated/activatable/cost.ts @@ -4,247 +4,246 @@ import { CostMap, IndefiniteOneTimeCost, ModifiableOneTimeCost, + ModifiableSustainedCost, MultipleOneTimeCosts, NonModifiableOneTimeCost, NonModifiableOneTimeCostPerCountable, + NonModifiableSustainedCost, OneTimeCost, SingleOneTimeCost, SustainedCost, } from "optolith-database-schema/types/_ActivatableSkillCost" import { GetById } from "../../../../helpers/getTypes.js" -import { Translate, TranslateMap } from "../../../../helpers/translate.js" +import { LocaleEnvironment } from "../../../../helpers/locale.js" import { + appendNoteIfRequested, getResponsiveText, - getResponsiveTextOptional, replaceTextIfRequested, responsive, ResponsiveTextSize, } from "../../responsiveText.js" +import { formatEnergyByEntity } from "../../units/energy.js" +import { formatTimeSpan } from "../../units/timeSpan.js" import { MISSING_VALUE } from "../../unknown.js" import { Entity } from "./entity.js" -import { ModifiableParameter } from "./modifiableParameter.js" -import { getTextForNonModifiableSuffix } from "./nonModifiable.js" +import { wrapIfMinimum } from "./isMinimumMaximum.js" +import { + getNonModifiableSuffixTranslation, + ModifiableParameter, +} from "./nonModifiableSuffix.js" import { getModifiableBySpeed, Speed } from "./speed.js" -import { formatCost, formatTimeSpan } from "./units.js" -const getTextForModifiableOneTimeCost = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - translateMap: TranslateMap - }, - value: ModifiableOneTimeCost, - env: { - speed: Speed - entity: Entity - responsiveText: ResponsiveTextSize - } +const getModifiableOneTimeCostTranslation = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + entity: Entity, + speed: Speed, + value: ModifiableOneTimeCost ): string => mapNullable( - deps.getSkillModificationLevelById(value.initial_modification_level), + getSkillModificationLevelById(value.initial_modification_level), (modificationLevel) => { const cost = getModifiableBySpeed( - modificationLevel, - env.speed, (config) => config.cost, - (config) => config.cost + (config) => config.cost, + speed, + modificationLevel ) return replaceTextIfRequested( + "replacement", value.translations, - formatCost(deps.translate, env.entity, cost), - deps.translateMap, - env.responsiveText + locale.translateMap, + responsiveTextSize, + formatEnergyByEntity(locale, entity, cost) ) } ) ?? MISSING_VALUE -const getMinimumText = ( - isMinimum: boolean | undefined, - translate: Translate, - responsiveText: ResponsiveTextSize -) => - isMinimum !== true - ? "" - : responsive( - responsiveText, - () => translate("at least "), - () => translate("min. ") - ) - -const getTextForNonModifiableOneTimeCostPerCountable = ( - deps: { - translate: Translate - translateMap: TranslateMap - formatCost: (x: number | string) => string - }, - value: NonModifiableOneTimeCostPerCountable | undefined, - env: { - responsiveText: ResponsiveTextSize - } +const getNonModifiableOneTimeCostPerCountableTranslation = ( + formatCost: (x: number | string) => string, + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: NonModifiableOneTimeCostPerCountable | undefined ) => mapNullable(value, (perCountable) => { + const entity = getResponsiveText( + locale.translateMap(perCountable.translations)?.countable, + responsiveTextSize + ) + const countableText = responsive( - env.responsiveText, - (entity) => deps.translate(" per {0}", entity), - (entity) => deps.translate("/{0}", entity), - getResponsiveText( - deps.translateMap(perCountable.translations)?.countable, - env.responsiveText - ) + responsiveTextSize, + () => locale.translate(" per {0}", entity), + () => locale.translate("/{0}", entity) ) const minimumTotalText = mapNullable(perCountable.minimum_total, (minimumTotal) => - deps.translate(", minimum of {0}", deps.formatCost(minimumTotal)) + locale.translate(", minimum of {0}", formatCost(minimumTotal)) ) ?? "" return countableText + minimumTotalText }) ?? "" -const getTextForPermanentValue = ( - value: number | undefined, - responsiveText: ResponsiveTextSize, - translate: Translate +const getPermanentValueTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + permanentValue: number | undefined ) => - value === undefined + permanentValue === undefined ? "" : responsive( - responsiveText, - (perm) => translate(", {0} of which are permanent", perm), - (perm) => translate(" ({0} perm.)", perm), - value + responsiveTextSize, + () => locale.translate(", {0} of which are permanent", permanentValue), + () => locale.translate(" ({0} perm.)", permanentValue) ) -const getTextForNonModifiableOneTimeCost = ( - deps: { translate: Translate; translateMap: TranslateMap }, - value: NonModifiableOneTimeCost, - env: { - responsiveText: ResponsiveTextSize - entity: Entity - } +const getNonModifiableOneTimeCostTranslation = ( + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: NonModifiableOneTimeCost ): string => { - const isMinimum = getMinimumText( + const formatCostP = formatEnergyByEntity.bind(this, locale, entity) + + const perCountable = getNonModifiableOneTimeCostPerCountableTranslation( + formatCostP, + locale, + responsiveTextSize, + value.per + ) + + const permanentValue = getPermanentValueTranslation( + locale, + responsiveTextSize, + value.permanent_value + ) + + const costWrappedIfMinimum = wrapIfMinimum( + locale, + responsiveTextSize, value.is_minimum, - deps.translate, - env.responsiveText - ) - const formatCostP = formatCost.bind(this, deps.translate, env.entity) - const per = getTextForNonModifiableOneTimeCostPerCountable( - { ...deps, formatCost: formatCostP }, - value.per, - env - ) - const permanent = getTextForPermanentValue( - value.permanent_value, - env.responsiveText, - deps.translate - ) - const translation = deps.translateMap(value.translations) - const note = mapNullableDefault( - translation === undefined || translation.note === undefined - ? undefined - : getResponsiveTextOptional(translation.note, env.responsiveText), - (noteIfPresent) => ` (${noteIfPresent})`, - "" + formatCostP(value.value) + perCountable + permanentValue ) - const cannotModify = getTextForNonModifiableSuffix( - deps.translate, - env.entity, + const withNote = appendNoteIfRequested( + "note", + value.translations, + locale.translateMap, + responsiveTextSize, + costWrappedIfMinimum + ) + + const cannotModify = getNonModifiableSuffixTranslation( + locale, + entity, ModifiableParameter.Cost, - env.responsiveText + responsiveTextSize ) - return ( - isMinimum + formatCostP(value.value) + per + permanent + note + cannotModify - ) + return withNote + cannotModify } -const getTextForIndefiniteOneTimeCost = ( - deps: { translate: Translate; translateMap: TranslateMap }, - value: IndefiniteOneTimeCost, - env: { - responsiveText: ResponsiveTextSize - entity: Entity - } +const getIndefiniteOneTimeCostTranslation = ( + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: IndefiniteOneTimeCost ): string => - (getResponsiveText( - deps.translateMap(value.translations)?.description, - env.responsiveText - ) ?? "") + - getTextForNonModifiableSuffix( - deps.translate, - env.entity, + getResponsiveText( + locale.translateMap(value.translations)?.description, + responsiveTextSize + ) + + getNonModifiableSuffixTranslation( + locale, + entity, ModifiableParameter.Cost, - env.responsiveText + responsiveTextSize ) -const getTextForSingleOneTimeCost = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - translateMap: TranslateMap - }, - value: SingleOneTimeCost, - env: { - speed: Speed - responsiveText: ResponsiveTextSize - entity: Entity - } +const getSingleOneTimeCostTranslation = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: SingleOneTimeCost ): string => { switch (value.tag) { case "Modifiable": - return getTextForModifiableOneTimeCost(deps, value.modifiable, env) + return getModifiableOneTimeCostTranslation( + getSkillModificationLevelById, + locale, + responsiveTextSize, + entity, + speed, + value.modifiable + ) case "NonModifiable": - return getTextForNonModifiableOneTimeCost(deps, value.non_modifiable, env) + return getNonModifiableOneTimeCostTranslation( + locale, + entity, + responsiveTextSize, + value.non_modifiable + ) case "Indefinite": - return getTextForIndefiniteOneTimeCost(deps, value.indefinite, env) + return getIndefiniteOneTimeCostTranslation( + locale, + entity, + responsiveTextSize, + value.indefinite + ) default: return assertExhaustive(value) } } -const getTextForMultipleOneTimeCosts = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - translateMap: TranslateMap - }, - value: MultipleOneTimeCosts, +const getMultipleOneTimeCostsTranslation = ( type: "conjunction" | "disjunction", - env: { - speed: Speed - responsiveText: ResponsiveTextSize - entity: Entity - } + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: MultipleOneTimeCosts ): string => { const modifiable = !value.every((part) => part.tag === "Modifiable") - ? getTextForNonModifiableSuffix( - deps.translate, - env.entity, + ? getNonModifiableSuffixTranslation( + locale, + entity, ModifiableParameter.Cost, - env.responsiveText + responsiveTextSize ) : "" return ( value - .map((part) => getTextForSingleOneTimeCost(deps, part, env)) + .map((part) => + getSingleOneTimeCostTranslation( + getSkillModificationLevelById, + locale, + speed, + entity, + responsiveTextSize, + part + ) + ) .join( (() => { switch (type) { case "conjunction": return responsive( - env.responsiveText, - () => deps.translate(" and "), - () => deps.translate(" + ") + responsiveTextSize, + () => locale.translate(" and "), + () => locale.translate(" + ") ) case "disjunction": return responsive( - env.responsiveText, - () => deps.translate(" or "), - () => deps.translate(" / ") + responsiveTextSize, + () => locale.translate(" or "), + () => locale.translate(" / ") ) default: return assertExhaustive(type) @@ -254,18 +253,13 @@ const getTextForMultipleOneTimeCosts = ( ) } -const getTextForCostMap = ( - deps: { - translate: Translate - translateMap: TranslateMap - }, - value: CostMap, - env: { - responsiveText: ResponsiveTextSize - entity: Entity - } +const getCostMapTranslation = ( + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: CostMap ): string => { - const translation = deps.translateMap(value.translations) + const translation = locale.translateMap(value.translations) if (value.translations !== undefined && translation === undefined) { return MISSING_VALUE @@ -275,29 +269,33 @@ const getTextForCostMap = ( return translation.replacement } - const costs = value.options.map((option) => option.value).join("/") const labels = value.options .map( - (option) => deps.translateMap(option.translations)?.label ?? MISSING_VALUE + (option) => + locale.translateMap(option.translations)?.label ?? MISSING_VALUE ) .join("/") + + const costs = value.options.map((option) => option.value).join("/") + const permanentCosts = value.options.every( (option) => option.permanent_value !== undefined ) ? value.options.map((option) => option.permanent_value!).join("/") : undefined - const formatCostP = formatCost.bind(this, deps.translate, env.entity) - const notModifiable = getTextForNonModifiableSuffix( - deps.translate, - env.entity, + const formatCostP = formatEnergyByEntity.bind(this, locale, entity) + + const notModifiable = getNonModifiableSuffixTranslation( + locale, + entity, ModifiableParameter.Cost, - env.responsiveText + responsiveTextSize ) return ( formatCostP(costs) + - deps.translate(" for ") + + locale.translate(" for ") + mapNullableDefault( translation?.list_prepend, (listPrepend) => `${listPrepend} `, @@ -306,7 +304,7 @@ const getTextForCostMap = ( labels + (translation?.list_append ?? "") + (permanentCosts !== undefined - ? deps.translate( + ? locale.translate( ", {0} of which are permanent", formatCostP(permanentCosts) ) @@ -318,172 +316,203 @@ const getTextForCostMap = ( /** * Returns the text for the cost of a one-time activatable skill. */ -export const getTextForOneTimeCost = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - translateMap: TranslateMap - }, - value: OneTimeCost, - env: { - speed: Speed - responsiveText: ResponsiveTextSize - entity: Entity - } +export const getOneTimeCostTranslation = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: OneTimeCost ): string => { switch (value.tag) { case "Single": - return getTextForSingleOneTimeCost(deps, value.single, env) + return getSingleOneTimeCostTranslation( + getSkillModificationLevelById, + locale, + speed, + entity, + responsiveTextSize, + value.single + ) case "Conjunction": - return getTextForMultipleOneTimeCosts( - deps, - value.conjunction, + return getMultipleOneTimeCostsTranslation( "conjunction", - env + getSkillModificationLevelById, + locale, + speed, + entity, + responsiveTextSize, + value.conjunction ) case "Disjunction": - return getTextForMultipleOneTimeCosts( - deps, - value.disjunction, + return getMultipleOneTimeCostsTranslation( "disjunction", - env + getSkillModificationLevelById, + locale, + speed, + entity, + responsiveTextSize, + value.disjunction ) case "Map": - return getTextForCostMap(deps, value.map, env) + return getCostMapTranslation( + locale, + entity, + responsiveTextSize, + value.map + ) default: return assertExhaustive(value) } } +const getModifiableSustainedCostTranslation = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: ModifiableSustainedCost +) => + mapNullable( + getSkillModificationLevelById(value.initial_modification_level), + (modificationLevel) => { + const cost = getModifiableBySpeed( + (config) => config.cost, + (config) => config.cost, + speed, + modificationLevel + ) + + const formatCostP = formatEnergyByEntity.bind(this, locale, entity) + + const interval = formatTimeSpan( + locale, + responsiveTextSize, + value.interval.unit, + value.interval.value + ) + + return responsive( + responsiveTextSize, + () => + `${formatCostP(cost) + locale.translate(" (casting)")} + ${ + formatCostP(cost / 2) + locale.translate(" per {0}", interval) + }`, + () => + `${formatCostP(cost)} + ${ + formatCostP(cost / 2) + locale.translate("/{0}", interval) + }` + ) + } + ) ?? MISSING_VALUE + +const getNonModifiableSustainedCostTranslation = ( + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: NonModifiableSustainedCost +) => { + const formatCostP = formatEnergyByEntity.bind(this, locale, entity) + + const per = (() => { + if (value.per === undefined) { + return { countable: "", minimumTotal: "" } + } + + const countable = responsive( + responsiveTextSize, + (entity) => locale.translate(" per {0}", entity), + (entity) => locale.translate("/{0}", entity), + getResponsiveText( + locale.translateMap(value.per.translations)?.countable, + responsiveTextSize + ) + ) + + const minimumTotal = + value.per.minimum_total !== undefined + ? locale.translate( + ", minimum of {0}", + formatCostP(value.per.minimum_total) + ) + : "" + + return { countable, minimumTotal } + })() + + const interval = formatTimeSpan( + locale, + responsiveTextSize, + value.interval.unit, + value.interval.value + ) + + const cost = + responsive( + responsiveTextSize, + () => + `${formatCostP(value.value) + locale.translate(" (casting)")} + ${ + (value.is_minimum === true + ? locale.translate("half of the activation cost") + : formatCostP(value.value / 2)) + + per.countable + + locale.translate(" per {0}", interval) + }`, + () => + `${formatCostP(value.value)} + ${ + (value.is_minimum === true ? "50%" : formatCostP(value.value / 2)) + + per.countable + + locale.translate("/{0}", interval) + }` + ) + per.minimumTotal + + const costWrappedIfMinimum = wrapIfMinimum( + locale, + responsiveTextSize, + value.is_minimum, + cost + ) + + return ( + costWrappedIfMinimum + + getNonModifiableSuffixTranslation( + locale, + entity, + ModifiableParameter.Cost, + responsiveTextSize + ) + ) +} + /** * Returns the text for the cost of a sustained activatable skill. */ -export const getTextForSustainedCost = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - translateMap: TranslateMap - }, - value: SustainedCost, - env: { - speed: Speed - responsiveText: ResponsiveTextSize - entity: Entity - } +export const getSustainedCostTranslation = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: SustainedCost ): string => { switch (value.tag) { - case "Modifiable": { - const modificationLevel = deps.getSkillModificationLevelById( - value.modifiable.initial_modification_level + case "Modifiable": + return getModifiableSustainedCostTranslation( + getSkillModificationLevelById, + locale, + speed, + entity, + responsiveTextSize, + value.modifiable ) - - if (modificationLevel === undefined) { - return MISSING_VALUE - } - - const cost = (() => { - switch (env.speed) { - case Speed.Fast: - return modificationLevel.fast.cost - case Speed.Slow: - return modificationLevel.slow.cost - default: - return assertExhaustive(env.speed) - } - })() - - const formatCostP = formatCost.bind(this, deps.translate, env.entity) - const interval = formatTimeSpan( - deps.translate, - env.responsiveText, - value.modifiable.interval.unit, - value.modifiable.interval.value + case "NonModifiable": + return getNonModifiableSustainedCostTranslation( + locale, + entity, + responsiveTextSize, + value.non_modifiable ) - - return responsive( - env.responsiveText, - () => - `${formatCostP(cost) + deps.translate(" (casting)")} + ${ - formatCostP(cost / 2) + deps.translate(" per {0}", interval) - }`, - () => - `${formatCostP(cost)} + ${ - formatCostP(cost / 2) + deps.translate("/{0}", interval) - }` - ) - } - case "NonModifiable": { - const isMinimum = getMinimumText( - value.non_modifiable.is_minimum, - deps.translate, - env.responsiveText - ) - const cost = value.non_modifiable.value - const formatCostP = formatCost.bind(this, deps.translate, env.entity) - - const per = (() => { - if (value.non_modifiable.per === undefined) { - return { countable: "", minimumTotal: "" } - } - - const countable = responsive( - env.responsiveText, - (entity) => deps.translate(" per {0}", entity), - (entity) => deps.translate("/{0}", entity), - getResponsiveText( - deps.translateMap(value.non_modifiable.per.translations)?.countable, - env.responsiveText - ) - ) - - const minimumTotal = - value.non_modifiable.per.minimum_total !== undefined - ? deps.translate( - ", minimum of {0}", - formatCostP(value.non_modifiable.per.minimum_total) - ) - : "" - - return { countable, minimumTotal } - })() - - const interval = formatTimeSpan( - deps.translate, - env.responsiveText, - value.non_modifiable.interval.unit, - value.non_modifiable.interval.value - ) - - return ( - isMinimum + - responsive( - env.responsiveText, - () => - `${formatCostP(cost) + deps.translate(" (casting)")} + ${ - (value.non_modifiable.is_minimum === true - ? deps.translate("half of the activation cost") - : formatCostP(cost / 2)) + - per.countable + - deps.translate(" per {0}", interval) - }`, - () => - `${formatCostP(cost)} + ${ - (value.non_modifiable.is_minimum === true - ? "50%" - : formatCostP(cost / 2)) + - per.countable + - deps.translate("/{0}", interval) - }` - ) + - per.minimumTotal + - getTextForNonModifiableSuffix( - deps.translate, - env.entity, - ModifiableParameter.Cost, - env.responsiveText - ) - ) - } default: return assertExhaustive(value) } diff --git a/src/entities/partial/rated/activatable/duration.ts b/src/entities/partial/rated/activatable/duration.ts index 33595e5..8ac93d4 100644 --- a/src/entities/partial/rated/activatable/duration.ts +++ b/src/entities/partial/rated/activatable/duration.ts @@ -1,176 +1,165 @@ -import { mapNullableDefault } from "@optolith/helpers/nullable" +import { mapNullable } from "@optolith/helpers/nullable" import { assertExhaustive } from "@optolith/helpers/typeSafety" +import { CastingTimeDuringLovemaking } from "optolith-database-schema/types/_ActivatableSkillCastingTime" import { CheckResultBasedDuration, DurationForOneTime, DurationForSustained, FixedDuration, Immediate, + IndefiniteDuration, PermanentDuration, } from "optolith-database-schema/types/_ActivatableSkillDuration" import { BlessingDuration } from "optolith-database-schema/types/Blessing" import { CantripDuration } from "optolith-database-schema/types/Cantrip" -import { Translate, TranslateMap } from "../../../../helpers/translate.js" +import { LocaleEnvironment } from "../../../../helpers/locale.js" import { getResponsiveText, replaceTextIfRequested, responsive, ResponsiveTextSize, } from "../../responsiveText.js" -import { getTextForCheckResultBased } from "./checkResultBased.js" -import { getTextForIsMaximum } from "./isMaximum.js" -import { formatTimeSpan } from "./units.js" +import { formatTimeSpan } from "../../units/timeSpan.js" +import { getCheckResultBasedValueTranslation } from "./checkResultBased.js" +import { wrapAsMaximum, wrapIfMaximum } from "./isMinimumMaximum.js" +import { appendInParensIfNotEmpty } from "./parensIf.js" -const getTextForImmediateDuration = ( - deps: { - translate: Translate - translateMap: TranslateMap - }, - value: Immediate, - env: { - responsiveText: ResponsiveTextSize - } +const getImmediateDurationTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: Immediate ): string => { - const text = - deps.translate("Immediate") + - mapNullableDefault( - value.maximum, - (max) => { - const maxText = formatTimeSpan( - deps.translate, - env.responsiveText, - max.unit, - max.value - ) + const text = appendInParensIfNotEmpty( + mapNullable(value.maximum, (max) => { + const maxText = formatTimeSpan( + locale, + responsiveTextSize, + max.unit, + max.value + ) - return responsive( - env.responsiveText, - () => deps.translate(" (no more than {0})", maxText), - () => deps.translate(" (max. {0})", maxText) - ) - }, - "" - ) + return responsive( + responsiveTextSize, + () => locale.translate("no more than {0}", maxText), + () => locale.translate("max. {0}", maxText) + ) + }), + locale.translate("Immediate") + ) return replaceTextIfRequested( + "replacement", value.translations, - text, - deps.translateMap, - env.responsiveText + locale.translateMap, + responsiveTextSize, + text ) } -const getTextForPermanentDuration = ( - deps: { - translate: Translate - translateMap: TranslateMap - }, - value: PermanentDuration, - env: { - responsiveText: ResponsiveTextSize - } -): string => { - const translation = deps.translateMap(value.translations) - const text = deps.translate("Permanent") - - if (translation?.replacement !== undefined) { - return getResponsiveText( - translation.replacement, - env.responsiveText - ).replace("$1", text) - } else { - return text - } -} - -const getTextForFixedDuration = ( - deps: { - translate: Translate - translateMap: TranslateMap - }, - value: FixedDuration, - env: { - responsiveText: ResponsiveTextSize - } -): string => { - const isMaximum = getTextForIsMaximum( - value.is_maximum, - deps.translate, - env.responsiveText +const getPermanentDurationTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: PermanentDuration +): string => + replaceTextIfRequested( + "replacement", + value.translations, + locale.translateMap, + responsiveTextSize, + locale.translate("Permanent") ) - const unitValue = formatTimeSpan( - deps.translate, - env.responsiveText, + +const getFixedDurationTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: FixedDuration +): string => { + const duration = formatTimeSpan( + locale, + responsiveTextSize, value.unit, value.value ) - const text = isMaximum + unitValue - const translation = deps.translateMap(value.translations) - if (translation?.replacement !== undefined) { - return getResponsiveText( - translation.replacement, - env.responsiveText - ).replace("$1", text) - } else { - return text - } -} - -const getTextForCheckResultBasedDuration = ( - deps: { - translate: Translate - translateMap: TranslateMap - }, - value: CheckResultBasedDuration, - env: { - responsiveText: ResponsiveTextSize - } -): string => { - const isMaximum = getTextForIsMaximum( + const durationWrappedIfMaximum = wrapIfMaximum( + locale, + responsiveTextSize, value.is_maximum, - deps.translate, - env.responsiveText + duration ) - return formatTimeSpan( - deps.translate, - env.responsiveText, - value.unit, - isMaximum + getTextForCheckResultBased(value, deps.translate) + return replaceTextIfRequested( + "replacement", + value.translations, + locale.translateMap, + responsiveTextSize, + durationWrappedIfMaximum ) } +const getCheckResultBasedDurationTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: CheckResultBasedDuration +): string => { + const duration = formatTimeSpan( + locale, + responsiveTextSize, + value.unit, + getCheckResultBasedValueTranslation(locale.translate, value) + ) + + return wrapIfMaximum(locale, responsiveTextSize, value.is_maximum, duration) +} + +const getIndefiniteDurationTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: IndefiniteDuration +) => + getResponsiveText( + locale.translateMap(value.translations)?.description, + responsiveTextSize + ) + /** * Returns the text for the duration of a one-time activatable skill. */ -export const getTextForDurationForOneTime = ( - deps: { - translate: Translate - translateMap: TranslateMap - }, - value: DurationForOneTime, - env: { - responsiveText: ResponsiveTextSize - } +export const getDurationForOneTimeTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: DurationForOneTime ): string => { switch (value.tag) { case "Immediate": - return getTextForImmediateDuration(deps, value.immediate, env) + return getImmediateDurationTranslation( + locale, + responsiveTextSize, + value.immediate + ) case "Permanent": - return getTextForPermanentDuration(deps, value.permanent, env) + return getPermanentDurationTranslation( + locale, + responsiveTextSize, + value.permanent + ) case "Fixed": - return getTextForFixedDuration(deps, value.fixed, env) + return getFixedDurationTranslation( + locale, + responsiveTextSize, + value.fixed + ) case "CheckResultBased": - return getTextForCheckResultBasedDuration( - deps, - value.check_result_based, - env + return getCheckResultBasedDurationTranslation( + locale, + responsiveTextSize, + value.check_result_based ) case "Indefinite": - return getResponsiveText( - deps.translateMap(value.indefinite.translations)?.description, - env.responsiveText + return getIndefiniteDurationTranslation( + locale, + responsiveTextSize, + value.indefinite ) default: return assertExhaustive(value) @@ -180,61 +169,67 @@ export const getTextForDurationForOneTime = ( /** * Returns the text for the duration of a sustained activatable skill. */ -export const getTextForDurationForSustained = ( - deps: { translate: Translate }, - value: DurationForSustained | undefined, - env: { - responsiveText: ResponsiveTextSize - } +export const getDurationForSustainedTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: DurationForSustained | undefined ): string => value === undefined ? responsive( - env.responsiveText, - () => deps.translate("Sustained"), - () => deps.translate("(S)") + responsiveTextSize, + () => locale.translate("Sustained"), + () => locale.translate("(S)") ) - : responsive( - env.responsiveText, - () => deps.translate("no more than "), - () => deps.translate("max. ") - ) + - formatTimeSpan( - deps.translate, - env.responsiveText, - value.maximum.unit, - value.maximum.value + : wrapAsMaximum( + locale, + responsiveTextSize, + formatTimeSpan( + locale, + responsiveTextSize, + value.maximum.unit, + value.maximum.value + ) ) +const getDurationDuringLovemakingTranslation = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: CastingTimeDuringLovemaking +): string => formatTimeSpan(locale, responsiveTextSize, value.unit, value.value) + /** * Returns the text for the duration of a cantrip. */ -export const getTextForCantripDuration = ( - deps: { translate: Translate; translateMap: TranslateMap }, - value: CantripDuration, - env: { - responsiveText: ResponsiveTextSize - } +export const getDurationTranslationForCantrip = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: CantripDuration ): string => { switch (value.tag) { case "Immediate": - return getTextForImmediateDuration(deps, value.immediate, env) + return getImmediateDurationTranslation( + locale, + responsiveTextSize, + value.immediate + ) case "Fixed": - return getTextForFixedDuration(deps, value.fixed, env) + return getFixedDurationTranslation( + locale, + responsiveTextSize, + value.fixed + ) case "Indefinite": - return getResponsiveText( - deps.translateMap(value.indefinite.translations)?.description, - env.responsiveText + return getIndefiniteDurationTranslation( + locale, + responsiveTextSize, + value.indefinite ) - case "DuringLovemaking": { - const { value: lovemakingValue, unit: lovemakingUnit } = + case "DuringLovemaking": + return getDurationDuringLovemakingTranslation( + locale, + responsiveTextSize, value.during_lovemaking - return formatTimeSpan( - deps.translate, - env.responsiveText, - lovemakingUnit, - lovemakingValue ) - } default: return assertExhaustive(value) } @@ -243,22 +238,29 @@ export const getTextForCantripDuration = ( /** * Returns the text for the duration of a blessing. */ -export const getTextForBlessingDuration = ( - deps: { translate: Translate; translateMap: TranslateMap }, - value: BlessingDuration, - env: { - responsiveText: ResponsiveTextSize - } +export const getDurationTranslationForBlessing = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: BlessingDuration ): string => { switch (value.tag) { case "Immediate": - return getTextForImmediateDuration(deps, value.immediate, env) + return getImmediateDurationTranslation( + locale, + responsiveTextSize, + value.immediate + ) case "Fixed": - return getTextForFixedDuration(deps, value.fixed, env) + return getFixedDurationTranslation( + locale, + responsiveTextSize, + value.fixed + ) case "Indefinite": - return getResponsiveText( - deps.translateMap(value.indefinite.translations)?.description, - env.responsiveText + return getIndefiniteDurationTranslation( + locale, + responsiveTextSize, + value.indefinite ) default: return assertExhaustive(value) diff --git a/src/entities/partial/rated/activatable/effect.ts b/src/entities/partial/rated/activatable/effect.ts index b4cc1ad..2dd5836 100644 --- a/src/entities/partial/rated/activatable/effect.ts +++ b/src/entities/partial/rated/activatable/effect.ts @@ -2,26 +2,26 @@ import { filterNonNullable } from "@optolith/helpers/array" import { mapNullable } from "@optolith/helpers/nullable" import { assertExhaustive } from "@optolith/helpers/typeSafety" import { Effect } from "optolith-database-schema/types/_ActivatableSkillEffect" -import { Translate } from "../../../../helpers/translate.js" +import { LocaleEnvironment } from "../../../../helpers/locale.js" import { LibraryEntryContent } from "../../../../libraryEntry.js" const getContentPartsForQualityLevels = ( + getQualityLevelString: (index: number) => string | number, + locale: LocaleEnvironment, source: { text_before: string quality_levels: string[] text_after?: string - }, - getQualityLevelString: (index: number) => string | number, - translate: Translate + } ): LibraryEntryContent[] => filterNonNullable([ { - label: translate("Effect"), + label: locale.translate("Effect"), value: source.text_before, }, ...source.quality_levels.map((text, index) => ({ value: text, - label: translate("QL {0}", getQualityLevelString(index)), + label: locale.translate("QL {0}", getQualityLevelString(index)), })), mapNullable(source.text_after, (textAfter) => ({ value: textAfter, @@ -33,28 +33,28 @@ const getContentPartsForQualityLevels = ( * Gets the text for the effect of an activatable skill. */ export const getTextForEffect = ( - effect: Effect, - translate: Translate + locale: LocaleEnvironment, + effect: Effect ): LibraryEntryContent[] => { switch (effect.tag) { case "Plain": return [ { - label: translate("Effect"), + label: locale.translate("Effect"), value: effect.plain.text, }, ] case "ForEachQualityLevel": return getContentPartsForQualityLevels( - effect.for_each_quality_level, (index) => index + 1, - translate + locale, + effect.for_each_quality_level ) case "ForEachTwoQualityLevels": return getContentPartsForQualityLevels( - effect.for_each_two_quality_levels, (index) => `${index * 2 + 1}–${index * 2 + 2}`, - translate + locale, + effect.for_each_two_quality_levels ) default: return assertExhaustive(effect) diff --git a/src/entities/partial/rated/activatable/index.ts b/src/entities/partial/rated/activatable/index.ts index 61cef5b..8743d46 100644 --- a/src/entities/partial/rated/activatable/index.ts +++ b/src/entities/partial/rated/activatable/index.ts @@ -5,16 +5,19 @@ import { SlowSustainedPerformanceParameters, } from "optolith-database-schema/types/_ActivatableSkill" import { GetById } from "../../../../helpers/getTypes.js" -import { Translate, TranslateMap } from "../../../../helpers/translate.js" +import { LocaleEnvironment } from "../../../../helpers/locale.js" import { ResponsiveTextSize } from "../../responsiveText.js" import { - getTextForFastCastingTime, - getTextForSlowCastingTime, + getFastCastingTimeTranslation, + getSlowCastingTimeTranslation, } from "./castingTime.js" -import { getTextForOneTimeCost, getTextForSustainedCost } from "./cost.js" import { - getTextForDurationForOneTime, - getTextForDurationForSustained, + getOneTimeCostTranslation, + getSustainedCostTranslation, +} from "./cost.js" +import { + getDurationForOneTimeTranslation, + getDurationForSustainedTranslation, } from "./duration.js" import { Entity } from "./entity.js" import { getTextForActivatableSkillRange } from "./range.js" @@ -23,121 +26,179 @@ import { Speed } from "./speed.js" /** * Get the texts for all fast one-time performance parameters. */ -export const getTextForFastOneTimePerformanceParameters = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - translateMap: TranslateMap - }, - value: FastOneTimePerformanceParameters, - env: { - entity: Entity - responsiveText: ResponsiveTextSize - } +export const getFastOneTimePerformanceParametersTranslations = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: FastOneTimePerformanceParameters ): { castingTime: string cost: string range: string duration: string } => ({ - castingTime: getTextForFastCastingTime(deps, value.casting_time, env), - cost: getTextForOneTimeCost(deps, value.cost, { speed: Speed.Fast, ...env }), - range: getTextForActivatableSkillRange(deps, value.range, { - speed: Speed.Fast, - ...env, - }), - duration: getTextForDurationForOneTime(deps, value.duration, env), + castingTime: getFastCastingTimeTranslation( + getSkillModificationLevelById, + locale, + entity, + responsiveTextSize, + value.casting_time + ), + cost: getOneTimeCostTranslation( + getSkillModificationLevelById, + locale, + Speed.Fast, + entity, + responsiveTextSize, + value.cost + ), + range: getTextForActivatableSkillRange( + getSkillModificationLevelById, + locale, + Speed.Fast, + responsiveTextSize, + entity, + value.range + ), + duration: getDurationForOneTimeTranslation( + locale, + responsiveTextSize, + value.duration + ), }) /** * Get the texts for all fast sustained performance parameters. */ -export const getTextForFastSustainedPerformanceParameters = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - translateMap: TranslateMap - }, - value: FastSustainedPerformanceParameters, - env: { - entity: Entity - responsiveText: ResponsiveTextSize - } +export const getFastSustainedPerformanceParametersTranslations = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: FastSustainedPerformanceParameters ): { castingTime: string cost: string range: string duration: string } => ({ - castingTime: getTextForFastCastingTime(deps, value.casting_time, env), - cost: getTextForSustainedCost(deps, value.cost, { - speed: Speed.Fast, - ...env, - }), - range: getTextForActivatableSkillRange(deps, value.range, { - speed: Speed.Fast, - ...env, - }), - duration: getTextForDurationForSustained(deps, value.duration, env), + castingTime: getFastCastingTimeTranslation( + getSkillModificationLevelById, + locale, + entity, + responsiveTextSize, + value.casting_time + ), + cost: getSustainedCostTranslation( + getSkillModificationLevelById, + locale, + Speed.Fast, + entity, + responsiveTextSize, + value.cost + ), + range: getTextForActivatableSkillRange( + getSkillModificationLevelById, + locale, + Speed.Fast, + responsiveTextSize, + entity, + value.range + ), + duration: getDurationForSustainedTranslation( + locale, + responsiveTextSize, + value.duration + ), }) /** * Get the texts for all slow one-time performance parameters. */ -export const getTextForSlowOneTimePerformanceParameters = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - translateMap: TranslateMap - }, - value: SlowOneTimePerformanceParameters, - env: { - entity: Entity - responsiveText: ResponsiveTextSize - } +export const getSlowOneTimePerformanceParametersTranslations = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: SlowOneTimePerformanceParameters ): { castingTime: string cost: string range: string duration: string } => ({ - castingTime: getTextForSlowCastingTime(deps, value.casting_time, env), - cost: getTextForOneTimeCost(deps, value.cost, { speed: Speed.Slow, ...env }), - range: getTextForActivatableSkillRange(deps, value.range, { - speed: Speed.Slow, - ...env, - }), - duration: getTextForDurationForOneTime(deps, value.duration, env), + castingTime: getSlowCastingTimeTranslation( + getSkillModificationLevelById, + locale, + entity, + responsiveTextSize, + value.casting_time + ), + cost: getOneTimeCostTranslation( + getSkillModificationLevelById, + locale, + Speed.Slow, + entity, + responsiveTextSize, + value.cost + ), + range: getTextForActivatableSkillRange( + getSkillModificationLevelById, + locale, + Speed.Slow, + responsiveTextSize, + entity, + value.range + ), + duration: getDurationForOneTimeTranslation( + locale, + responsiveTextSize, + value.duration + ), }) /** * Get the texts for all slow sustained performance parameters. */ -export const getTextForSlowSustainedPerformanceParameters = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - translateMap: TranslateMap - }, - value: SlowSustainedPerformanceParameters, - env: { - entity: Entity - responsiveText: ResponsiveTextSize - } +export const getSlowSustainedPerformanceParametersTranslations = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: SlowSustainedPerformanceParameters ): { castingTime: string cost: string range: string duration: string } => ({ - castingTime: getTextForSlowCastingTime(deps, value.casting_time, env), - cost: getTextForSustainedCost(deps, value.cost, { - speed: Speed.Slow, - ...env, - }), - range: getTextForActivatableSkillRange(deps, value.range, { - speed: Speed.Slow, - ...env, - }), - duration: getTextForDurationForSustained(deps, value.duration, env), + castingTime: getSlowCastingTimeTranslation( + getSkillModificationLevelById, + locale, + entity, + responsiveTextSize, + value.casting_time + ), + cost: getSustainedCostTranslation( + getSkillModificationLevelById, + locale, + Speed.Slow, + entity, + responsiveTextSize, + value.cost + ), + range: getTextForActivatableSkillRange( + getSkillModificationLevelById, + locale, + Speed.Slow, + responsiveTextSize, + entity, + value.range + ), + duration: getDurationForSustainedTranslation( + locale, + responsiveTextSize, + value.duration + ), }) diff --git a/src/entities/partial/rated/activatable/isMaximum.ts b/src/entities/partial/rated/activatable/isMaximum.ts deleted file mode 100644 index 0f5bbb7..0000000 --- a/src/entities/partial/rated/activatable/isMaximum.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Translate } from "../../../../helpers/translate.js" -import { responsive, ResponsiveTextSize } from "../../responsiveText.js" - -/** - * Returns the text to prepend for the `is_maximum` property. - */ -export const getTextForIsMaximum = ( - is_maximum: boolean | undefined, - translate: Translate, - responsiveText: ResponsiveTextSize -): string => { - if (is_maximum !== true) { - return "" - } - - return responsive( - responsiveText, - () => translate("no more than "), - () => translate("max. ") - ) -} diff --git a/src/entities/partial/rated/activatable/isMinimumMaximum.ts b/src/entities/partial/rated/activatable/isMinimumMaximum.ts new file mode 100644 index 0000000..cfa586e --- /dev/null +++ b/src/entities/partial/rated/activatable/isMinimumMaximum.ts @@ -0,0 +1,54 @@ +import { LocaleEnvironment } from "../../../../helpers/locale.js" +import { responsive, ResponsiveTextSize } from "../../responsiveText.js" + +/** + * Wraps the text in a translation that indicates it’s a minimum value. + */ +export const wrapAsMinimum = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + text: string +): string => + responsive( + responsiveTextSize, + () => locale.translate("at least {0}", text), + () => locale.translate("min. {0}", text) + ) + +/** + * Wraps the text in a translation that indicates it’s a minimum value if the + * `is_minimum` property says it’s a minimum value. + */ +export const wrapIfMinimum = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + is_minimum: boolean | undefined, + text: string +) => + is_minimum === true ? wrapAsMinimum(locale, responsiveTextSize, text) : "" + +/** + * Wraps the text in a translation that indicates it’s a maximum value. + */ +export const wrapAsMaximum = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + text: string +): string => + responsive( + responsiveTextSize, + () => locale.translate("no more than {0}", text), + () => locale.translate("max. {0}", text) + ) + +/** + * Wraps the text in a translation that indicates it’s a maximum value if the + * `is_maximum` property says it’s a maximum value. + */ +export const wrapIfMaximum = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + is_maximum: boolean | undefined, + text: string +): string => + is_maximum === true ? wrapAsMaximum(locale, responsiveTextSize, text) : "" diff --git a/src/entities/partial/rated/activatable/modifiableParameter.ts b/src/entities/partial/rated/activatable/modifiableParameter.ts deleted file mode 100644 index 1245a79..0000000 --- a/src/entities/partial/rated/activatable/modifiableParameter.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * A parameter that is designed to be modifiable. - */ -export enum ModifiableParameter { - CastingTime, - Cost, - Range, -} diff --git a/src/entities/partial/rated/activatable/nonModifiable.ts b/src/entities/partial/rated/activatable/nonModifiable.ts deleted file mode 100644 index 4e2bd02..0000000 --- a/src/entities/partial/rated/activatable/nonModifiable.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { assertExhaustive } from "@optolith/helpers/typeSafety" -import { Translate } from "../../../../helpers/translate.js" -import { ResponsiveTextSize } from "../../responsiveText.js" -import { Entity } from "./entity.js" -import { ModifiableParameter } from "./modifiableParameter.js" - -/** - * Returns the suffix for the text of a non-modifiable parameter that indicates - * that the parameter cannot be modified. - */ -export const getTextForNonModifiableSuffix = ( - translate: Translate, - entity: Entity, - param: ModifiableParameter, - responsiveText: ResponsiveTextSize -): string => { - if (responsiveText === ResponsiveTextSize.Compressed) { - switch (entity) { - case Entity.Spell: - case Entity.Ritual: - case Entity.LiturgicalChant: - case Entity.Ceremony: - return translate(" (cannot modify)") - case Entity.Cantrip: - case Entity.Blessing: - return "" - default: - return assertExhaustive(entity) - } - } - - switch (entity) { - case Entity.Spell: - switch (param) { - case ModifiableParameter.CastingTime: - return translate( - " (you cannot use a modification on this spell’s casting time)" - ) - case ModifiableParameter.Cost: - return translate( - " (you cannot use a modification on this spell’s cost)" - ) - case ModifiableParameter.Range: - return translate( - " (you cannot use a modification on this spell’s range)" - ) - default: - return assertExhaustive(param) - } - case Entity.Ritual: - switch (param) { - case ModifiableParameter.CastingTime: - return translate( - " (you cannot use a modification on this ritual’s ritual time)" - ) - case ModifiableParameter.Cost: - return translate( - " (you cannot use a modification on this ritual’s cost)" - ) - case ModifiableParameter.Range: - return translate( - " (you cannot use a modification on this ritual’s range)" - ) - default: - return assertExhaustive(param) - } - case Entity.LiturgicalChant: - switch (param) { - case ModifiableParameter.CastingTime: - return translate( - " (you cannot use a modification on this chant’s liturgical time)" - ) - case ModifiableParameter.Cost: - return translate( - " (you cannot use a modification on this chant’s cost)" - ) - case ModifiableParameter.Range: - return translate( - " (you cannot use a modification on this chant’s range)" - ) - default: - return assertExhaustive(param) - } - case Entity.Ceremony: - switch (param) { - case ModifiableParameter.CastingTime: - return translate( - " (you cannot use a modification on this ceremony’s ceremonial time)" - ) - case ModifiableParameter.Cost: - return translate( - " (you cannot use a modification on this ceremony’s cost)" - ) - case ModifiableParameter.Range: - return translate( - " (you cannot use a modification on this ceremony’s range)" - ) - default: - return assertExhaustive(param) - } - case Entity.Cantrip: - case Entity.Blessing: - return "" - default: - return assertExhaustive(entity) - } -} diff --git a/src/entities/partial/rated/activatable/nonModifiableSuffix.ts b/src/entities/partial/rated/activatable/nonModifiableSuffix.ts new file mode 100644 index 0000000..079aeb9 --- /dev/null +++ b/src/entities/partial/rated/activatable/nonModifiableSuffix.ts @@ -0,0 +1,91 @@ +import { mapNullable } from "@optolith/helpers/nullable" +import { assertExhaustive } from "@optolith/helpers/typeSafety" +import { UI } from "optolith-database-schema/types/UI" +import { LocaleEnvironment } from "../../../../helpers/locale.js" +import { responsive, ResponsiveTextSize } from "../../responsiveText.js" +import { Entity } from "./entity.js" + +/** + * A parameter that is designed to be modifiable. + */ +export enum ModifiableParameter { + CastingTime, + Cost, + Range, +} + +const translationKeyForNonModifiableSuffix: { + [key in Entity]: + | { + [key in ModifiableParameter]: keyof UI + } + | undefined +} = { + [Entity.Spell]: { + [ModifiableParameter.CastingTime]: + " (you cannot use a modification on this spell’s casting time)", + [ModifiableParameter.Cost]: + " (you cannot use a modification on this spell’s cost)", + [ModifiableParameter.Range]: + " (you cannot use a modification on this spell’s range)", + }, + [Entity.Ritual]: { + [ModifiableParameter.CastingTime]: + " (you cannot use a modification on this ritual’s ritual time)", + [ModifiableParameter.Cost]: + " (you cannot use a modification on this ritual’s cost)", + [ModifiableParameter.Range]: + " (you cannot use a modification on this ritual’s range)", + }, + [Entity.LiturgicalChant]: { + [ModifiableParameter.CastingTime]: + " (you cannot use a modification on this chant’s liturgical time)", + [ModifiableParameter.Cost]: + " (you cannot use a modification on this chant’s cost)", + [ModifiableParameter.Range]: + " (you cannot use a modification on this chant’s range)", + }, + [Entity.Ceremony]: { + [ModifiableParameter.CastingTime]: + " (you cannot use a modification on this ceremony’s ceremonial time)", + [ModifiableParameter.Cost]: + " (you cannot use a modification on this ceremony’s cost)", + [ModifiableParameter.Range]: + " (you cannot use a modification on this ceremony’s range)", + }, + [Entity.Cantrip]: undefined, + [Entity.Blessing]: undefined, +} + +/** + * Returns the suffix for the text of a non-modifiable parameter that indicates + * that the parameter cannot be modified. + */ +export const getNonModifiableSuffixTranslation = ( + locale: LocaleEnvironment, + entity: Entity, + param: ModifiableParameter, + responsiveTextSize: ResponsiveTextSize +): string => + responsive( + responsiveTextSize, + () => + mapNullable( + translationKeyForNonModifiableSuffix[entity]?.[param], + (key) => locale.translate(key) + ) ?? "", + () => { + switch (entity) { + case Entity.Spell: + case Entity.Ritual: + case Entity.LiturgicalChant: + case Entity.Ceremony: + return locale.translate(" (cannot modify)") + case Entity.Cantrip: + case Entity.Blessing: + return "" + default: + return assertExhaustive(entity) + } + } + ) diff --git a/src/entities/partial/rated/activatable/parensIf.ts b/src/entities/partial/rated/activatable/parensIf.ts index bc7f958..fef92a1 100644 --- a/src/entities/partial/rated/activatable/parensIf.ts +++ b/src/entities/partial/rated/activatable/parensIf.ts @@ -9,8 +9,10 @@ export const parensIf = (text: string | undefined): string => * Appends a string in parentheses with a leading space if it is not empty or * `undefined`. */ -export const appendInParens = ( - text: string, - append: string | undefined +export const appendInParensIfNotEmpty = ( + textToAppend: string | undefined, + text: string ): string => - append === undefined || append === "" ? text : `${text} (${append})` + textToAppend === undefined || textToAppend === "" + ? text + : `${text} (${textToAppend})` diff --git a/src/entities/partial/rated/activatable/range.ts b/src/entities/partial/rated/activatable/range.ts index b929c45..bf1dd89 100644 --- a/src/entities/partial/rated/activatable/range.ts +++ b/src/entities/partial/rated/activatable/range.ts @@ -1,47 +1,174 @@ +import { mapNullable } from "@optolith/helpers/nullable" import { assertExhaustive } from "@optolith/helpers/typeSafety" import { + CheckResultBasedRange, + FixedRange, + ModifiableRange, Range, - RangeUnit, + RangeValue, } from "optolith-database-schema/types/_ActivatableSkillRange" import { BlessingRange } from "optolith-database-schema/types/Blessing" import { CantripRange } from "optolith-database-schema/types/Cantrip" import { GetById } from "../../../../helpers/getTypes.js" -import { Translate, TranslateMap } from "../../../../helpers/translate.js" +import { LocaleEnvironment } from "../../../../helpers/locale.js" import { - getResponsiveText, - getResponsiveTextOptional, - responsive, + appendNoteIfRequested, + replaceTextIfRequested, ResponsiveTextSize, } from "../../responsiveText.js" +import { formatLength } from "../../units/length.js" import { MISSING_VALUE } from "../../unknown.js" -import { getTextForCheckResultBased } from "./checkResultBased.js" +import { getCheckResultBasedValueTranslation } from "./checkResultBased.js" import { Entity } from "./entity.js" -import { getTextForIsMaximum } from "./isMaximum.js" -import { ModifiableParameter } from "./modifiableParameter.js" -import { getTextForNonModifiableSuffix } from "./nonModifiable.js" -import { Speed } from "./speed.js" +import { wrapIfMaximum } from "./isMinimumMaximum.js" +import { + getNonModifiableSuffixTranslation, + ModifiableParameter, +} from "./nonModifiableSuffix.js" +import { getModifiableBySpeed, Speed } from "./speed.js" -const toRangeUnit = ( - unit: RangeUnit, - value: number | string, - translate: Translate, - responsiveText: ResponsiveTextSize -) => { - switch (unit) { - case "Steps": - return responsive( - responsiveText, - () => translate("{0} yards", value), - () => translate("{0} yd", value) +const getModifiableRangeTranslation = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + responsiveTextSize: ResponsiveTextSize, + value: ModifiableRange +) => + mapNullable( + getSkillModificationLevelById(value.initial_modification_level), + (modificationLevel) => { + const range = getModifiableBySpeed( + (config) => config.range, + (config) => config.range, + speed, + modificationLevel ) - case "Miles": - return responsive( - responsiveText, - () => translate("{0} miles", value), - () => translate("{0} mi.", value) + + if (range === 1) { + return locale.translate("Touch") + } + + return formatLength(locale, responsiveTextSize, "Steps", range) + } + ) ?? MISSING_VALUE + +const getSightTranslation = (locale: LocaleEnvironment) => + locale.translate("Sight") + +const getSelfTranslation = (locale: LocaleEnvironment) => + locale.translate("Self") + +const getGlobalTranslation = (locale: LocaleEnvironment) => + locale.translate("Global") + +const getTouchTranslation = ( + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize +) => + locale.translate("Touch") + + getNonModifiableSuffixTranslation( + locale, + entity, + ModifiableParameter.Range, + responsiveTextSize + ) + +const getFixedRangeTranslation = ( + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: FixedRange +) => + formatLength(locale, responsiveTextSize, value.unit, value.value) + + getNonModifiableSuffixTranslation( + locale, + entity, + ModifiableParameter.Range, + responsiveTextSize + ) + +const wrapIfRadius = ( + locale: LocaleEnvironment, + is_radius: boolean | undefined, + text: string +) => (is_radius === true ? `${text} ${locale.translate("Radius")}` : text) + +const getCheckResultBasedRangeTranslation = ( + locale: LocaleEnvironment, + entity: Entity, + responsiveTextSize: ResponsiveTextSize, + value: CheckResultBasedRange +) => { + const range = formatLength( + locale, + responsiveTextSize, + value.unit, + getCheckResultBasedValueTranslation(locale.translate, value) + ) + + const rangeWrappedIfRadius = wrapIfRadius(locale, value.is_radius, range) + + const rangeWrappedIfRadiusAndIfMaximum = wrapIfMaximum( + locale, + responsiveTextSize, + value.is_maximum, + rangeWrappedIfRadius + ) + + return ( + rangeWrappedIfRadiusAndIfMaximum + + getNonModifiableSuffixTranslation( + locale, + entity, + ModifiableParameter.Range, + responsiveTextSize + ) + ) +} + +const getRangeValueTranslation = ( + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + responsiveTextSize: ResponsiveTextSize, + entity: Entity, + value: RangeValue +) => { + switch (value.tag) { + case "Modifiable": + return getModifiableRangeTranslation( + getSkillModificationLevelById, + locale, + speed, + responsiveTextSize, + value.modifiable + ) + case "Sight": + return getSightTranslation(locale) + case "Self": + return getSelfTranslation(locale) + case "Global": + return getGlobalTranslation(locale) + case "Touch": + return getTouchTranslation(locale, entity, responsiveTextSize) + case "Fixed": { + return getFixedRangeTranslation( + locale, + entity, + responsiveTextSize, + value.fixed + ) + } + case "CheckResultBased": + return getCheckResultBasedRangeTranslation( + locale, + entity, + responsiveTextSize, + value.check_result_based ) default: - return assertExhaustive(unit) + return assertExhaustive(value) } } @@ -49,194 +176,88 @@ const toRangeUnit = ( * Returns the text for the range of an activatable skill. */ export const getTextForActivatableSkillRange = ( - deps: { - getSkillModificationLevelById: GetById.Static.SkillModificationLevel - translate: Translate - translateMap: TranslateMap - }, - value: Range, - env: { - speed: Speed - responsiveText: ResponsiveTextSize - entity: Entity - } + getSkillModificationLevelById: GetById.Static.SkillModificationLevel, + locale: LocaleEnvironment, + speed: Speed, + responsiveTextSize: ResponsiveTextSize, + entity: Entity, + value: Range ): string => { - const translation = deps.translateMap(value.translations) + const rangeValue = getRangeValueTranslation( + getSkillModificationLevelById, + locale, + speed, + responsiveTextSize, + entity, + value.value + ) - if (value.translations !== undefined && translation === undefined) { - return MISSING_VALUE + const withReplacement = replaceTextIfRequested( + "replacement", + value.translations, + locale.translateMap, + responsiveTextSize, + rangeValue + ) + + return appendNoteIfRequested( + "note", + value.translations, + locale.translateMap, + responsiveTextSize, + withReplacement + ) +} + +const getTextForTinyAcitvatableRange = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + entity: Entity, + value: CantripRange | BlessingRange +): string => { + switch (value.tag) { + case "Self": + return getSelfTranslation(locale) + case "Touch": + return getTouchTranslation(locale, entity, responsiveTextSize) + case "Fixed": + return getFixedRangeTranslation( + locale, + entity, + responsiveTextSize, + value.fixed + ) + default: + return assertExhaustive(value) } - - const rangeValue = (() => { - switch (value.value.tag) { - case "Modifiable": { - const modificationLevel = deps.getSkillModificationLevelById( - value.value.modifiable.initial_modification_level - ) - - if (modificationLevel === undefined) { - return MISSING_VALUE - } - - const range = (() => { - switch (env.speed) { - case Speed.Fast: - return modificationLevel.fast.range - case Speed.Slow: - return modificationLevel.slow.range - default: - return assertExhaustive(env.speed) - } - })() - - if (range === 1) { - return deps.translate("Touch") - } - - return toRangeUnit("Steps", range, deps.translate, env.responsiveText) - } - case "Sight": - return deps.translate("Sight") - case "Self": - return deps.translate("Self") - case "Global": - return deps.translate("Global") - case "Touch": - return ( - deps.translate("Touch") + - getTextForNonModifiableSuffix( - deps.translate, - env.entity, - ModifiableParameter.Range, - env.responsiveText - ) - ) - case "Fixed": { - return ( - toRangeUnit( - value.value.fixed.unit, - value.value.fixed.value, - deps.translate, - env.responsiveText - ) + - getTextForNonModifiableSuffix( - deps.translate, - env.entity, - ModifiableParameter.Range, - env.responsiveText - ) - ) - } - case "CheckResultBased": { - const isMaximum = getTextForIsMaximum( - value.value.check_result_based.is_maximum, - deps.translate, - env.responsiveText - ) - - const isRadius = - value.value.check_result_based.is_radius === true - ? ` ${deps.translate("Radius")}` - : "" - - return ( - isMaximum + - toRangeUnit( - value.value.check_result_based.unit, - getTextForCheckResultBased( - value.value.check_result_based, - deps.translate - ), - deps.translate, - env.responsiveText - ) + - isRadius + - getTextForNonModifiableSuffix( - deps.translate, - env.entity, - ModifiableParameter.Range, - env.responsiveText - ) - ) - } - default: - return assertExhaustive(value.value) - } - })() - - const withReplacement = - translation?.replacement !== undefined - ? getResponsiveText(translation.replacement, env.responsiveText).replace( - "$1", - rangeValue - ) - : rangeValue - - const withNote = (() => { - if (translation?.note === undefined) { - return withReplacement - } - - const note = getResponsiveTextOptional(translation.note, env.responsiveText) - - if (note === undefined) { - return withReplacement - } - - return `${withReplacement} (${note})` - })() - - return withNote } /** * Returns the text for the range of a cantrip. */ export const getTextForCantripRange = ( - deps: { translate: Translate }, - value: CantripRange, - env: { responsiveText: ResponsiveTextSize } -): string => { - switch (value.tag) { - case "Self": - return deps.translate("Self") - case "Touch": - return deps.translate("Touch") - case "Fixed": { - return toRangeUnit( - value.fixed.unit, - value.fixed.value, - deps.translate, - env.responsiveText - ) - } - default: - return assertExhaustive(value) - } -} + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: CantripRange +): string => + getTextForTinyAcitvatableRange( + locale, + responsiveTextSize, + Entity.Cantrip, + value + ) /** * Returns the text for the range of a blessing. */ export const getTextForBlessingRange = ( - deps: { translate: Translate }, - value: BlessingRange, - env: { responsiveText: ResponsiveTextSize } -): string => { - switch (value.tag) { - case "Self": - return deps.translate("Self") - case "Touch": - return deps.translate("Touch") - case "Fixed": { - return toRangeUnit( - value.fixed.unit, - value.fixed.value, - deps.translate, - env.responsiveText - ) - } - default: - return assertExhaustive(value) - } -} + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + value: BlessingRange +): string => + getTextForTinyAcitvatableRange( + locale, + responsiveTextSize, + Entity.Blessing, + value + ) diff --git a/src/entities/partial/rated/activatable/speed.ts b/src/entities/partial/rated/activatable/speed.ts index 2b32e48..3cb75e2 100644 --- a/src/entities/partial/rated/activatable/speed.ts +++ b/src/entities/partial/rated/activatable/speed.ts @@ -17,10 +17,10 @@ export enum Speed { * Returns a common value for a skill modification level depending on the speed. */ export const getModifiableBySpeed = ( - level: SkillModificationLevel, - speed: Speed, fast: (config: FastSkillModificationLevelConfig) => T, - slow: (config: SlowSkillModificationLevelConfig) => T + slow: (config: SlowSkillModificationLevelConfig) => T, + speed: Speed, + level: SkillModificationLevel ): T => { switch (speed) { case Speed.Fast: diff --git a/src/entities/partial/rated/activatable/targetCategory.ts b/src/entities/partial/rated/activatable/targetCategory.ts index bd9f37c..4288023 100644 --- a/src/entities/partial/rated/activatable/targetCategory.ts +++ b/src/entities/partial/rated/activatable/targetCategory.ts @@ -1,59 +1,92 @@ import { mapNullable } from "@optolith/helpers/nullable" import { assertExhaustive } from "@optolith/helpers/typeSafety" -import { TargetCategory } from "optolith-database-schema/types/_ActivatableSkillTargetCategory" +import { + TargetCategory, + TargetCategoryIdentifier, +} from "optolith-database-schema/types/_ActivatableSkillTargetCategory" +import { TargetCategoryReference } from "optolith-database-schema/types/_SimpleReferences" import { GetById } from "../../../../helpers/getTypes.js" -import { Translate, TranslateMap } from "../../../../helpers/translate.js" +import { LocaleEnvironment } from "../../../../helpers/locale.js" import { LibraryEntryContent } from "../../../../libraryEntry.js" import { MISSING_VALUE } from "../../unknown.js" -import { appendInParens } from "./parensIf.js" +import { appendInParensIfNotEmpty } from "./parensIf.js" + +const getSelfTranslation = (locale: LocaleEnvironment) => + locale.translate("Self") + +const getZoneTranslation = (locale: LocaleEnvironment) => + locale.translate("Zone") + +const getLiturgicalChantsAndCeremoniesTranslation = ( + locale: LocaleEnvironment +) => locale.translate("Liturgical Chants and Ceremonies") + +const getCantripsTranslation = (locale: LocaleEnvironment) => + locale.translate("Cantrips") + +const getPredefinedTranslation = ( + getTargetCategoryById: GetById.Static.TargetCategory, + locale: LocaleEnvironment, + value: TargetCategoryReference +) => { + const numericId = value.id.target_category + const specificTargetCategory = getTargetCategoryById(numericId) + + return ( + mapNullable( + locale.translateMap(specificTargetCategory?.translations), + (translation) => translation.name + ) ?? MISSING_VALUE + ) +} + +const getTargetCategoryTranslationByType = ( + getTargetCategoryById: GetById.Static.TargetCategory, + locale: LocaleEnvironment, + id: TargetCategoryIdentifier +) => { + switch (id.tag) { + case "Self": + return getSelfTranslation(locale) + case "Zone": + return getZoneTranslation(locale) + case "LiturgicalChantsAndCeremonies": + return getLiturgicalChantsAndCeremoniesTranslation(locale) + case "Cantrips": + return getCantripsTranslation(locale) + case "Predefined": + return getPredefinedTranslation( + getTargetCategoryById, + locale, + id.predefined + ) + default: + return assertExhaustive(id) + } +} /** * Get the text for the target category. */ -export const getTextForTargetCategory = ( - deps: { - translate: Translate - translateMap: TranslateMap - getTargetCategoryById: GetById.Static.TargetCategory - }, +export const getTargetCategoryTranslation = ( + getTargetCategoryById: GetById.Static.TargetCategory, + locale: LocaleEnvironment, values: TargetCategory ): LibraryEntryContent => ({ - label: deps.translate("Target Category"), + label: locale.translate("Target Category"), value: values.length === 0 - ? deps.translate("all") + ? locale.translate("all") : values - .map(({ id, translations }) => { - const mainName = (() => { - switch (id.tag) { - case "Self": - return deps.translate("Self") - case "Zone": - return deps.translate("Zone") - case "LiturgicalChantsAndCeremonies": - return deps.translate("Liturgical Chants and Ceremonies") - case "Cantrips": - return deps.translate("Cantrips") - case "Predefined": { - const numericId = id.predefined.id.target_category - const specificTargetCategory = - deps.getTargetCategoryById(numericId) - return ( - mapNullable( - deps.translateMap(specificTargetCategory?.translations), - (translation) => translation.name - ) ?? MISSING_VALUE - ) - } - default: - return assertExhaustive(id) - } - })() - - return appendInParens( - mainName, - deps.translateMap(translations)?.note + .map(({ id, translations }) => + appendInParensIfNotEmpty( + locale.translateMap(translations)?.note, + getTargetCategoryTranslationByType( + getTargetCategoryById, + locale, + id + ) ) - }) + ) .join(", "), }) diff --git a/src/entities/partial/rated/activatable/units.ts b/src/entities/partial/rated/activatable/units.ts deleted file mode 100644 index 68899c3..0000000 --- a/src/entities/partial/rated/activatable/units.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { assertExhaustive } from "@optolith/helpers/typeSafety" -import { Translate } from "../../../../helpers/translate.js" -import { responsive, ResponsiveTextSize } from "../../responsiveText.js" -import { Entity } from "./entity.js" - -type TimeSpanUnit = - | "Seconds" - | "Minutes" - | "Hours" - | "Days" - | "Weeks" - | "Months" - | "Years" - | "Centuries" - | "Actions" - | "CombatRounds" - | "SeductionActions" - | "Rounds" - -/** - * Returns the text for a time span unit. - */ -export const formatTimeSpan = ( - translate: Translate, - responsiveTextSize: ResponsiveTextSize, - unit: TimeSpanUnit, - value: number | string -): string => { - switch (unit) { - case "Seconds": - return responsive( - responsiveTextSize, - () => translate("{0} seconds", value), - () => translate("{0} s", value) - ) - case "Minutes": - return responsive( - responsiveTextSize, - () => translate("{0} minutes", value), - () => translate("{0} min", value) - ) - case "Hours": - return responsive( - responsiveTextSize, - () => translate("{0} hours", value), - () => translate("{0} h", value) - ) - case "Days": - return responsive( - responsiveTextSize, - () => translate("{0} days", value), - () => translate("{0} d", value) - ) - case "Weeks": - return responsive( - responsiveTextSize, - () => translate("{0} weeks", value), - () => translate("{0} wks.", value) - ) - case "Months": - return responsive( - responsiveTextSize, - () => translate("{0} months", value), - () => translate("{0} mos.", value) - ) - case "Years": - return responsive( - responsiveTextSize, - () => translate("{0} years", value), - () => translate("{0} yrs.", value) - ) - case "Centuries": - return responsive( - responsiveTextSize, - () => translate("{0} centuries", value), - () => translate("{0} cent.", value) - ) - case "Actions": - return responsive( - responsiveTextSize, - () => translate("{0} actions", value), - () => translate("{0} act", value) - ) - case "CombatRounds": - return responsive( - responsiveTextSize, - () => translate("{0} combat rounds", value), - () => translate("{0} CR", value) - ) - case "SeductionActions": - return responsive( - responsiveTextSize, - () => translate("{0} seduction actions", value), - () => translate("{0} SA", value) - ) - case "Rounds": - return responsive( - responsiveTextSize, - () => translate("{0} rounds", value), - () => translate("{0} rnds", value) - ) - default: - return assertExhaustive(unit) - } -} - -/** - * Returns the text for a cost unit that is based on the entity type. - */ -export const formatCost = ( - translate: Translate, - entity: Entity, - value: number | string -) => { - switch (entity) { - case Entity.Cantrip: - case Entity.Spell: - case Entity.Ritual: - return translate("{0} AE", value) - case Entity.Blessing: - case Entity.LiturgicalChant: - case Entity.Ceremony: - return translate("{0} KP", value) - default: - return assertExhaustive(entity) - } -} diff --git a/src/entities/partial/responsiveText.ts b/src/entities/partial/responsiveText.ts index 4486295..a8154cd 100644 --- a/src/entities/partial/responsiveText.ts +++ b/src/entities/partial/responsiveText.ts @@ -7,6 +7,7 @@ import { ResponsiveTextReplace, } from "optolith-database-schema/types/_ResponsiveText" import { TranslateMap } from "../../helpers/translate.js" +import { appendInParensIfNotEmpty } from "./rated/activatable/parensIf.js" import { MISSING_VALUE } from "./unknown.js" /** @@ -76,14 +77,33 @@ export const getResponsiveTextOptional = ( /** * Replaces a text with a given value if a replacement is requested, otherwise - * just return the . + * just return the given value. */ -export const replaceTextIfRequested = ( - translation: LocaleMap<{ replacement?: ResponsiveTextReplace }> | undefined, - valueToReplace: string, +export const replaceTextIfRequested = ( + key: Key, + translation: LocaleMap<{ [K in Key]?: ResponsiveTextReplace }> | undefined, translateMap: TranslateMap, - responsiveText: ResponsiveTextSize + responsiveText: ResponsiveTextSize, + valueToReplace: string ) => - mapNullable(translateMap(translation)?.replacement, (replacement) => + mapNullable(translateMap(translation)?.[key], (replacement) => getResponsiveText(replacement, responsiveText).replace("$1", valueToReplace) ) ?? valueToReplace + +/** + * Appends a note to a given value if a note is requested, otherwise just return + * the given value. + */ +export const appendNoteIfRequested = ( + key: Key, + translation: LocaleMap<{ [K in Key]?: ResponsiveTextOptional }> | undefined, + translateMap: TranslateMap, + responsiveText: ResponsiveTextSize, + valueToAppendTo: string +) => + mapNullable(translateMap(translation)?.[key], (note) => + appendInParensIfNotEmpty( + getResponsiveTextOptional(note, responsiveText), + valueToAppendTo + ) + ) ?? valueToAppendTo diff --git a/src/entities/partial/units/energy.ts b/src/entities/partial/units/energy.ts new file mode 100644 index 0000000..478823f --- /dev/null +++ b/src/entities/partial/units/energy.ts @@ -0,0 +1,48 @@ +import { assertExhaustive } from "@optolith/helpers/typeSafety" +import { UI } from "optolith-database-schema/types/UI" +import { LocaleEnvironment } from "../../../helpers/locale.js" +import { Entity } from "../rated/activatable/entity.js" + +type EnergyUnit = "ArcaneEnergy" | "KarmaPoints" + +const lengthUnitTranslationKeys: { + [key in EnergyUnit]: keyof UI +} = { + ArcaneEnergy: "{0} AE", + KarmaPoints: "{0} KP", +} + +/** + * Returns the text for an energy unit. + */ +export const formatEnergy = ( + locale: LocaleEnvironment, + unit: EnergyUnit, + value: number | string +): string => { + const key = lengthUnitTranslationKeys[unit] + + return locale.translate(key, value) +} + +/** + * Returns the text for an energy unit that is based on the entity type. + */ +export const formatEnergyByEntity = ( + locale: LocaleEnvironment, + entity: Entity, + value: number | string +) => { + switch (entity) { + case Entity.Cantrip: + case Entity.Spell: + case Entity.Ritual: + return formatEnergy(locale, "ArcaneEnergy", value) + case Entity.Blessing: + case Entity.LiturgicalChant: + case Entity.Ceremony: + return formatEnergy(locale, "KarmaPoints", value) + default: + return assertExhaustive(entity) + } +} diff --git a/src/entities/partial/units/length.ts b/src/entities/partial/units/length.ts new file mode 100644 index 0000000..4d14936 --- /dev/null +++ b/src/entities/partial/units/length.ts @@ -0,0 +1,30 @@ +import { UI } from "optolith-database-schema/types/UI" +import { LocaleEnvironment } from "../../../helpers/locale.js" +import { responsive, ResponsiveTextSize } from "../responsiveText.js" + +type LengthUnit = "Steps" | "Miles" + +const lengthUnitTranslationKeys: { + [key in LengthUnit]: [full: keyof UI, compressed: keyof UI] +} = { + Steps: ["{0} yards", "{0} yd"], + Miles: ["{0} miles", "{0} mi."], +} + +/** + * Returns the text for a length unit. + */ +export const formatLength = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + unit: LengthUnit, + value: number | string +) => { + const [fullKey, compressedKey] = lengthUnitTranslationKeys[unit] + + return responsive( + responsiveTextSize, + () => locale.translate(fullKey, value), + () => locale.translate(compressedKey, value) + ) +} diff --git a/src/entities/partial/units/timeSpan.ts b/src/entities/partial/units/timeSpan.ts new file mode 100644 index 0000000..ae9d796 --- /dev/null +++ b/src/entities/partial/units/timeSpan.ts @@ -0,0 +1,52 @@ +import { UI } from "optolith-database-schema/types/UI" +import { LocaleEnvironment } from "../../../helpers/locale.js" +import { ResponsiveTextSize, responsive } from "../responsiveText.js" + +type TimeSpanUnit = + | "Seconds" + | "Minutes" + | "Hours" + | "Days" + | "Weeks" + | "Months" + | "Years" + | "Centuries" + | "Actions" + | "CombatRounds" + | "SeductionActions" + | "Rounds" + +const timeSpanUnitTranslationKeys: { + [key in TimeSpanUnit]: [full: keyof UI, compressed: keyof UI] +} = { + Seconds: ["{0} seconds", "{0} s"], + Minutes: ["{0} minutes", "{0} min"], + Hours: ["{0} hours", "{0} h"], + Days: ["{0} days", "{0} d"], + Weeks: ["{0} weeks", "{0} wks."], + Months: ["{0} months", "{0} mos."], + Years: ["{0} years", "{0} yrs."], + Centuries: ["{0} centuries", "{0} cent."], + Actions: ["{0} actions", "{0} act"], + CombatRounds: ["{0} combat rounds", "{0} CR"], + SeductionActions: ["{0} seduction actions", "{0} SA"], + Rounds: ["{0} rounds", "{0} rnds"], +} + +/** + * Returns the text for a time span unit. + */ +export const formatTimeSpan = ( + locale: LocaleEnvironment, + responsiveTextSize: ResponsiveTextSize, + unit: TimeSpanUnit, + value: number | string +): string => { + const [fullKey, compressedKey] = timeSpanUnitTranslationKeys[unit] + + return responsive( + responsiveTextSize, + () => locale.translate(fullKey, value), + () => locale.translate(compressedKey, value) + ) +} diff --git a/src/entities/skill.ts b/src/entities/skill.ts index 25c1bda..56fb937 100644 --- a/src/entities/skill.ts +++ b/src/entities/skill.ts @@ -21,7 +21,7 @@ export const getSkillLibraryEntry = createLibraryEntryCreator< } >( (entry, { getAttributeById, blessedTraditions, diseases, regions, cache }) => - ({ translate, translateMap, localeCompare }) => { + ({ translate, translateMap, compare: localeCompare }) => { const translation = translateMap(entry.translations) if (translation === undefined) { diff --git a/src/entities/spell.ts b/src/entities/spell.ts index f641cf3..b892eb8 100644 --- a/src/entities/spell.ts +++ b/src/entities/spell.ts @@ -16,18 +16,18 @@ import { createLibraryEntryCreator, LibraryEntryContent, } from "../libraryEntry.js" -import { getTextForCantripDuration } from "./partial/rated/activatable/duration.js" +import { getDurationTranslationForCantrip } from "./partial/rated/activatable/duration.js" import { getTextForEffect } from "./partial/rated/activatable/effect.js" import { Entity } from "./partial/rated/activatable/entity.js" import { - getTextForFastOneTimePerformanceParameters, - getTextForFastSustainedPerformanceParameters, - getTextForSlowOneTimePerformanceParameters, - getTextForSlowSustainedPerformanceParameters, + getFastOneTimePerformanceParametersTranslations, + getFastSustainedPerformanceParametersTranslations, + getSlowOneTimePerformanceParametersTranslations, + getSlowSustainedPerformanceParametersTranslations, } from "./partial/rated/activatable/index.js" import { parensIf } from "./partial/rated/activatable/parensIf.js" import { getTextForCantripRange } from "./partial/rated/activatable/range.js" -import { getTextForTargetCategory } from "./partial/rated/activatable/targetCategory.js" +import { getTargetCategoryTranslation } from "./partial/rated/activatable/targetCategory.js" import { createImprovementCost } from "./partial/rated/improvementCost.js" import { getTextForCheck } from "./partial/rated/skillCheck.js" import { ResponsiveTextSize } from "./partial/responsiveText.js" @@ -124,7 +124,8 @@ export const getCantripLibraryEntry = createLibraryEntryCreator< getCurriculumById, } ) => - ({ translate, translateMap, localeCompare }) => { + (locale) => { + const { translate, translateMap, compare: localeCompare } = locale const translation = translateMap(entry.translations) if (translation === undefined) { @@ -132,19 +133,15 @@ export const getCantripLibraryEntry = createLibraryEntryCreator< } const range = getTextForCantripRange( - { translate }, - entry.parameters.range, - { - responsiveText: ResponsiveTextSize.Full, - } + locale, + ResponsiveTextSize.Full, + entry.parameters.range ) - const duration = getTextForCantripDuration( - { translate, translateMap }, - entry.parameters.duration, - { - responsiveText: ResponsiveTextSize.Full, - } + const duration = getDurationTranslationForCantrip( + locale, + ResponsiveTextSize.Full, + entry.parameters.duration ) return { @@ -169,8 +166,9 @@ export const getCantripLibraryEntry = createLibraryEntryCreator< ? `***${duration}*** (${translation.duration})` : duration, }, - getTextForTargetCategory( - { translate, translateMap, getTargetCategoryById }, + getTargetCategoryTranslation( + getTargetCategoryById, + locale, entry.target ), getTextForProperty( @@ -266,7 +264,8 @@ export const getSpellLibraryEntry = createLibraryEntryCreator< getMagicalTraditionById, } ) => - ({ translate, translateMap, localeCompare }) => { + (locale) => { + const { translate, translateMap, compare: localeCompare } = locale const translation = translateMap(entry.translations) if (translation === undefined) { @@ -276,31 +275,21 @@ export const getSpellLibraryEntry = createLibraryEntryCreator< const { castingTime, cost, range, duration } = (() => { switch (entry.parameters.tag) { case "OneTime": - return getTextForFastOneTimePerformanceParameters( - { - getSkillModificationLevelById, - translate, - translateMap, - }, - entry.parameters.one_time, - { - entity: Entity.Spell, - responsiveText: ResponsiveTextSize.Full, - } + return getFastOneTimePerformanceParametersTranslations( + getSkillModificationLevelById, + locale, + Entity.Spell, + ResponsiveTextSize.Full, + entry.parameters.one_time ) case "Sustained": - return getTextForFastSustainedPerformanceParameters( - { - getSkillModificationLevelById, - translate, - translateMap, - }, - entry.parameters.sustained, - { - entity: Entity.Spell, - responsiveText: ResponsiveTextSize.Full, - } + return getFastSustainedPerformanceParametersTranslations( + getSkillModificationLevelById, + locale, + Entity.Spell, + ResponsiveTextSize.Full, + entry.parameters.sustained ) default: @@ -322,7 +311,7 @@ export const getSpellLibraryEntry = createLibraryEntryCreator< getToughness, } ), - ...getTextForEffect(translation.effect, translate), + ...getTextForEffect(locale, translation.effect), { label: translate("Casting Time"), value: @@ -351,8 +340,9 @@ export const getSpellLibraryEntry = createLibraryEntryCreator< ? `***${duration}*** (${translation.duration.full})` : duration, }, - getTextForTargetCategory( - { translate, translateMap, getTargetCategoryById }, + getTargetCategoryTranslation( + getTargetCategoryById, + locale, entry.target ), getTextForProperty( @@ -397,7 +387,8 @@ export const getRitualLibraryEntry = createLibraryEntryCreator< getMagicalTraditionById, } ) => - ({ translate, translateMap, localeCompare }) => { + (locale) => { + const { translate, translateMap, compare: localeCompare } = locale const translation = translateMap(entry.translations) if (translation === undefined) { @@ -407,31 +398,21 @@ export const getRitualLibraryEntry = createLibraryEntryCreator< const { castingTime, cost, range, duration } = (() => { switch (entry.parameters.tag) { case "OneTime": - return getTextForSlowOneTimePerformanceParameters( - { - getSkillModificationLevelById, - translate, - translateMap, - }, - entry.parameters.one_time, - { - entity: Entity.Ritual, - responsiveText: ResponsiveTextSize.Full, - } + return getSlowOneTimePerformanceParametersTranslations( + getSkillModificationLevelById, + locale, + Entity.Ritual, + ResponsiveTextSize.Full, + entry.parameters.one_time ) case "Sustained": - return getTextForSlowSustainedPerformanceParameters( - { - getSkillModificationLevelById, - translate, - translateMap, - }, - entry.parameters.sustained, - { - entity: Entity.Ritual, - responsiveText: ResponsiveTextSize.Full, - } + return getSlowSustainedPerformanceParametersTranslations( + getSkillModificationLevelById, + locale, + Entity.Ritual, + ResponsiveTextSize.Full, + entry.parameters.sustained ) default: @@ -453,7 +434,7 @@ export const getRitualLibraryEntry = createLibraryEntryCreator< getToughness, } ), - ...getTextForEffect(translation.effect, translate), + ...getTextForEffect(locale, translation.effect), { label: translate("Ritual Time"), value: @@ -482,8 +463,9 @@ export const getRitualLibraryEntry = createLibraryEntryCreator< ? `***${duration}*** (${translation.duration.full})` : duration, }, - getTextForTargetCategory( - { translate, translateMap, getTargetCategoryById }, + getTargetCategoryTranslation( + getTargetCategoryById, + locale, entry.target ), getTextForProperty( diff --git a/src/helpers/locale.ts b/src/helpers/locale.ts new file mode 100644 index 0000000..2740ced --- /dev/null +++ b/src/helpers/locale.ts @@ -0,0 +1,9 @@ +import { Compare } from "@optolith/helpers/compare" +import { Translate, TranslateMap } from "./translate.js" + +export type LocaleEnvironment = { + id: string + translate: Translate + translateMap: TranslateMap + compare: Compare +} diff --git a/src/libraryEntry.ts b/src/libraryEntry.ts index 6234d19..39dd91e 100644 --- a/src/libraryEntry.ts +++ b/src/libraryEntry.ts @@ -1,7 +1,6 @@ import { filterNonNullable } from "@optolith/helpers/array" -import { Compare } from "@optolith/helpers/compare" import { PublicationRefs } from "optolith-database-schema/types/source/_PublicationRef" -import { Translate, TranslateMap } from "./helpers/translate.js" +import { LocaleEnvironment } from "./helpers/locale.js" /** * Creates a function that creates the JSON representation of the rules text for @@ -40,11 +39,9 @@ export type LibraryEntryCreator = ( * A function that is already configures for a specific entity and returns the * JSON representation of the rules text for a ibrary entry. */ -export type LibraryEntryConfiguredCreator = (params: { - translate: Translate - translateMap: TranslateMap - localeCompare: Compare -}) => R | undefined +export type LibraryEntryConfiguredCreator = ( + locale: LocaleEnvironment +) => R | undefined /** * A JSON representation of the rules text for a library entry. diff --git a/src/tsconfig.json b/src/tsconfig.json index f3c1dc7..cd1303a 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -14,6 +14,7 @@ "noImplicitThis": true, "noPropertyAccessFromIndexSignature": true, "noUncheckedIndexedAccess": true, + "noUncheckedSideEffectImports": true, "noUnusedLocals": true, "noUnusedParameters": true, "outDir": "../lib", diff --git a/test/entities/partial/rated/activatable/checkResultBased.ts b/test/entities/partial/rated/activatable/checkResultBased.ts new file mode 100644 index 0000000..b22bab7 --- /dev/null +++ b/test/entities/partial/rated/activatable/checkResultBased.ts @@ -0,0 +1,35 @@ +import assert from "node:assert/strict" +import { describe, it } from "node:test" +import { getCheckResultBasedValueTranslation } from "../../../../../src/entities/partial/rated/activatable/checkResultBased.js" +import { translateMock } from "../../../../../src/helpers/translate.js" + +describe("getTextForCheckResultBased", () => { + it("should return the value text for a check-result-based parameter of an activatable skill", () => { + assert.equal( + getCheckResultBasedValueTranslation(translateMock, { + base: "QualityLevels", + }), + "QL" + ) + assert.equal( + getCheckResultBasedValueTranslation(translateMock, { + base: "SkillPoints", + }), + "SP" + ) + assert.equal( + getCheckResultBasedValueTranslation(translateMock, { + base: "QualityLevels", + modifier: { arithmetic: "Divide", value: 2 }, + }), + "QL / 2" + ) + assert.equal( + getCheckResultBasedValueTranslation(translateMock, { + base: "SkillPoints", + modifier: { arithmetic: "Multiply", value: 3 }, + }), + "SP × 3" + ) + }) +}) diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..a9ce78e --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "composite": true, + "declaration": true, + "forceConsistentCasingInFileNames": true, + "lib": ["esnext"], + "module": "NodeNext", + "noEmit": true, + "noEmitHelpers": true, + "noErrorTruncation": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUncheckedSideEffectImports": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "strict": true, + "target": "ESNext", + "useUnknownInCatchVariables": true, + }, + "references": [ + { "path": "../src" }, + ] +} diff --git a/tsconfig.json b/tsconfig.json index 91b2b35..6ab5769 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,5 +2,6 @@ "files": [], "references": [ { "path": "src" }, + { "path": "test" }, ], }