feat: profession description generator

This commit is contained in:
Lukas Obermann
2026-02-25 13:31:36 +01:00
parent d1fa45ef4c
commit cf9be74548
40 changed files with 2751 additions and 1420 deletions
+1
View File
@@ -2,3 +2,4 @@ arrowParens: avoid
semi: false
tabWidth: 2
trailingComma: all
printWidth: 100
+2
View File
@@ -73,6 +73,8 @@ const collator = new Intl.Collator(localeId, { usage: "sort" })
const localeEnv: LocaleEnvironment = {
id: localeId,
format: (text, args) =>
new MessageFormat(localeId, text, { bidiIsolation: "none" }).format(args),
compare: collator.compare.bind(collator),
translate: (key, ...rest) =>
new MessageFormat(localeId, localeInstance.translations?.[key] ?? key, {
+35 -35
View File
@@ -1,44 +1,44 @@
import { createEntityDescriptionCreator } from "../creator.js"
import type { GetInstanceById } from "../helpers/getTypes.js"
import { printInfluencePrerequisites } from "./partial/prerequisites/index.js"
/**
* Get a JSON representation of the rules text for an influence.
*/
export const getInfluenceEntityDescription =
createEntityDescriptionCreator<"Influence">(
(_, locale, { content: entry }) => {
const { translate, translateMap } = locale
const translation = translateMap(entry.translations)
export const getInfluenceEntityDescription = createEntityDescriptionCreator<
"Influence",
{
getInstanceById: GetInstanceById<"Publication" | "Influence">
}
>(({ getInstanceById }, locale, { content: entry }) => {
const { translate, translateMap } = locale
const translation = translateMap(entry.translations)
if (translation === undefined) {
return undefined
}
if (translation === undefined) {
return undefined
}
return {
title: translation.name,
className: "influence",
body: [
{
type: "definitionList",
items: [
...(translation.effects?.map(effect => ({
label: effect.label,
value: effect.text,
})) ?? []),
entry.prerequisites === undefined
? undefined
: {
label: translate("Prerequisites"),
value: printInfluencePrerequisites(
locale,
entry.prerequisites,
),
},
],
},
return {
title: translation.name,
className: "influence",
body: [
{
type: "definitionList",
items: [
...(translation.effects?.map(effect => ({
label: effect.label,
value: effect.text,
})) ?? []),
entry.prerequisites === undefined
? undefined
: {
label: translate("Prerequisites"),
value: printInfluencePrerequisites(getInstanceById, locale, entry.prerequisites),
},
],
errata: translation.errata,
references: entry.src,
}
},
)
},
],
errata: translation.errata,
references: entry.src,
}
})
+447 -401
View File
@@ -1,34 +1,50 @@
import { allSame } from "@elyukai/utils/array/filters"
import { ensureNonEmpty, isNotEmpty } from "@elyukai/utils/array/nonEmpty"
import { deepEqual } from "@elyukai/utils/equality"
import { identity, on } from "@elyukai/utils/function"
import { isNotNullish } from "@elyukai/utils/nullable"
import { mapObject } from "@optolith/helpers/object"
import { romanize } from "@optolith/helpers/roman"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import type { ResolvedSelectOption } from "optolith-database-schema/cache"
import type {
ActivatableIdentifier,
ActivatableNameBuilderRules,
RequirableSelectOptionIdentifier,
} from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../helpers/getTypes.js"
import {
AdvantageIdentifier,
DisadvantageIdentifier,
KarmaSpecialAbilityIdentifier,
} from "../../helpers/identifiers.js"
import { LocaleEnvironment } from "../../helpers/locale.js"
import type { LocaleMap } from "../../helpers/translate.js"
import type {
LocaleMap,
Translate,
TranslateMap,
} from "../../helpers/translate.js"
import type { GetResolvedSelectOptionById } from "./prerequisites/single/activatable.js"
import { MISSING_VALUE } from "./unknown.js"
/**
* Returns the full name of the activatable entry as well as its components.
*/
export type ActivatableNameComponents = {
full: ActivatableNameChunk
fullWithoutLevel:
id: ActivatableIdentifier
base: ActivatableNameChunk
options:
| ActivatableNameChunk
| [ActivatableNameChunk, ActivatableNameChunk]
| undefined
level: number | undefined
nameBuilderRules: Required<ActivatableNameBuilderRules>
}
/**
* Returns the full name of the activatable entry as well as its components.
*/
export type CombinedActivatableNameComponents = {
id: ActivatableIdentifier
base: ActivatableNameChunk
options: (
| ActivatableNameChunk
| [ActivatableNameChunk, ActivatableNameChunk]
)[]
level: number | undefined
nameBuilderRules: Required<ActivatableNameBuilderRules>
}
/**
@@ -38,7 +54,7 @@ export type ActivatableNameComponents = {
export type ActivatableNameChunk =
| LocaleMap<string>
| string
| ((locale: LocaleEnvironment) => string)
| ((translateMap: TranslateMap) => string)
const combineChunks = (
a: ActivatableNameChunk,
@@ -60,14 +76,16 @@ const combineChunks = (
} else if (typeof b === "function") {
return fs => join(a(fs), b(fs))
} else if (typeof b === "object") {
return fs => join(a(fs), fs.translateMap(b) ?? MISSING_VALUE)
return translateMap =>
join(a(translateMap), translateMap(b) ?? MISSING_VALUE)
}
return b
} else if (typeof a === "object") {
if (typeof b === "string") {
return mapObject(a, aValue => join(aValue, b))
} else if (typeof b === "function") {
return fs => join(fs.translateMap(a) ?? MISSING_VALUE, b(fs))
return translateMap =>
join(translateMap(a) ?? MISSING_VALUE, b(translateMap))
} else if (typeof b === "object") {
const ret: LocaleMap<string> = {}
for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
@@ -80,431 +98,459 @@ const combineChunks = (
return a
}
const mapChunk = (
chunk: ActivatableNameChunk,
map: (str: string) => string,
): ActivatableNameChunk => {
if (typeof chunk === "string") {
return map(chunk)
} else if (typeof chunk === "function") {
return fs => map(chunk(fs))
} else if (typeof chunk === "object") {
return mapObject(chunk, chunkValue => map(chunkValue))
}
return chunk
}
const combinePair = (pair: [ActivatableNameChunk, ActivatableNameChunk]) =>
combineChunks(pair[0], pair[1], (a, b) => `${a}: ${b}`)
const normalizeChunks = (
chunk: ActivatableNameChunk | [ActivatableNameChunk, ActivatableNameChunk],
): ActivatableNameChunk => (Array.isArray(chunk) ? combinePair(chunk) : chunk)
/**
* Zips multiple name chunks together, separating them with commas. Pairs of chunks are combined with a colon.
*/
const zipChunks = (
chunks: (
| ActivatableNameChunk
| [ActivatableNameChunk, ActivatableNameChunk]
)[],
): ActivatableNameChunk => {
const withNormalizedPairs = chunks.map(chunk => {
if (Array.isArray(chunk)) {
return combineChunks(chunk[0], chunk[1], (a, b) => `${a}: ${b}`)
}
): ActivatableNameChunk =>
chunks
.map(normalizeChunks)
.reduce(
(acc, chunk) =>
acc === "" ? chunk : combineChunks(acc, chunk, (a, b) => `${a}, ${b}`),
"",
)
const renderLevel = (
formatAsPrerequisite: boolean,
id: ActivatableIdentifier,
level: number | undefined,
) =>
level === undefined
? undefined
: formatAsPrerequisite ||
id.kind === "Advantage" ||
id.kind === "Disadvantage" ||
level === 1
? romanize(level)
: `I${romanize(level)}`
/**
* Converts a name chunk to a displayable string.
*/
const renderActivatableNameChunk = (
translateMap: TranslateMap,
chunk: ActivatableNameChunk,
): string => {
if (typeof chunk === "string") {
return chunk
})
return withNormalizedPairs.reduce(
(acc, chunk) =>
acc === "" ? chunk : combineChunks(acc, chunk, (a, b) => `${a}, ${b}`),
"",
)
} else if (typeof chunk === "function") {
return chunk(translateMap)
} else if (typeof chunk === "object") {
return translateMap(chunk) ?? MISSING_VALUE
}
return chunk
}
const combineBaseName = (
base: ActivatableNameChunk,
level: number | undefined,
options: ActivatableNameComponents["options"],
config: {
levelPlacement?: "before" | "after"
useParenthesis?: boolean
} = {},
): Pick<ActivatableNameComponents, "full" | "fullWithoutLevel"> => {
const { levelPlacement = "after", useParenthesis = true } = config
const appendLevel: (str: string) => string =
level === undefined ? x => x : x => `${x} ${romanize(level)}`
/**
* This can be used to render an activatable entry with a selectable level, as it splits the text where the level should be inserted.
*/
export const renderActivatableNameComponentsWithoutLevel = (
translateMap: TranslateMap,
components: ActivatableNameComponents,
): [beforeLevel: string, afterLevel?: string] => {
const { levelPlacement, useParenthesis } = components.nameBuilderRules
const wrapParens = (str: string) => `(${str})`
const wrapParens: (text: string) => string = useParenthesis
? str => `(${str})`
: identity
const appendOptions = useParenthesis
? (baseStr: string, optionsStr: string) =>
optionsStr === "" ? baseStr : `${baseStr} ${wrapParens(optionsStr)}`
: (baseStr: string, optionsStr: string) =>
optionsStr === "" ? baseStr : `${baseStr} ${optionsStr}`
const base = renderActivatableNameChunk(translateMap, components.base)
const options =
components.options === undefined
? undefined
: wrapParens(
renderActivatableNameChunk(
translateMap,
normalizeChunks(components.options),
),
)
const full = combineChunks(
base,
zipChunks(options),
(() => {
switch (levelPlacement) {
case "before":
return (a, b) => appendOptions(appendLevel(a), b)
case "after":
return (a, b) => appendLevel(appendOptions(a, b))
default:
return assertExhaustive(levelPlacement)
}
})(),
)
switch (levelPlacement) {
case "before":
return {
full,
fullWithoutLevel: [base, mapChunk(zipChunks(options), wrapParens)],
}
case "after":
return {
full,
fullWithoutLevel: combineChunks(
base,
zipChunks(options),
appendOptions,
),
}
switch (levelPlacement.kind) {
case "BeforeOptions":
return [base, options]
case "AfterOptions":
return [[base, options].filter(isNotNullish).join(" ")]
default:
return assertExhaustive(levelPlacement)
}
}
const getEntrySpecificFullName = (
getInstanceById: GetInstanceById<"Aspect">,
locale: LocaleEnvironment,
id: ActivatableIdentifier,
base: ActivatableNameChunk,
level: number | undefined,
options: RequirableSelectOptionIdentifier[] | undefined,
printedOptions: ActivatableNameComponents["options"],
): Pick<ActivatableNameComponents, "full" | "fullWithoutLevel"> | undefined => {
switch (id.kind) {
case "Advantage":
switch (id.Advantage) {
case AdvantageIdentifier.HatredOf: {
const [firstOption, ...rest] = printedOptions
/**
* Renders the name components of an activatable entry.
*/
export const renderActivatableNameComponents = (
translateMap: TranslateMap,
components: ActivatableNameComponents,
formatAsPrerequisite: boolean,
): string => {
const levelText = renderLevel(
formatAsPrerequisite,
components.id,
components.level,
)
if (firstOption === undefined || Array.isArray(firstOption)) {
return undefined
}
const [beforeLevel, afterLevel] = renderActivatableNameComponentsWithoutLevel(
translateMap,
components,
)
return combineBaseName(
combineChunks(base, firstOption, (a, b) => `${a} ${b}`),
level,
rest,
)
}
case AdvantageIdentifier.ImmunityToPoison:
case AdvantageIdentifier.ImmunityToDisease:
return combineBaseName(base, level, printedOptions, {
useParenthesis: false,
})
default:
return undefined
}
case "Disadvantage":
switch (id.Disadvantage) {
case DisadvantageIdentifier.PersonalityFlaw: {
const [selection, optionalText, ...rest] = printedOptions
return [beforeLevel, levelText, afterLevel].filter(isNotNullish).join(" ")
}
if (
selection === undefined ||
Array.isArray(selection) ||
Array.isArray(optionalText)
) {
return undefined
}
return combineBaseName(base, level, [
optionalText === undefined ? selection : [selection, optionalText],
...rest,
])
}
case DisadvantageIdentifier.AfraidOf:
return combineBaseName(base, level, printedOptions, {
useParenthesis: false,
})
case DisadvantageIdentifier.Principles:
case DisadvantageIdentifier.Obligations:
return combineBaseName(base, level, printedOptions, {
levelPlacement: "before",
})
default:
return undefined
}
case "AdvancedCombatSpecialAbility":
case "AdvancedKarmaSpecialAbility":
case "AdvancedMagicalSpecialAbility":
return undefined
case "AdvancedSkillSpecialAbility":
switch (id.AdvancedSkillSpecialAbility) {
// case AdvancedSkillSpecialAbilityIdentifier.Fachwissen: {
// const [skillId, firstApplicationId, secondApplicationId] = options ?? []
// const aspect =
// aspectId?.tag === "Aspect"
// ? getAspectById(aspectId.aspect)
// : undefined
// const aspectTranslations = locale.translateMap(aspect?.translations)
// if (aspectTranslations === undefined) {
// return undefined
// }
// return combineBaseName(
// combineChunks(
// base,
// aspectTranslations.master_of_aspect_suffix ??
// aspectTranslations.name,
// (a, b) => `${a} ${b}`,
// ),
// level,
// [],
// )
// const getApp = (
// getSid: (r: Record<ActiveObjectWithId>) => Maybe<string | number>,
// ) =>
// pipe(
// SA.applications,
// filter(pipe(AA.prerequisite, isNothing)),
// find(pipe(AA.id, elemF(getSid(hero_entry)))),
// fmap(AA.name),
// )
// return pipe_(
// hero_entry,
// AOWIA.sid,
// misStringM,
// bindF(lookupF(SDA.skills(staticData))),
// bindF(skill =>
// pipe_(
// List(getApp(AOWIA.sid2)(skill), getApp(AOWIA.sid3)(skill)),
// catMaybes,
// ensure(xs => flength(xs) === 2),
// fmap(
// pipe(
// sortStrings(staticData),
// formatList("conjunction")(staticData),
// apps => `${SA.name(skill)}: ${apps}`,
// ),
// ),
// ),
// ),
// )
// }
default:
return undefined
}
case "AncestorGlyph":
case "ArcaneOrbEnchantment":
case "AttireEnchantment":
case "Beutelzauber":
case "BlessedTradition":
case "BowlEnchantment":
case "BrawlingSpecialAbility":
case "CauldronEnchantment":
case "CeremonialItemSpecialAbility":
case "ChronicleEnchantment":
case "CombatSpecialAbility":
case "CombatStyleSpecialAbility":
case "CommandSpecialAbility":
case "DaggerRitual":
case "FamiliarSpecialAbility":
case "FatePointSexSpecialAbility":
case "FatePointSpecialAbility":
case "FoolsHatEnchantment":
return undefined
case "GeneralSpecialAbility":
switch (id.GeneralSpecialAbility) {
// case GeneralSpecialAbilityIdentifier.LanguageSpecialization: {
// const [languageId, specializationId] = options ?? []
// const language =
// languageId?.tag === "Language"
// ? getLanguageById(languageId.language)
// : undefined
// const specializations =
// language?.specializations?.tag === "Specific"
// ? language.specializations.specific.list
// : []
// const specialization =
// specializationId?.tag === "General"
// ? specializations.find(
// spec => spec.id === specializationId.general,
// )
// : undefined
// if (aspectTranslations === undefined) {
// return undefined
// }
// return combineBaseName(
// combineChunks(
// base,
// aspectTranslations.master_of_aspect_suffix ??
// aspectTranslations.name,
// (a, b) => `${a} ${b}`,
// ),
// level,
// [],
// )
// return pipe(
// SDA.specialAbilities,
// lookup<string>(SpecialAbilityId.Language),
// bindF(pipe(findSelectOption, thrush(AOWIA.sid(hero_entry)))),
// bindF(lang =>
// pipe(
// AOWIA.sid2,
// bindF(
// ifElse<string | number, string>(isString)<Maybe<string>>(
// Just,
// )(spec_id =>
// bind(SOA.specializations(lang))(subscriptF(spec_id - 1)),
// ),
// ),
// fmap(spec => `${SOA.name(lang)}: ${spec}`),
// )(hero_entry),
// ),
// )(staticData)
// }
default:
return undefined
}
case "Haubenzauber":
case "InstrumentEnchantment":
return undefined
case "KarmaSpecialAbility":
switch (id.KarmaSpecialAbility) {
case KarmaSpecialAbilityIdentifier.MasterOfAspect: {
const [aspectId] = options ?? []
const aspect =
aspectId?.kind === "Aspect"
? getInstanceById("Aspect", aspectId.Aspect)
: undefined
const aspectTranslations = locale.translateMap(aspect?.translations)
if (aspectTranslations === undefined) {
return undefined
}
return combineBaseName(
combineChunks(
base,
aspectTranslations.master_of_aspect_suffix ??
aspectTranslations.name,
(a, b) => `${a} ${b}`,
),
level,
[],
)
}
default:
return undefined
}
case "Krallenkettenzauber":
case "Kristallkugelzauber":
case "LiturgicalStyleSpecialAbility":
case "LycantropicGift":
case "MagicalSign":
case "MagicalSpecialAbility":
case "MagicalTradition":
case "MagicStyleSpecialAbility":
case "OrbEnchantment":
case "PactGift":
case "ProtectiveWardingCircleSpecialAbility":
case "RingEnchantment":
case "Sermon":
case "SexSpecialAbility":
case "SickleRitual":
case "SikaryanDrainSpecialAbility":
case "SkillStyleSpecialAbility":
case "SpellSwordEnchantment":
case "StaffEnchantment":
case "ToyEnchantment":
case "Trinkhornzauber":
case "VampiricGift":
case "Vision":
case "WandEnchantment":
case "WeaponEnchantment":
return undefined
default:
return assertExhaustive(id)
/**
* Combines multiple name components into a single one when all values except for options are the same, joining options according to the passed function.
*
* Returns `undefined` if the components cannot be combined.
*/
export const combineNameComponents = (
elements: ActivatableNameComponents[],
): CombinedActivatableNameComponents | undefined => {
if (
isNotEmpty(elements) &&
allSame(
elements,
on(item => [item.id, item.level], deepEqual),
)
) {
return {
...elements[0],
options:
ensureNonEmpty(elements.map(e => e.options).filter(isNotNullish)) ?? [],
}
}
return undefined
}
/**
* Renders the name components of an activatable entry, combining multiple options into one string.
*/
export const renderCombinedActivatableNameComponents = (
translateMap: TranslateMap,
components: CombinedActivatableNameComponents,
formatAsPrerequisite: boolean,
join: (list: string[]) => string = list => list.join(", "),
): string =>
renderActivatableNameComponents(
translateMap,
{
...components,
options: join(
components.options.map(chunk =>
renderActivatableNameChunk(translateMap, normalizeChunks(chunk)),
),
),
},
formatAsPrerequisite,
)
/**
* Renders the name components of multiple activatable entries.
*/
export const renderMultipleStandaloneActivatableNameComponents = (
translateMap: TranslateMap,
components: ActivatableNameComponents[],
formatAsPrerequisite: boolean,
join: (list: string[]) => string = list => list.join(", "),
): string =>
join(
components.map(item =>
renderActivatableNameComponents(translateMap, item, formatAsPrerequisite),
),
)
/**
* Renders the name components of multiple activatable entries, combining parts if possible.
*/
export const renderActivatableNameComponentsCombinedIfPossible = (
translateMap: TranslateMap,
components: ActivatableNameComponents[],
formatAsPrerequisite: boolean,
join: (list: string[]) => string = list => list.join(", "),
): string => {
const combined = combineNameComponents(components)
if (combined === undefined) {
return renderMultipleStandaloneActivatableNameComponents(
translateMap,
components,
formatAsPrerequisite,
join,
)
} else {
return renderCombinedActivatableNameComponents(
translateMap,
combined,
formatAsPrerequisite,
join,
)
}
}
// const getEntrySpecificFullName = (
// getInstanceById: GetInstanceById<"Aspect">,
// locale: LocaleEnvironment,
// id: ActivatableIdentifier,
// base: ActivatableNameChunk,
// level: number | undefined,
// printedOptions: ActivatableNameComponents["options"],
// ):
// | Pick<ActivatableNameComponents, "full" | "fullWithoutLevel" | "level">
// | undefined => {
// switch (id.kind) {
// case "Advantage":
// switch (id.Advantage) {
// case AdvantageIdentifier.HatredOf: {
// const [firstOption, ...rest] = printedOptions
// if (firstOption === undefined || Array.isArray(firstOption)) {
// return undefined
// }
// return combineBaseName(
// combineChunks(base, firstOption, (a, b) => `${a} ${b}`),
// level,
// rest,
// )
// }
// default:
// return undefined
// }
// case "Disadvantage":
// switch (id.Disadvantage) {
// case DisadvantageIdentifier.PersonalityFlaw: {
// const [selection, optionalText, ...rest] = printedOptions
// if (
// selection === undefined ||
// Array.isArray(selection) ||
// Array.isArray(optionalText)
// ) {
// return undefined
// }
// return combineBaseName(base, level, [
// optionalText === undefined ? selection : [selection, optionalText],
// ...rest,
// ])
// }
// default:
// return undefined
// }
// case "AdvancedCombatSpecialAbility":
// case "AdvancedKarmaSpecialAbility":
// case "AdvancedMagicalSpecialAbility":
// case "AdvancedSkillSpecialAbility":
// switch (id.AdvancedSkillSpecialAbility) {
// case AdvancedSkillSpecialAbilityIdentifier.Fachwissen: {
// const [skillId, firstApplicationId, secondApplicationId] = options ?? []
// const aspect =
// aspectId?.tag === "Aspect"
// ? getAspectById(aspectId.aspect)
// : undefined
// const aspectTranslations = locale.translateMap(aspect?.translations)
// if (aspectTranslations === undefined) {
// return undefined
// }
// return combineBaseName(
// combineChunks(
// base,
// aspectTranslations.master_of_aspect_suffix ??
// aspectTranslations.name,
// (a, b) => `${a} ${b}`,
// ),
// level,
// [],
// )
// const getApp = (
// getSid: (r: Record<ActiveObjectWithId>) => Maybe<string | number>,
// ) =>
// pipe(
// SA.applications,
// filter(pipe(AA.prerequisite, isNothing)),
// find(pipe(AA.id, elemF(getSid(hero_entry)))),
// fmap(AA.name),
// )
// return pipe_(
// hero_entry,
// AOWIA.sid,
// misStringM,
// bindF(lookupF(SDA.skills(staticData))),
// bindF(skill =>
// pipe_(
// List(getApp(AOWIA.sid2)(skill), getApp(AOWIA.sid3)(skill)),
// catMaybes,
// ensure(xs => flength(xs) === 2),
// fmap(
// pipe(
// sortStrings(staticData),
// formatList("conjunction")(staticData),
// apps => `${SA.name(skill)}: ${apps}`,
// ),
// ),
// ),
// ),
// )
// }
// default:
// return undefined
// }
// case "AncestorGlyph":
// case "ArcaneOrbEnchantment":
// case "AttireEnchantment":
// case "Beutelzauber":
// case "BlessedTradition":
// case "BowlEnchantment":
// case "BrawlingSpecialAbility":
// case "CauldronEnchantment":
// case "CeremonialItemSpecialAbility":
// case "ChronicleEnchantment":
// case "CombatSpecialAbility":
// case "CombatStyleSpecialAbility":
// case "CommandSpecialAbility":
// case "DaggerRitual":
// case "FamiliarSpecialAbility":
// case "FatePointSexSpecialAbility":
// case "FatePointSpecialAbility":
// case "FoolsHatEnchantment":
// return undefined
// case "GeneralSpecialAbility":
// switch (id.GeneralSpecialAbility) {
// case GeneralSpecialAbilityIdentifier.LanguageSpecialization: {
// const [languageId, specializationId] = options ?? []
// const language =
// languageId?.tag === "Language"
// ? getLanguageById(languageId.language)
// : undefined
// const specializations =
// language?.specializations?.tag === "Specific"
// ? language.specializations.specific.list
// : []
// const specialization =
// specializationId?.tag === "General"
// ? specializations.find(
// spec => spec.id === specializationId.general,
// )
// : undefined
// if (aspectTranslations === undefined) {
// return undefined
// }
// return pipe(
// SDA.specialAbilities,
// lookup<string>(SpecialAbilityId.Language),
// bindF(pipe(findSelectOption, thrush(AOWIA.sid(hero_entry)))),
// bindF(lang =>
// pipe(
// AOWIA.sid2,
// bindF(
// ifElse<string | number, string>(isString)<Maybe<string>>(
// Just,
// )(spec_id =>
// bind(SOA.specializations(lang))(subscriptF(spec_id - 1)),
// ),
// ),
// fmap(spec => `${SOA.name(lang)}: ${spec}`),
// )(hero_entry),
// ),
// )(staticData)
// }
const renderOptions = (
displayedInProfession: boolean,
getResolvedSelectOptionById: GetResolvedSelectOptionById,
id: ActivatableIdentifier,
options: RequirableSelectOptionIdentifier[] | undefined,
):
| ActivatableNameChunk
| [ActivatableNameChunk, ActivatableNameChunk]
| undefined => {
const arr =
options?.map(optionId => {
const optTranslations = getResolvedSelectOptionById(id, optionId)?.content
.translations
return optTranslations === undefined
? MISSING_VALUE
: mapObject(optTranslations, t10n =>
displayedInProfession
? (t10n.name_in_profession ?? t10n.name)
: t10n.name,
)
}) ?? []
if (isNotEmpty(arr) && arr.length > 1) {
const [first, ...rest] = arr
return [first, zipChunks(rest)]
}
return arr[0]
}
/**
* Gets the name components for an activatable entry.
*/
export const getNameComponents = <T>(
getInstanceById: GetInstanceById<"Aspect">,
locale: LocaleEnvironment,
translate: Translate,
id: ActivatableIdentifier,
options: RequirableSelectOptionIdentifier[] | undefined,
level: number | undefined,
nameBuilderRules: ActivatableNameBuilderRules | undefined,
translations: LocaleMap<T>,
getBaseName: (translation: T) => string,
getSelectOptionById: (
id: RequirableSelectOptionIdentifier,
) => ResolvedSelectOption | undefined,
getResolvedSelectOptionById: GetResolvedSelectOptionById,
displayedInProfession: boolean,
): ActivatableNameComponents => {
const base = mapObject(translations, getBaseName)
const nameOptions: ActivatableNameComponents["options"] = (() => {
const arr =
options?.map(optionId => {
const optTranslations =
getSelectOptionById(optionId)?.content.translations
return optTranslations === undefined
? MISSING_VALUE
: mapObject(optTranslations, t10n =>
displayedInProfession
? (t10n.name_in_profession ?? t10n.name)
: t10n.name,
)
}) ?? []
const nameBuilderRulesWithDefaults: Required<ActivatableNameBuilderRules> = {
levelPlacement: nameBuilderRules?.levelPlacement ?? {
kind: "AfterOptions",
},
useParenthesis: nameBuilderRules?.useParenthesis ?? true,
}
if (arr.length > 1) {
const [first, ...rest] = arr
return [[first!, zipChunks(rest)]]
}
const renderedBase = mapObject(translations, getBaseName)
const renderedOptions = renderOptions(
displayedInProfession,
getResolvedSelectOptionById,
id,
options,
)
return arr
})()
const isTradition =
id.kind === "MagicalTradition" || id.kind === "BlessedTradition"
const actualBase = isTradition ? translate("Tradition") : renderedBase
const actualOptions:
| ActivatableNameChunk
| [ActivatableNameChunk, ActivatableNameChunk]
| undefined = isTradition
? renderedOptions === undefined
? renderedBase
: [renderedBase, normalizeChunks(renderedOptions)]
: renderedOptions
return {
...(getEntrySpecificFullName(
getInstanceById,
locale,
id,
base,
level,
options,
nameOptions,
) ?? combineBaseName(base, level, nameOptions)),
base,
options: nameOptions,
id,
base: actualBase,
options: actualOptions,
level,
nameBuilderRules: nameBuilderRulesWithDefaults,
}
}
/**
* Converts a name chunk to a displayable string.
*/
export const printActivatableNameChunk = (
locale: LocaleEnvironment,
chunk: ActivatableNameChunk,
): string => {
if (typeof chunk === "string") {
return chunk
} else if (typeof chunk === "function") {
return chunk(locale)
} else if (typeof chunk === "object") {
return locale.translateMap(chunk) ?? MISSING_VALUE
}
return chunk
}
@@ -1,6 +1,6 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import { DisplayOption } from "optolith-database-schema/gen"
import { LocaleEnvironment } from "../../../helpers/locale.js"
import type { TranslateMap } from "../../../helpers/translate.js"
import { MISSING_VALUE } from "../unknown.js"
import { PrerequisitePart } from "./part.js"
@@ -8,7 +8,7 @@ import { PrerequisitePart } from "./part.js"
* Get the translation of a display option.
*/
export const printDisplayOption = (
locale: LocaleEnvironment,
translateMap: TranslateMap,
displayOption: DisplayOption,
): PrerequisitePart | undefined => {
switch (displayOption.kind) {
@@ -16,9 +16,7 @@ export const printDisplayOption = (
return undefined
case "ReplaceWith":
return {
value:
locale.translateMap(displayOption.ReplaceWith.translations)
?.replacement ?? MISSING_VALUE,
value: translateMap(displayOption.ReplaceWith.translations)?.replacement ?? MISSING_VALUE,
sentenceType: displayOption.ReplaceWith.sentence_type,
isMeta: false,
}
+73 -75
View File
@@ -1,4 +1,5 @@
import { on } from "@elyukai/utils/function"
import { mapNullable } from "@elyukai/utils/nullable"
import { numAsc } from "@optolith/helpers/compare"
import { isNotNullish } from "@optolith/helpers/nullable"
import { romanize } from "@optolith/helpers/roman"
@@ -32,9 +33,14 @@ import {
} from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../helpers/locale.js"
import type { TranslateMap } from "../../../helpers/translate.js"
import {
renderActivatableNameComponents,
renderActivatableNameComponentsCombinedIfPossible,
} from "../activatableNameChunks.js"
import { MISSING_VALUE } from "../unknown.js"
import { printDisplayOption } from "./displayOption.js"
import { joinPrerequisiteParts, PrerequisitePart } from "./part.js"
import { hasPartValueObject, joinPrerequisiteParts, PrerequisitePart } from "./part.js"
import {
printAdvantageDisadvantagePrerequisiteGroup,
printAnimistPowerPrerequisiteGroup,
@@ -56,26 +62,24 @@ import { GetResolvedSelectOptionById } from "./single/activatable.js"
type Prerequisite = { kind: string }
const printPrerequisiteGroup = (
locale: LocaleEnvironment,
translateMap: TranslateMap,
group: PrerequisiteGroup<unknown>,
): PrerequisitePart => ({
value: locale.translateMap(group.translations)?.text ?? MISSING_VALUE,
value: translateMap(group.translations)?.text ?? MISSING_VALUE,
sentenceType: undefined,
isMeta: false,
})
const printPrerequisitesDisjunction = <T extends Prerequisite>(
getPrerequisiteTranslation: (prerequisite: T) => PrerequisitePart | undefined,
locale: LocaleEnvironment,
locale: Pick<LocaleEnvironment, "translateMap" | "join">,
disjunction: PrerequisitesDisjunction<T>,
): PrerequisitePart | undefined => {
if (disjunction.display_option !== undefined) {
return printDisplayOption(locale, disjunction.display_option)
return printDisplayOption(locale.translateMap, disjunction.display_option)
}
const [first, ...other] = disjunction.list
.map(getPrerequisiteTranslation)
.filter(isNotNullish)
const [first, ...other] = disjunction.list.map(getPrerequisiteTranslation).filter(isNotNullish)
if (first === undefined) {
return undefined
@@ -83,16 +87,26 @@ const printPrerequisitesDisjunction = <T extends Prerequisite>(
if (
disjunction.list.length < 2 ||
disjunction.list
.slice(1)
.every(part => part.kind === disjunction.list[0]!.kind)
disjunction.list.slice(1).every(part => part.kind === disjunction.list[0]!.kind)
) {
return {
label: first.label,
value: locale.join(
[first, ...other].map(part => part.value),
"disjunction",
),
value:
hasPartValueObject(first) && other.every(hasPartValueObject)
? renderActivatableNameComponentsCombinedIfPossible(
locale.translateMap,
[first.value, ...other.map(part => part.value)],
true,
list => locale.join(list, "disjunction"),
)
: locale.join(
[first, ...other].map(part =>
typeof part.value === "string"
? part.value
: renderActivatableNameComponents(locale.translateMap, part.value, true),
),
"disjunction",
),
sentenceType: undefined,
isMeta: false,
}
@@ -113,20 +127,16 @@ const printPrerequisitesDisjunction = <T extends Prerequisite>(
*/
const printPrerequisitesElement = <T extends Prerequisite>(
printPrerequisite: (prerequisite: T) => PrerequisitePart | undefined,
locale: LocaleEnvironment,
locale: Pick<LocaleEnvironment, "translateMap" | "join">,
element: PrerequisitesElement<T>,
): PrerequisitePart | undefined => {
switch (element.kind) {
case "Single":
return printPrerequisite(element.Single)
case "Disjunction":
return printPrerequisitesDisjunction(
printPrerequisite,
locale,
element.Disjunction,
)
return printPrerequisitesDisjunction(printPrerequisite, locale, element.Disjunction)
case "Group":
return printPrerequisiteGroup(locale, element.Group)
return printPrerequisiteGroup(locale.translateMap, element.Group)
default:
return assertExhaustive(element)
}
@@ -137,14 +147,19 @@ const printPrerequisitesElement = <T extends Prerequisite>(
*/
const printPlainPrerequisites = <T extends Prerequisite>(
printPrerequisite: (prerequisite: T) => PrerequisitePart | undefined,
locale: LocaleEnvironment,
locale: Pick<LocaleEnvironment, "translate" | "translateMap" | "compare" | "join">,
prerequisites: PlainPrerequisites<T>,
): string =>
joinPrerequisiteParts(
locale,
locale.translate,
locale.translateMap,
locale.compare,
prerequisites
.map(element =>
printPrerequisitesElement(printPrerequisite, locale, element),
mapNullable(printPrerequisitesElement(printPrerequisite, locale, element), part => ({
type: element.kind === "Single" ? element.Single.kind : element.kind,
part,
})),
)
.filter(isNotNullish),
)
@@ -173,18 +188,13 @@ const printPrerequisitesForLevels = <T extends Prerequisite>(
const previousLevelPrerequisites: PrerequisitesForLevels<T> =
printPreviousLevelPrerequisites === undefined
? []
: Array.from(
{ length: printPreviousLevelPrerequisites.levels - 1 },
(_, i) => ({
level: i + 2,
prerequisite: {
kind: "Single",
Single: printPreviousLevelPrerequisites.createPreerequisite(
i + 2,
),
},
}),
)
: Array.from({ length: printPreviousLevelPrerequisites.levels - 1 }, (_, i) => ({
level: i + 2,
prerequisite: {
kind: "Single",
Single: printPreviousLevelPrerequisites.createPreerequisite(i + 2),
},
}))
const groupedByLevel = Map.groupBy(
[...value, ...previousLevelPrerequisites],
@@ -198,8 +208,7 @@ const printPrerequisitesForLevels = <T extends Prerequisite>(
.toArray()
.sort(on(item => item[0], numAsc))
const hasOnlyBasePrerequisites =
groupedByLevel.size === 1 && hasBasePrerequisites
const hasOnlyBasePrerequisites = groupedByLevel.size === 1 && hasBasePrerequisites
const printedParts = [
...(hasBasePrerequisites
@@ -211,10 +220,18 @@ const printPrerequisitesForLevels = <T extends Prerequisite>(
]),
...sortedByLevel.map(([levelNumber, prerequisites]) => {
const prerequisitesString = joinPrerequisiteParts(
locale,
locale.translate,
locale.translateMap,
locale.compare,
prerequisites
.map(prerequisite =>
printPrerequisiteForLevel(printPrerequisite, locale, prerequisite),
.map(element =>
mapNullable(printPrerequisiteForLevel(printPrerequisite, locale, element), part => ({
type:
element.prerequisite.kind === "Single"
? element.prerequisite.Single.kind
: element.prerequisite.kind,
part,
})),
)
.filter(isNotNullish),
)
@@ -238,8 +255,7 @@ export const printDerivedCharacteristicPrerequisites = (
value: DerivedCharacteristicPrerequisites,
): string =>
printPlainPrerequisites(
prerequisite =>
printDerivedCharacteristicPrerequisiteGroup(locale, prerequisite),
prerequisite => printDerivedCharacteristicPrerequisiteGroup(locale, prerequisite),
locale,
value,
)
@@ -253,8 +269,7 @@ export const printPublicationPrerequisites = (
value: PublicationPrerequisites,
): string =>
printPlainPrerequisites(
prerequisite =>
printPublicationPrerequisiteGroup(getInstanceById, locale, prerequisite),
prerequisite => printPublicationPrerequisiteGroup(getInstanceById, locale, prerequisite),
locale,
value,
)
@@ -347,14 +362,10 @@ export const printGeneralPrerequisites = (
*/
export const printProfessionPrerequisites = (
getInstanceById: GetInstanceById<
| "Race"
| "Culture"
| ActivatableIdentifier["kind"]
| RatedIdentifier["kind"]
| "Aspect"
"Race" | "Culture" | ActivatableIdentifier["kind"] | RatedIdentifier["kind"] | "Aspect"
>,
getResolvedSelectOptionById: GetResolvedSelectOptionById,
locale: LocaleEnvironment,
locale: Pick<LocaleEnvironment, "translate" | "translateMap" | "compare" | "join">,
value: ProfessionPrerequisites,
): string =>
printPlainPrerequisites(
@@ -415,12 +426,7 @@ export const printArcaneTraditionPrerequisites = (
value: ArcaneTraditionPrerequisites,
): string =>
printPlainPrerequisites(
prerequisite =>
printArcaneTraditionPrerequisiteGroup(
getInstanceById,
locale,
prerequisite,
),
prerequisite => printArcaneTraditionPrerequisiteGroup(getInstanceById, locale, prerequisite),
locale,
value,
)
@@ -434,12 +440,7 @@ export const printPersonalityTraitPrerequisites = (
value: PersonalityTraitPrerequisites,
): string =>
printPlainPrerequisites(
prerequisite =>
printPersonalityTraitPrerequisiteGroup(
getInstanceById,
locale,
prerequisite,
),
prerequisite => printPersonalityTraitPrerequisiteGroup(getInstanceById, locale, prerequisite),
locale,
value,
)
@@ -453,8 +454,7 @@ export const printSpellworkPrerequisites = (
value: SpellworkPrerequisites,
): string =>
printPlainPrerequisites(
prerequisite =>
printSpellworkPrerequisiteGroup(getInstanceById, locale, prerequisite),
prerequisite => printSpellworkPrerequisiteGroup(getInstanceById, locale, prerequisite),
locale,
value,
)
@@ -476,11 +476,12 @@ export const printLiturgyPrerequisites = (
* Print influence prerequisites as a string.
*/
export const printInfluencePrerequisites = (
getInstanceById: GetInstanceById<"Influence">,
locale: LocaleEnvironment,
value: InfluencePrerequisites,
): string =>
printPlainPrerequisites(
prerequisite => printInfluencePrerequisiteGroup(locale, prerequisite),
prerequisite => printInfluencePrerequisiteGroup(getInstanceById, locale, prerequisite),
locale,
value,
)
@@ -489,9 +490,7 @@ export const printInfluencePrerequisites = (
* Print language prerequisites as a string.
*/
export const printLanguagePrerequisites = (
getInstanceById: GetInstanceById<
"Race" | ActivatableIdentifier["kind"] | "Aspect"
>,
getInstanceById: GetInstanceById<"Race" | ActivatableIdentifier["kind"] | "Aspect">,
getResolvedSelectOptionById: GetResolvedSelectOptionById,
locale: LocaleEnvironment,
value: LanguagePrerequisites,
@@ -517,8 +516,7 @@ export const printAnimistPowerPrerequisites = (
value: AnimistPowerPrerequisites,
): string =>
printPlainPrerequisites(
prerequisite =>
printAnimistPowerPrerequisiteGroup(getInstanceById, locale, prerequisite),
prerequisite => printAnimistPowerPrerequisiteGroup(getInstanceById, locale, prerequisite),
locale,
value,
)
@@ -527,11 +525,12 @@ export const printAnimistPowerPrerequisites = (
* Print geode ritual prerequisites as a string.
*/
export const printGeodeRitualPrerequisites = (
getInstanceById: GetInstanceById<"Influence">,
locale: LocaleEnvironment,
value: GeodeRitualPrerequisites,
): string =>
printPlainPrerequisites(
prerequisite => printGeodeRitualPrerequisiteGroup(locale, prerequisite),
prerequisite => printGeodeRitualPrerequisiteGroup(getInstanceById, locale, prerequisite),
locale,
value,
)
@@ -545,8 +544,7 @@ export const printEnhancementPrerequisites = (
value: EnhancementPrerequisites,
): string =>
printPlainPrerequisites(
prerequisite =>
printEnhancementPrerequisiteGroup(getInstanceById, locale, prerequisite),
prerequisite => printEnhancementPrerequisiteGroup(getInstanceById, locale, prerequisite),
locale,
value,
)
+191 -30
View File
@@ -1,47 +1,208 @@
import { groupBy } from "@elyukai/utils/array/groups"
import { deepEqual, equal } from "@elyukai/utils/equality"
import { on } from "@elyukai/utils/function"
import { compareNumber, reduceCompare, type Compare } from "@elyukai/utils/ordering"
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import { SentenceType } from "optolith-database-schema/gen"
import { LocaleEnvironment } from "../../../helpers/locale.js"
import { SentenceType, type ActivatableIdentifier } from "optolith-database-schema/gen"
import type { LocaleCompare } from "../../../helpers/locale.js"
import type { Translate, TranslateMap } from "../../../helpers/translate.js"
import {
renderActivatableNameComponents,
renderActivatableNameComponentsCombinedIfPossible,
type ActivatableNameComponents,
} from "../activatableNameChunks.js"
/**
* A part of the total list of prerequisites.
*/
export type PrerequisitePart = {
label?: string
value: string
value: string | ActivatableNameComponents
sentenceType: SentenceType | undefined
isMeta: boolean
}
/**
* Type guard for prerequisite parts with a value object.
*/
export const hasPartValueObject = (
part: PrerequisitePart,
): part is PrerequisitePart & { value: ActivatableNameComponents } => typeof part.value !== "string"
const countFurtherToCombined = (
part: PrerequisitePart & { value: ActivatableNameComponents },
remainingParts: (PrerequisitePart & { value: ActivatableNameComponents })[],
) => {
const indexHasDifferentBaseValues = remainingParts.findIndex(
remaining =>
!(
part.label === remaining.label &&
deepEqual(part.value.id, remaining.value.id) &&
part.value.level === remaining.value.level
),
)
return indexHasDifferentBaseValues === -1
? // if -1 is returned, all remaining parts have the same base values, so we can combine with all of them
remainingParts.length
: indexHasDifferentBaseValues
}
const joinAdjacentParts = (
translateMap: TranslateMap,
localeCompare: LocaleCompare,
part: PrerequisitePart & { value: ActivatableNameComponents },
remainingParts: (PrerequisitePart & { value: ActivatableNameComponents })[],
): [renderedValue: string, furtherIncluded: number] => {
const valuesWithSameBase = remainingParts.slice(0, countFurtherToCombined(part, remainingParts))
if (valuesWithSameBase.length > 0) {
return [
renderActivatableNameComponentsCombinedIfPossible(
translateMap,
[part.value, ...valuesWithSameBase.map(p => p.value)],
true,
list => list.toSorted(localeCompare).join(", "),
),
valuesWithSameBase.length,
]
}
return [renderActivatableNameComponents(translateMap, part.value, true), 0]
}
const appendBySentenceType = (
previouslyRendered: string,
currentRendered: string,
sentenceType: SentenceType | undefined,
isLast: boolean,
): string => {
switch (sentenceType?.kind) {
case "Standalone":
return `${
/[.;]$/u.test(previouslyRendered)
? `${previouslyRendered.slice(0, -1)}. `
: `${previouslyRendered}. `
}${currentRendered}${currentRendered.endsWith(".") ? "" : "."}`
case "Connected":
return `${previouslyRendered === "" ? "" : /[.;]$/u.test(previouslyRendered) ? `${previouslyRendered} ` : `${previouslyRendered}; `}${currentRendered}${
isLast ? "" : ";"
}`
case undefined:
return previouslyRendered === ""
? currentRendered
: `${previouslyRendered}${/[.;]$/u.test(previouslyRendered) ? " " : ", "}${currentRendered}`
default:
return assertExhaustive(sentenceType)
}
}
type ActivatableGroup = "Advantage" | "Disadvantage" | "SpecialAbility"
const activatableKindToGroup = (kind: ActivatableIdentifier["kind"]): ActivatableGroup =>
kind === "Advantage" ? "Advantage" : kind === "Disadvantage" ? "Disadvantage" : "SpecialAbility"
const activatableGroupOrder: ActivatableGroup[] = ["Advantage", "Disadvantage", "SpecialAbility"]
const sortByActivatableGroup: Compare<ActivatableGroup> = on(
group => activatableGroupOrder.indexOf(group),
compareNumber,
)
const sortByActivatableGroupAndName = (
localeCompare: LocaleCompare,
): Compare<[ActivatableGroup, string]> =>
reduceCompare(
on(item => item[0], sortByActivatableGroup),
on(item => item[1], localeCompare),
)
const appendPrerequisitePartGroup = (
previous: string,
translateMap: TranslateMap,
localeCompare: LocaleCompare,
parts: PrerequisitePart[],
isLast: boolean,
) => {
if (parts.length === 0) {
return previous
} else if (parts.every(hasPartValueObject)) {
return appendBySentenceType(
previous,
parts
.reduce(
(
acc: [[ActivatableGroup, string][], number],
current,
i,
arr,
): [[ActivatableGroup, string][], number] => {
if (acc[1] > 0) {
return [acc[0], acc[1] - 1]
}
const [rendered, furtherIncluded] = joinAdjacentParts(
translateMap,
localeCompare,
current,
arr.slice(i + 1),
)
return [
[
...acc[0],
[activatableKindToGroup(current.value.id.kind), (current.label ?? "") + rendered],
],
furtherIncluded,
]
},
[[], 0],
)[0]
.toSorted(sortByActivatableGroupAndName(localeCompare))
.map(grouped => grouped[1])
.join(", "),
undefined,
isLast,
)
} else {
return parts.reduce(
(acc, current, i, arr) =>
appendBySentenceType(
acc,
(current.label ?? "") +
(typeof current.value === "string"
? current.value
: renderActivatableNameComponents(translateMap, current.value, true)),
current.sentenceType,
isLast && i === arr.length - 1,
),
previous,
)
}
}
/**
* Join prerequisite parts using their configuration.
*/
export const joinPrerequisiteParts = (
locale: LocaleEnvironment,
parts: PrerequisitePart[],
translate: Translate,
translateMap: TranslateMap,
localeCompare: LocaleCompare,
parts: { type: string; part: PrerequisitePart }[],
): string =>
parts.reduce(
(acc, part, i, arr) => {
const text =
part.label === undefined ? part.value : part.label + part.value
if (acc === "") {
return text
}
switch (part.sentenceType?.kind) {
case "Standalone":
return `${
/[.;]$/u.test(acc) ? `${acc.slice(0, -1)}. ` : `${acc}. `
}${text}${text.endsWith(".") ? "" : "."}`
case "Connected":
return `${/[.;]$/u.test(acc) ? `${acc} ` : `${acc}; `}${text}${
i < arr.length - 1 ? ";" : ""
}`
case undefined:
return `${acc}${/[.;]$/u.test(acc) ? " " : ", "}${text}`
default:
return assertExhaustive(part.sentenceType)
}
},
parts.every(part => part.isMeta) ? locale.translate("none") : "",
groupBy(
parts,
on(part => part.type, equal),
)
.map(group => ({
type: group[0]!.type,
parts: group.map(groupItem => groupItem.part),
}))
.reduce(
(acc, partGroup, i, arr) =>
appendPrerequisitePartGroup(
acc,
translateMap,
localeCompare,
partGroup.parts,
i === arr.length - 1,
),
parts.every(({ part }) => part.isMeta) ? translate("none") : "",
)
@@ -21,10 +21,7 @@ import {
import type { GetInstanceById } from "../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../helpers/locale.js"
import { PrerequisitePart } from "./part.js"
import {
GetResolvedSelectOptionById,
printActivatablePrerequisite,
} from "./single/activatable.js"
import { GetResolvedSelectOptionById, printActivatablePrerequisite } from "./single/activatable.js"
import { printAnimistPowerPrerequisite } from "./single/animistPower.js"
import { printBlessedTraditionPrerequisite } from "./single/blessedTradition.js"
import { printCommonSuggestedByRCPPrerequisite } from "./single/commonSuggestedByRCP.js"
@@ -59,15 +56,9 @@ export const printDerivedCharacteristicPrerequisiteGroup = (
case "Rule":
return printRulePrerequisite(locale, prerequisite.Rule)
case "BlessedTradition":
return printBlessedTraditionPrerequisite(
locale,
prerequisite.BlessedTradition,
)
return printBlessedTraditionPrerequisite(locale, prerequisite.BlessedTradition)
case "MagicalTradition":
return printMagicalTraditionPrerequisite(
locale,
prerequisite.MagicalTradition,
)
return printMagicalTraditionPrerequisite(locale, prerequisite.MagicalTradition)
default:
return assertExhaustive(prerequisite)
}
@@ -86,11 +77,7 @@ export const printPublicationPrerequisiteGroup = (
// default:
// return assertExhaustive(prerequisite)
// }
printPublicationPrerequisite(
getInstanceById,
locale,
prerequisite.Publication,
)
printPublicationPrerequisite(getInstanceById, locale, prerequisite.Publication)
/**
* Print the translation of a general prerequisite group.
@@ -115,49 +102,33 @@ export const printGeneralPrerequisiteGroup = (
): PrerequisitePart | undefined => {
switch (prerequisite.kind) {
case "Sex":
return printBinarySexPrerequisite(locale, prerequisite.Sex)
return printBinarySexPrerequisite(locale.translate, prerequisite.Sex)
case "Race":
return printRacePrerequisite(getInstanceById, locale, prerequisite.Race)
case "Culture":
return printCulturePrerequisite(
getInstanceById,
locale,
prerequisite.Culture,
)
return printCulturePrerequisite(getInstanceById, locale, prerequisite.Culture)
case "Pact":
return printPactPrerequisite(getInstanceById, locale, prerequisite.Pact)
case "SocialStatus":
return printSocialStatusPrerequisite(
getInstanceById,
locale,
prerequisite.SocialStatus,
)
return printSocialStatusPrerequisite(getInstanceById, locale, prerequisite.SocialStatus)
case "State":
return printStatePrerequisite(getInstanceById, locale, prerequisite.State)
case "Rule":
return printRulePrerequisite(locale, prerequisite.Rule)
case "PrimaryAttribute":
return printPrimaryAttributePrerequisite(
locale,
prerequisite.PrimaryAttribute,
)
return printPrimaryAttributePrerequisite(locale, prerequisite.PrimaryAttribute)
case "Activatable":
return printActivatablePrerequisite(
getInstanceById,
getResolvedSelectOptionById,
locale,
prerequisite.Activatable,
false,
)
case "BlessedTradition":
return printBlessedTraditionPrerequisite(
locale,
prerequisite.BlessedTradition,
)
return printBlessedTraditionPrerequisite(locale, prerequisite.BlessedTradition)
case "MagicalTradition":
return printMagicalTraditionPrerequisite(
locale,
prerequisite.MagicalTradition,
)
return printMagicalTraditionPrerequisite(locale, prerequisite.MagicalTradition)
case "Rated":
return printRatedPrerequisite(getInstanceById, locale, prerequisite.Rated)
case "RatedMinimumNumber":
@@ -167,24 +138,13 @@ export const printGeneralPrerequisiteGroup = (
prerequisite.RatedMinimumNumber,
)
case "RatedSum":
return printRatedSumPrerequisite(
getInstanceById,
locale,
prerequisite.RatedSum,
)
return printRatedSumPrerequisite(getInstanceById, locale, prerequisite.RatedSum)
case "Enhancement":
return printEnhancementPrerequisite(
getInstanceById,
locale,
prerequisite.Enhancement,
)
return printEnhancementPrerequisite(getInstanceById, locale, prerequisite.Enhancement)
case "Text":
return printTextPrerequisite(locale, prerequisite.Text)
case "SexualCharacteristic":
return printSexualCharacteristicPrerequisite(
locale,
prerequisite.SexualCharacteristic,
)
return printSexualCharacteristicPrerequisite(locale, prerequisite.SexualCharacteristic)
default:
return assertExhaustive(prerequisite)
}
@@ -195,33 +155,26 @@ export const printGeneralPrerequisiteGroup = (
*/
export const printProfessionPrerequisiteGroup = (
getInstanceById: GetInstanceById<
| "Race"
| "Culture"
| ActivatableIdentifier["kind"]
| RatedIdentifier["kind"]
| "Aspect"
"Race" | "Culture" | ActivatableIdentifier["kind"] | RatedIdentifier["kind"] | "Aspect"
>,
getResolvedSelectOptionById: GetResolvedSelectOptionById,
locale: LocaleEnvironment,
locale: Pick<LocaleEnvironment, "translate" | "translateMap">,
prerequisite: ProfessionPrerequisiteGroup,
): PrerequisitePart | undefined => {
switch (prerequisite.kind) {
case "Sex":
return printBinarySexPrerequisite(locale, prerequisite.Sex)
return printBinarySexPrerequisite(locale.translate, prerequisite.Sex)
case "Race":
return printRacePrerequisite(getInstanceById, locale, prerequisite.Race)
case "Culture":
return printCulturePrerequisite(
getInstanceById,
locale,
prerequisite.Culture,
)
return printCulturePrerequisite(getInstanceById, locale, prerequisite.Culture)
case "Activatable":
return printActivatablePrerequisite(
getInstanceById,
getResolvedSelectOptionById,
locale,
prerequisite.Activatable,
true,
)
case "Rated":
return printRatedPrerequisite(getInstanceById, locale, prerequisite.Rated)
@@ -296,13 +249,9 @@ export const printArcaneTraditionPrerequisiteGroup = (
): PrerequisitePart | undefined => {
switch (prerequisite.kind) {
case "Sex":
return printBinarySexPrerequisite(locale, prerequisite.Sex)
return printBinarySexPrerequisite(locale.translate, prerequisite.Sex)
case "Culture":
return printCulturePrerequisite(
getInstanceById,
locale,
prerequisite.Culture,
)
return printCulturePrerequisite(getInstanceById, locale, prerequisite.Culture)
default:
return assertExhaustive(prerequisite)
}
@@ -320,11 +269,7 @@ export const printPersonalityTraitPrerequisiteGroup = (
case "Race":
return printRacePrerequisite(getInstanceById, locale, prerequisite.Race)
case "Culture":
return printCulturePrerequisite(
getInstanceById,
locale,
prerequisite.Culture,
)
return printCulturePrerequisite(getInstanceById, locale, prerequisite.Culture)
case "PersonalityTrait":
return printPersonalityTraitPrerequisite(
getInstanceById,
@@ -374,12 +319,13 @@ export const printLiturgyPrerequisiteGroup = (
* Print the translation of an influence prerequisite group.
*/
export const printInfluencePrerequisiteGroup = (
getInstanceById: GetInstanceById<"Influence">,
locale: LocaleEnvironment,
prerequisite: InfluencePrerequisiteGroup,
): PrerequisitePart | undefined => {
switch (prerequisite.kind) {
case "Influence":
return printInfluencePrerequisite(locale, prerequisite.Influence)
return printInfluencePrerequisite(getInstanceById, locale, prerequisite.Influence)
case "Text":
return printTextPrerequisite(locale, prerequisite.Text)
default:
@@ -391,9 +337,7 @@ export const printInfluencePrerequisiteGroup = (
* Print the translation of a language prerequisite group.
*/
export const printLanguagePrerequisiteGroup = (
getInstanceById: GetInstanceById<
"Race" | ActivatableIdentifier["kind"] | "Aspect"
>,
getInstanceById: GetInstanceById<"Race" | ActivatableIdentifier["kind"] | "Aspect">,
getResolvedSelectOptionById: GetResolvedSelectOptionById,
locale: LocaleEnvironment,
prerequisite: LanguagePrerequisiteGroup,
@@ -407,6 +351,7 @@ export const printLanguagePrerequisiteGroup = (
getResolvedSelectOptionById,
locale,
prerequisite.Activatable,
false,
)
case "Text":
return printTextPrerequisite(locale, prerequisite.Text)
@@ -428,16 +373,13 @@ export const printAnimistPowerPrerequisiteGroup = (
// default:
// return assertExhaustive(prerequisite)
// }
printAnimistPowerPrerequisite(
getInstanceById,
locale,
prerequisite.AnimistPower,
)
printAnimistPowerPrerequisite(getInstanceById, locale, prerequisite.AnimistPower)
/**
* Print the translation of a geode ritual prerequisite group.
*/
export const printGeodeRitualPrerequisiteGroup = (
getInstanceById: GetInstanceById<"Influence">,
locale: LocaleEnvironment,
prerequisite: GeodeRitualPrerequisiteGroup,
): PrerequisitePart | undefined =>
@@ -446,7 +388,7 @@ export const printGeodeRitualPrerequisiteGroup = (
// default:
// return assertExhaustive(prerequisite)
// }
printInfluencePrerequisite(locale, prerequisite.Influence)
printInfluencePrerequisite(getInstanceById, locale, prerequisite.Influence)
/**
* Print the translation of an enhancement prerequisite group.
@@ -460,11 +402,7 @@ export const printEnhancementPrerequisiteGroup = (
case "Rated":
return printRatedPrerequisite(getInstanceById, locale, prerequisite.Rated)
case "Enhancement":
return printEnhancementPrerequisite(
getInstanceById,
locale,
prerequisite.Enhancement,
)
return printEnhancementPrerequisite(getInstanceById, locale, prerequisite.Enhancement)
default:
return assertExhaustive(prerequisite)
}
@@ -480,16 +418,9 @@ export const printPreconditionGroup = (
): PrerequisitePart | undefined => {
switch (prerequisite.kind) {
case "Publication":
return printPublicationPrerequisite(
getInstanceById,
locale,
prerequisite.Publication,
)
return printPublicationPrerequisite(getInstanceById, locale, prerequisite.Publication)
case "SexualCharacteristic":
return printSexualCharacteristicPrerequisite(
locale,
prerequisite.SexualCharacteristic,
)
return printSexualCharacteristicPrerequisite(locale, prerequisite.SexualCharacteristic)
default:
return assertExhaustive(prerequisite)
}
@@ -4,16 +4,14 @@ import type {
} from "optolith-database-schema/cache"
import type {
ActivatableIdentifier,
ActivatableNameBuilderRules,
ActivatablePrerequisite,
RequirableSelectOptionIdentifier,
} from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { LocaleMap } from "../../../../helpers/translate.js"
import {
getNameComponents,
printActivatableNameChunk,
} from "../../activatableNameChunks.js"
import type { LocaleMap, Translate } from "../../../../helpers/translate.js"
import { getNameComponents } from "../../activatableNameChunks.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
@@ -25,31 +23,39 @@ export type GetResolvedSelectOptionById = (
selectOptionId: ResolvedSelectOptionIdentifier,
) => ResolvedSelectOption | undefined
const printActivatableName = (
/**
* Get the name components of an activatable.
*/
export const printActivatableName = (
getInstanceById: GetInstanceById<ActivatableIdentifier["kind"] | "Aspect">,
locale: LocaleEnvironment,
translate: Translate,
id: ActivatableIdentifier,
options: RequirableSelectOptionIdentifier[] | undefined,
level: number | undefined,
getResolvedSelectOptionById: GetResolvedSelectOptionById,
displayedInProfession: boolean,
) => {
const entry: { translations: LocaleMap<{ name: string }> } | undefined =
getInstanceById(id)
const entry:
| {
nameBuilderRules?: ActivatableNameBuilderRules
translations: LocaleMap<{ name: string }>
}
| undefined = getInstanceById(id)
if (entry === undefined) {
return undefined
}
return getNameComponents(
getInstanceById,
locale,
translate,
id,
options,
level,
entry.nameBuilderRules,
entry.translations,
t => t.name,
selectOptionId => getResolvedSelectOptionById(id, selectOptionId),
false,
getResolvedSelectOptionById,
displayedInProfession,
)
}
@@ -59,20 +65,22 @@ const printActivatableName = (
export const printActivatablePrerequisite = (
getInstanceById: GetInstanceById<ActivatableIdentifier["kind"] | "Aspect">,
getResolvedSelectOptionById: GetResolvedSelectOptionById,
locale: LocaleEnvironment,
locale: Pick<LocaleEnvironment, "translate" | "translateMap">,
prerequisite: ActivatablePrerequisite,
displayedInProfession: boolean,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
const nameComponents = printActivatableName(
getInstanceById,
locale,
locale.translate,
prerequisite.id,
prerequisite.options,
prerequisite.level,
getResolvedSelectOptionById,
displayedInProfession,
)
if (nameComponents === undefined) {
@@ -81,11 +89,19 @@ export const printActivatablePrerequisite = (
return {
label: `${
prerequisite.active
? locale.translate("special ability")
: locale.translate("no special ability")
prerequisite.id.kind === "Advantage"
? prerequisite.active
? locale.translate("advantage")
: locale.translate("no advantage")
: prerequisite.id.kind === "Disadvantage"
? prerequisite.active
? locale.translate("disadvantage")
: locale.translate("no disadvantage")
: prerequisite.active
? locale.translate("special ability")
: locale.translate("no special ability")
} `,
value: printActivatableNameChunk(locale, nameComponents.full),
value: nameComponents,
sentenceType: undefined,
isMeta: false,
}
@@ -15,7 +15,7 @@ export const printAnimistPowerPrerequisite = (
prerequisite: AnimistPowerPrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
const animistPower = getInstanceById("AnimistPower", prerequisite.id)
@@ -23,9 +23,7 @@ export const printAnimistPowerPrerequisite = (
return {
value: [
locale.translateMap(animistPower?.translations)?.name ?? "MISSING_VALUE",
prerequisite.level === undefined
? undefined
: romanize(prerequisite.level),
prerequisite.level === undefined ? undefined : romanize(prerequisite.level),
prerequisite.value.toString(),
]
.filter(isNotNullish)
@@ -35,7 +35,7 @@ export const printBlessedTraditionPrerequisite = (
prerequisite: BlessedTraditionPrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
return {
@@ -9,11 +9,11 @@ import { PrerequisitePart } from "../part.js"
*/
export const printCulturePrerequisite = (
getInstanceById: GetInstanceById<"Culture">,
locale: LocaleEnvironment,
locale: Pick<LocaleEnvironment, "translate" | "translateMap">,
prerequisite: CulturePrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
const culture = getInstanceById("Culture", prerequisite.id)
@@ -1,4 +1,5 @@
import { InfluencePrerequisite } from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import { MISSING_VALUE } from "../../unknown.js"
import { printDisplayOption } from "../displayOption.js"
@@ -8,17 +9,20 @@ import { PrerequisitePart } from "../part.js"
* Get the translation of a culture prerequisite.
*/
export const printInfluencePrerequisite = (
locale: LocaleEnvironment,
getInstanceById: GetInstanceById<"Influence">,
locale: Pick<LocaleEnvironment, "translate" | "translateMap">,
prerequisite: InfluencePrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
// TODO
const name =
locale.translateMap(getInstanceById("Influence", prerequisite.id)?.translations)?.name ??
MISSING_VALUE
return {
value: MISSING_VALUE,
value: `${locale.translate("no influence")} ${name}`,
sentenceType: undefined,
isMeta: false,
}
@@ -31,7 +31,7 @@ export const printMagicalTraditionPrerequisite = (
prerequisite: MagicalTraditionPrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
return {
@@ -16,7 +16,7 @@ export const printPactPrerequisite = (
prerequisite: PactPrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
const pactCategory = getInstanceById("PactCategory", prerequisite.category)
@@ -28,16 +28,14 @@ export const printPactPrerequisite = (
domain: locale.join(
prerequisite.domain.map(
id =>
locale.translateMap(
getInstanceById("PactDomain", id)?.translations,
)?.name ?? MISSING_VALUE,
locale.translateMap(getInstanceById("PactDomain", id)?.translations)?.name ??
MISSING_VALUE,
),
"disjunction",
),
}),
locale.translate("{$pact} level {$pactLevel}", {
pact:
locale.translateMap(pactCategory?.translations)?.name ?? MISSING_VALUE,
pact: locale.translateMap(pactCategory?.translations)?.name ?? MISSING_VALUE,
pactLevel: romanize(prerequisite.level ?? 1),
}),
].filter(isNotNullish)
@@ -13,25 +13,19 @@ export const printPersonalityTraitPrerequisite = (
prerequisite: PersonalityTraitPrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
const personalityTrait = getInstanceById("PersonalityTrait", prerequisite.id)
const personalityTraitTranslation = locale.translateMap(
personalityTrait?.translations,
)
const personalityTraitTranslation = locale.translateMap(personalityTrait?.translations)
if (
personalityTrait === undefined ||
personalityTraitTranslation === undefined
) {
if (personalityTrait === undefined || personalityTraitTranslation === undefined) {
return undefined
}
const name = `${personalityTraitTranslation.name} (${locale.translate(
"Level {$level}",
{ level: personalityTrait.level },
)})`
const name = `${personalityTraitTranslation.name} (${locale.translate("Level {$level}", {
level: personalityTrait.level,
})})`
return {
value: prerequisite.active
@@ -11,7 +11,7 @@ export const printPrimaryAttributePrerequisite = (
prerequisite: PrimaryAttributePrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
return {
@@ -13,7 +13,7 @@ export const printPublicationPrerequisite = (
prerequisite: PublicationPrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
const publication = getInstanceById("Publication", prerequisite.id)
@@ -9,11 +9,11 @@ import { PrerequisitePart } from "../part.js"
*/
export const printRacePrerequisite = (
getInstanceById: GetInstanceById<"Race">,
locale: LocaleEnvironment,
locale: Pick<LocaleEnvironment, "translate" | "translateMap">,
prerequisite: RacePrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
const race = getInstanceById("Race", prerequisite.id)
@@ -1,10 +1,8 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import type {
RatedIdentifier,
RatedPrerequisite,
} from "optolith-database-schema/gen"
import type { RatedIdentifier, RatedPrerequisite } from "optolith-database-schema/gen"
import { type GetInstanceById } from "../../../../helpers/getTypes.js"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { TranslateMap } from "../../../../helpers/translate.js"
import { MISSING_VALUE } from "../../unknown.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
@@ -20,56 +18,40 @@ const printRatedName = (
| "LiturgicalChant"
| "Ceremony"
>,
locale: LocaleEnvironment,
translateMap: TranslateMap,
id: RatedIdentifier,
) => {
switch (id.kind) {
case "Attribute":
return (
locale.translateMap(
getInstanceById("Attribute", id.Attribute)?.translations,
)?.name ?? MISSING_VALUE
translateMap(getInstanceById("Attribute", id.Attribute)?.translations)?.name ??
MISSING_VALUE
)
case "Skill":
return (
locale.translateMap(getInstanceById("Skill", id.Skill)?.translations)
?.name ?? MISSING_VALUE
)
return translateMap(getInstanceById("Skill", id.Skill)?.translations)?.name ?? MISSING_VALUE
case "CloseCombatTechnique":
return (
locale.translateMap(
getInstanceById("CloseCombatTechnique", id.CloseCombatTechnique)
?.translations,
)?.name ?? MISSING_VALUE
translateMap(getInstanceById("CloseCombatTechnique", id.CloseCombatTechnique)?.translations)
?.name ?? MISSING_VALUE
)
case "RangedCombatTechnique":
return (
locale.translateMap(
getInstanceById("RangedCombatTechnique", id.RangedCombatTechnique)
?.translations,
translateMap(
getInstanceById("RangedCombatTechnique", id.RangedCombatTechnique)?.translations,
)?.name ?? MISSING_VALUE
)
case "Spell":
return (
locale.translateMap(getInstanceById("Spell", id.Spell)?.translations)
?.name ?? MISSING_VALUE
)
return translateMap(getInstanceById("Spell", id.Spell)?.translations)?.name ?? MISSING_VALUE
case "Ritual":
return (
locale.translateMap(getInstanceById("Ritual", id.Ritual)?.translations)
?.name ?? MISSING_VALUE
)
return translateMap(getInstanceById("Ritual", id.Ritual)?.translations)?.name ?? MISSING_VALUE
case "LiturgicalChant":
return (
locale.translateMap(
getInstanceById("LiturgicalChant", id.LiturgicalChant)?.translations,
)?.name ?? MISSING_VALUE
translateMap(getInstanceById("LiturgicalChant", id.LiturgicalChant)?.translations)?.name ??
MISSING_VALUE
)
case "Ceremony":
return (
locale.translateMap(
getInstanceById("Ceremony", id.Ceremony)?.translations,
)?.name ?? MISSING_VALUE
translateMap(getInstanceById("Ceremony", id.Ceremony)?.translations)?.name ?? MISSING_VALUE
)
default:
return assertExhaustive(id)
@@ -90,17 +72,15 @@ export const printRatedPrerequisite = (
| "LiturgicalChant"
| "Ceremony"
>,
locale: LocaleEnvironment,
locale: Pick<LocaleEnvironment, "translateMap">,
prerequisite: RatedPrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
return {
value: `${printRatedName(getInstanceById, locale, prerequisite.id)} ${
prerequisite.value
}`,
value: `${printRatedName(getInstanceById, locale.translateMap, prerequisite.id)} ${prerequisite.value}`,
sentenceType: undefined,
isMeta: false,
}
@@ -10,40 +10,25 @@ import { MISSING_VALUE } from "../../unknown.js"
import { printDisplayOption } from "../displayOption.js"
import { PrerequisitePart } from "../part.js"
const printNumberOfTheFollowingSkills = (
locale: LocaleEnvironment,
number: number,
): string =>
locale.translate(
".input {$count :number} {{{$count} of the following skills}}",
{ count: number },
)
const printNumberOfTheFollowingSkills = (locale: LocaleEnvironment, number: number): string =>
locale.translate(".input {$count :number} {{{$count} of the following skills}}", {
count: number,
})
const printNumberOfAllCombatTechniques = (
locale: LocaleEnvironment,
number: number,
): string =>
const printNumberOfAllCombatTechniques = (locale: LocaleEnvironment, number: number): string =>
locale.translate(".input {$count :number} {{{$count} combat techniques}}", {
count: number,
})
const printNumberOfCloseCombatTechniques = (
locale: LocaleEnvironment,
number: number,
): string =>
locale.translate(
".input {$count :number} {{{$count} close combat techniques}}",
{ count: number },
)
const printNumberOfCloseCombatTechniques = (locale: LocaleEnvironment, number: number): string =>
locale.translate(".input {$count :number} {{{$count} close combat techniques}}", {
count: number,
})
const printNumberOfRangedCombatTechniques = (
locale: LocaleEnvironment,
number: number,
): string =>
locale.translate(
".input {$count :number} {{{$count} ranged combat techniques}}",
{ count: number },
)
const printNumberOfRangedCombatTechniques = (locale: LocaleEnvironment, number: number): string =>
locale.translate(".input {$count :number} {{{$count} ranged combat techniques}}", {
count: number,
})
const printNumberOfCombatTechniques = (
locale: LocaleEnvironment,
@@ -71,17 +56,13 @@ export const printRatedMinimumNumberPrerequisite = (
prerequisite: RatedMinimumNumberPrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
switch (prerequisite.targets.kind) {
case "Skills": {
const skills = prerequisite.targets.Skills.targets
.map(
id =>
locale.translateMap(getInstanceById("Skill", id)?.translations)
?.name,
)
.map(id => locale.translateMap(getInstanceById("Skill", id)?.translations)?.name)
.filter(isNotNullish)
return {
@@ -124,10 +105,7 @@ export const printRatedMinimumNumberPrerequisite = (
count: prerequisite.number,
property:
locale.translateMap(
getInstanceById(
"Property",
prerequisite.targets.Spellworks.property,
)?.translations,
getInstanceById("Property", prerequisite.targets.Spellworks.property)?.translations,
)?.name ?? MISSING_VALUE,
minRating: prerequisite.value,
},
@@ -145,8 +123,7 @@ export const printRatedMinimumNumberPrerequisite = (
count: prerequisite.number,
aspect:
locale.translateMap(
getInstanceById("Aspect", prerequisite.targets.Liturgies.aspect)
?.translations,
getInstanceById("Aspect", prerequisite.targets.Liturgies.aspect)?.translations,
)?.name ?? MISSING_VALUE,
minRating: prerequisite.value,
},
@@ -14,25 +14,18 @@ export const printRatedSumPrerequisite = (
prerequisite: RatedSumPrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
const skills = prerequisite.targets
.map(
skillId =>
locale.translateMap(getInstanceById("Skill", skillId)?.translations)
?.name,
)
.map(skillId => locale.translateMap(getInstanceById("Skill", skillId)?.translations)?.name)
.filter(isNotNullish)
return {
value: locale.translate(
"the SR for {$skill} combined must add up to at least {$minRating}",
{
skill: locale.join(skills, "conjunction"),
minRating: prerequisite.sum,
},
),
value: locale.translate("the SR for {$skill} combined must add up to at least {$minRating}", {
skill: locale.join(skills, "conjunction"),
minRating: prerequisite.sum,
}),
sentenceType: undefined,
isMeta: false,
}
@@ -1,14 +1,14 @@
import { assertExhaustive } from "@optolith/helpers/typeSafety"
import type { BinarySex, SexPrerequisite } from "optolith-database-schema/gen"
import { LocaleEnvironment } from "../../../../helpers/locale.js"
import type { Translate } from "../../../../helpers/translate.js"
import { PrerequisitePart } from "../part.js"
const printId = (locale: LocaleEnvironment, id: BinarySex): string => {
const printId = (translate: Translate, id: BinarySex): string => {
switch (id.kind) {
case "Male":
return locale.translate("Male")
return translate("Male")
case "Female":
return locale.translate("Female")
return translate("Female")
default:
return assertExhaustive(id)
}
@@ -18,10 +18,10 @@ const printId = (locale: LocaleEnvironment, id: BinarySex): string => {
* Get the translation of a (binary) sex prerequisite.
*/
export const printBinarySexPrerequisite = (
locale: LocaleEnvironment,
translate: Translate,
prerequisite: SexPrerequisite,
): PrerequisitePart | undefined => ({
value: printId(locale, prerequisite.id),
value: printId(translate, prerequisite.id),
sentenceType: undefined,
isMeta: false,
})
@@ -13,13 +13,11 @@ export const printSocialStatusPrerequisite = (
prerequisite: SocialStatusPrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
const socialStatus = getInstanceById("SocialStatus", prerequisite.id)
const socialStatusTranslation = locale.translateMap(
socialStatus?.translations,
)
const socialStatusTranslation = locale.translateMap(socialStatus?.translations)
if (socialStatusTranslation === undefined) {
return undefined
@@ -13,7 +13,7 @@ export const printStatePrerequisite = (
prerequisite: StatePrerequisite,
): PrerequisitePart | undefined => {
if (prerequisite.display_option !== undefined) {
return printDisplayOption(locale, prerequisite.display_option)
return printDisplayOption(locale.translateMap, prerequisite.display_option)
}
const state = getInstanceById("State", prerequisite.id)
+1 -2
View File
@@ -37,9 +37,8 @@ export const getBaseProfessionPackageForCurriculum = (
(_acc: { id: string; content: ProfessionPackage } | undefined, version) =>
getChildInstancesForInstanceId("ProfessionPackage", version.id).find(
professionPackage =>
professionPackage.content.experience_level === undefined ||
professionPackage.content.experience_level ===
idMap.ExperienceLevel.Experienced,
idMap.ExperienceLevel.Experienced,
),
isNotNullish,
undefined,
@@ -15,7 +15,7 @@ import {
} from "optolith-database-schema/gen"
import { Case } from "../../../../helpers/enums.js"
import {
getInstanceByIdR,
getInstanceByIdFnR,
modifiableBySpeedR,
type StdReader,
} from "../../reader.js"
@@ -37,7 +37,7 @@ const deriveModifiableCastingTime = (
"s" | "ibi",
"SkillModificationLevel"
> =>
getInstanceByIdR<"SkillModificationLevel">().thenW(
getInstanceByIdFnR<"SkillModificationLevel">().thenW(
getInstanceById =>
mapNullable(
getInstanceById("SkillModificationLevel", modificationLevelId),
@@ -26,7 +26,7 @@ import { additionFormatter } from "../../mathOperation.js"
import {
formatEnergyFnR,
formatEnergyR,
getInstanceByIdR,
getInstanceByIdFnR,
modifiableBySpeedR,
responsiveLocaleJoinR,
responsiveR,
@@ -53,7 +53,7 @@ import {
const deriveModifiableCost = (
modificationLevelId: SkillModificationLevel_ID,
): StdReader<number | undefined, "s" | "ibi", "SkillModificationLevel"> =>
getInstanceByIdR<"SkillModificationLevel">().thenW(
getInstanceByIdFnR<"SkillModificationLevel">().thenW(
getInstanceById =>
mapNullable(
getInstanceById("SkillModificationLevel", modificationLevelId),
@@ -10,7 +10,7 @@ import type {
SkillModificationLevel_ID,
} from "optolith-database-schema/gen"
import {
getInstanceByIdR,
getInstanceByIdFnR,
modifiableBySpeedOptionalR,
modifiableBySpeedR,
translateFnR,
@@ -38,7 +38,7 @@ const deriveModifiableRange = (
"s" | "tm" | "ibi",
"SkillModificationLevel"
> =>
getInstanceByIdR<"SkillModificationLevel">().thenW(
getInstanceByIdFnR<"SkillModificationLevel">().thenW(
getInstanceById =>
mapNullable(
getInstanceById("SkillModificationLevel", modificationLevelId),
@@ -8,7 +8,7 @@ import type {
} from "optolith-database-schema/gen"
import { type RawDefinitionListEntityDescriptionSectionItem } from "../../../../index.js"
import {
getInstanceByIdR,
getInstanceByIdFnR,
translateMapR,
translateR,
type StdReader,
@@ -17,7 +17,7 @@ import { MISSING_VALUE } from "../../unknown.js"
import { appendInParensIfNotEmpty } from "./parensIf.js"
const renderPredefined = (targetCategoryId: TargetCategory_ID) =>
getInstanceByIdR<"TargetCategory">()
getInstanceByIdFnR<"TargetCategory">()
.thenW(
getInstanceById =>
mapNullable(
+2 -2
View File
@@ -7,7 +7,7 @@ import type {
} from "optolith-database-schema/gen"
import { type IdMap } from "../../../index.js"
import {
getInstanceByIdR,
getInstanceByIdFnR,
responsiveR,
responsiveThenR,
responsiveTranslateR,
@@ -50,7 +50,7 @@ const renderSkillCheckPenalty = (
penalty: SkillCheckPenalty,
): StdReader<string, "t" | "tm" | "rts" | "ibi", "DerivedCharacteristic"> => {
const getDerivedCharacteristicTranslation = (id: string) =>
getInstanceByIdR<"DerivedCharacteristic">()
getInstanceByIdFnR<"DerivedCharacteristic">()
.map(getInstanceById => getInstanceById("DerivedCharacteristic", id))
.thenW(dc =>
dc === undefined
+165 -52
View File
@@ -2,6 +2,7 @@ import { isNotNullish } from "@elyukai/utils/nullable"
import { Reader } from "@elyukai/utils/reader"
import { assertExhaustive } from "@elyukai/utils/typeSafety"
import type {
ChildEntityMap,
EntityMap,
FastSkillModificationLevelConfig,
ResponsiveText,
@@ -9,13 +10,15 @@ import type {
SkillModificationLevel,
SlowSkillModificationLevelConfig,
} from "optolith-database-schema/gen"
import type { GetInstanceById } from "../../helpers/getTypes.js"
import type { IdArgsVariant } from "tsondb/schema/gen"
import type {
LocaleCompare,
LocaleJoin,
LocaleJoinType,
} from "../../helpers/locale.js"
GetAllChildInstancesForParent,
GetAllInstances,
GetInstanceById,
} from "../../helpers/getTypes.js"
import type { LocaleCompare, LocaleJoin, LocaleJoinType } from "../../helpers/locale.js"
import type {
Format,
LocaleMap,
Translate,
TranslateMap,
@@ -24,15 +27,22 @@ import type {
TranslationParamsInArray,
Translations,
} from "../../helpers/translate.js"
import type { GetResolvedSelectOptionById } from "./prerequisites/single/activatable.js"
import type { ModifiableParameter } from "./rated/activatable/nonModifiableSuffix.js"
import { Speed } from "./rated/activatable/speed.js"
import { responsive, ResponsiveTextSize } from "./responsiveText.js"
import { formatEnergy, type EnergyUnit } from "./units/energy.js"
import { MISSING_VALUE } from "./unknown.js"
/**
* The standard set of environment properties for readers in this project.
*/
export type EnvMap<E extends keyof EntityMap = never> = {
export type EnvMap<
E extends keyof EntityMap = never,
AE extends keyof EntityMap = never,
CE extends keyof ChildEntityMap = never,
> = {
format: Format
translate: Translate
translateMap: TranslateMap
localeJoin: LocaleJoin
@@ -40,16 +50,18 @@ export type EnvMap<E extends keyof EntityMap = never> = {
responsiveTextSize: ResponsiveTextSize
speed: Speed
energyUnit: EnergyUnit
nonModifiableSuffix?: (
param: ModifiableParameter,
) => TranslationKeysWithoutParams
nonModifiableSuffix?: (param: ModifiableParameter) => TranslationKeysWithoutParams
getInstanceById: GetInstanceById<E>
getAllInstances: GetAllInstances<AE>
getChildInstancesForInstanceId: GetAllChildInstancesForParent<CE>
getResolvedSelectOptionById: GetResolvedSelectOptionById
}
/**
* Shortcuts for selecting keys of the shared environment map type.
*/
export type EnvMapAbbr = {
f: "format"
t: "translate"
tm: "translateMap"
lj: "localeJoin"
@@ -59,6 +71,9 @@ export type EnvMapAbbr = {
eu: "energyUnit"
nms: "nonModifiableSuffix"
ibi: "getInstanceById"
ai: "getAllInstances"
acibp: "getChildInstancesForInstanceId"
rso: "getResolvedSelectOptionById"
}
/**
@@ -67,9 +82,11 @@ export type EnvMapAbbr = {
* The keys are abbreviated to keep type annotations short.
*/
export type StdEnv<
in K extends keyof EnvMapAbbr = keyof EnvMapAbbr,
in E extends keyof EntityMap = never,
> = Pick<EnvMap<E>, EnvMapAbbr[K]>
K extends keyof EnvMapAbbr = keyof EnvMapAbbr,
E extends keyof EntityMap = never,
AE extends keyof EntityMap = never,
CE extends keyof ChildEntityMap = never,
> = Pick<EnvMap<E, AE, CE>, EnvMapAbbr[K]>
/**
* Shortcut for a reader with common environment properties.
@@ -80,24 +97,34 @@ export type StdReader<
T,
K extends keyof EnvMapAbbr,
E extends keyof EntityMap = never,
> = Reader<StdEnv<K, E>, T>
AE extends keyof EntityMap = never,
CE extends keyof ChildEntityMap = never,
> = Reader<StdEnv<K, E, AE, CE>, T>
// Specialized constructors for common contexts
/**
* Treats the text as a translation string and applies supplied arguments.
*/
export const formatR = (
text: string,
args?: Record<string, unknown> | undefined,
): Reader<{ format: Format }, string> => Reader.asks(env => env.format(text, args))
/**
* Creates a value from a translation key.
*/
export const translateR = <K extends keyof Translations>(
key: K,
...rest: TranslationParamsInArray<K>
): Reader<{ translate: Translate }, string> =>
Reader.asks(env => env.translate(key, ...rest))
): Reader<{ translate: Translate }, string> => Reader.asks(env => env.translate(key, ...rest))
/**
* Returns the `translate` function from the context.
*/
export const translateFnR: Reader<{ translate: Translate }, Translate> =
Reader.asks(env => env.translate)
export const translateFnR: Reader<{ translate: Translate }, Translate> = Reader.asks(
env => env.translate,
)
/**
* Takes the appropriate translation from a locale map.
@@ -110,10 +137,80 @@ export const translateMapR = <T>(
/**
* Returns the `translateMap` function from the context.
*/
export const translateMapFnR: Reader<
{ translateMap: TranslateMap },
TranslateMap
> = Reader.asks(env => env.translateMap)
export const translateMapFnR: Reader<{ translateMap: TranslateMap }, TranslateMap> = Reader.asks(
env => env.translateMap,
)
/**
* Retrieves the translation for the current locale from the given values `translations` property.
* @returns `undefined` if the value does not exist or does not have a translation for the current locale, otherwise the translation.
*/
export const translationR = <T>(
value: { translations?: LocaleMap<T> } | undefined,
): Reader<{ translateMap: TranslateMap }, T | undefined> =>
Reader.asks(env => env.translateMap(value?.translations))
/**
* Retrieves a specific property of the specified entry.
*/
export const mapTranslationR = <
E extends keyof EntityMap &
{
[K in keyof EntityMap]: EntityMap[K] extends { translations: LocaleMap<object> } ? K : never
}[keyof EntityMap],
R,
>(
...args: [
...IdArgsVariant<EntityMap, E>,
fn: (
translation: EntityMap[E] extends { translations: LocaleMap<infer T> }
? T | undefined
: never,
) => R,
]
): Reader<{ translateMap: TranslateMap; getInstanceById: GetInstanceById<E> }, R | undefined> =>
Reader.asks(env => {
const idArgs = args.length === 3 ? ([args[0], args[1]] as const) : ([args[0]] as const)
const fn = args.length === 3 ? args[2] : args[1]
const translation = env.translateMap<object>(env.getInstanceById(...idArgs)?.translations)
return fn(
translation as EntityMap[E] extends { translations: LocaleMap<infer T> }
? T | undefined
: never,
)
})
/**
* Retrieves the `name` property of the specified entry.
*/
export const nameR = <
E extends {
[K in keyof EntityMap]: EntityMap[K] extends { translations: LocaleMap<{ name: string }> }
? K
: never
}[keyof EntityMap],
>(
...args: IdArgsVariant<EntityMap, E>
): Reader<
{ translateMap: TranslateMap; getInstanceById: GetInstanceById<E> },
string | undefined
> => Reader.asks(env => env.translateMap(env.getInstanceById(...args)?.translations)?.name)
/**
* Retrieves the `name` property of the specified entry.
*
* Returns {@link MISSING_VALUE} if there is no name for the entry in the locale. This can happen if the entry itself does not exist, or it does not have a translation for the current locale.
*/
export const strictNameR = <
E extends {
[K in keyof EntityMap]: EntityMap[K] extends { translations: LocaleMap<{ name: string }> }
? K
: never
}[keyof EntityMap],
>(
...args: IdArgsVariant<EntityMap, E>
): Reader<{ translateMap: TranslateMap; getInstanceById: GetInstanceById<E> }, string> =>
nameR(...args).map(name => name ?? MISSING_VALUE)
/**
* Joins a list of strings according to the locales rules for the given type.
@@ -121,27 +218,50 @@ export const translateMapFnR: Reader<
export const localeJoinR = (
arr: string[],
type: LocaleJoinType,
): Reader<{ localeJoin: LocaleJoin }, string> =>
Reader.asks(env => env.localeJoin(arr, type))
): Reader<{ localeJoin: LocaleJoin }, string> => Reader.asks(env => env.localeJoin(arr, type))
/**
* Returns a function to retrieve an instance from the database by its entity name and ID.
*/
export const getInstanceByIdR = <E extends keyof EntityMap = never>(): Reader<
export const getInstanceByIdFnR = <E extends keyof EntityMap = never>(): Reader<
{ getInstanceById: GetInstanceById<E> },
GetInstanceById<E>
> => Reader.asks(env => env.getInstanceById)
/**
* Retrieves an instance from the database by its entity name and ID.
*/
export const getInstanceByIdR = <E extends keyof EntityMap>(
...args: IdArgsVariant<EntityMap, E>
): Reader<{ getInstanceById: GetInstanceById<E> }, EntityMap[E] | undefined> =>
Reader.asks(env => env.getInstanceById(...args))
/**
* Retrieves all instances of an entity from the database by their entity name.
*/
export const getAllInstancesR = <E extends keyof EntityMap>(
entityName: E,
): Reader<{ getAllInstances: GetAllInstances<E> }, { id: string; content: EntityMap[E] }[]> =>
Reader.asks(env => env.getAllInstances(entityName))
/**
* Retrieves all child instances of an entity from the database by their child entity name and their parents identifier.
*/
export const getChildInstancesForInstanceIdR = <CE extends keyof ChildEntityMap>(
entityName: CE,
parentId: ChildEntityMap[CE][2],
): Reader<
{ getChildInstancesForInstanceId: GetAllChildInstancesForParent<CE> },
{ id: string; content: ChildEntityMap[CE][0] }[]
> => Reader.asks(env => env.getChildInstancesForInstanceId(entityName, parentId))
/**
* Joins a list of strings according to the locales rules for the given type.
*/
export const responsiveLocaleJoinR = (
arr: string[],
type: LocaleJoinType,
): Reader<
{ localeJoin: LocaleJoin; responsiveTextSize: ResponsiveTextSize },
string
> =>
): Reader<{ localeJoin: LocaleJoin; responsiveTextSize: ResponsiveTextSize }, string> =>
Reader.asks(({ localeJoin, responsiveTextSize }) =>
responsive(
responsiveTextSize,
@@ -164,10 +284,17 @@ export const responsiveLocaleJoinR = (
/**
* Compares two strings according to the locales sorting rules.
*/
export const localeCompareR: Reader<
{ localeCompare: LocaleCompare },
LocaleCompare
> = Reader.asks(env => env.localeCompare)
export const localeCompareR: Reader<{ localeCompare: LocaleCompare }, LocaleCompare> = Reader.asks(
env => env.localeCompare,
)
/**
* Sorts an array of strings according to the locales sorting rules.
*/
export const localeSortR = <T extends string>(
arr: T[],
): Reader<{ localeCompare: LocaleCompare }, T[]> =>
Reader.asks(({ localeCompare }) => arr.toSorted(localeCompare))
/**
* Creates a responsive value from two functions that return the value for the full and compressed version, respectively.
@@ -176,9 +303,7 @@ export const responsiveR = <T>(
full: () => T,
compressed: () => T,
): Reader<{ responsiveTextSize: ResponsiveTextSize }, T> =>
Reader.asks(({ responsiveTextSize }) =>
responsive(responsiveTextSize, full, compressed),
)
Reader.asks(({ responsiveTextSize }) => responsive(responsiveTextSize, full, compressed))
/**
* Creates a responsive value from two functions that return the value for the full and compressed version, respectively.
@@ -208,10 +333,7 @@ export const responsiveTranslateR = <
fullKey: K,
compressedKey: K2,
...rest: TranslationParamsInArray<K> & TranslationParamsInArray<K2>
): Reader<
{ translate: Translate; responsiveTextSize: ResponsiveTextSize },
string
> =>
): Reader<{ translate: Translate; responsiveTextSize: ResponsiveTextSize }, string> =>
Reader.asks(({ translate, responsiveTextSize }) =>
responsive(
responsiveTextSize,
@@ -254,9 +376,7 @@ export const responsiveTextOptionalR = (
export const formatEnergyR = (
value: string | number,
): Reader<{ translate: Translate; energyUnit: EnergyUnit }, string> =>
Reader.asks(({ translate, energyUnit }) =>
formatEnergy(translate, energyUnit, value),
)
Reader.asks(({ translate, energyUnit }) => formatEnergy(translate, energyUnit, value))
/**
* Formats the given energy cost value with the appropriate unit based on the entity type.
@@ -330,19 +450,12 @@ export const modifyBySpeedR: Reader<
) => SpeedMap[S][K]
> = Reader.asks(
({ speed }) =>
<S extends Speed, K extends keyof SpeedMap[S]>(
key: K,
level: SkillModificationLevel,
) => {
<S extends Speed, K extends keyof SpeedMap[S]>(key: K, level: SkillModificationLevel) => {
switch (speed) {
case Speed.Fast:
return level.fast[
key as keyof FastSkillModificationLevelConfig
] as SpeedMap[S][K]
return level.fast[key as keyof FastSkillModificationLevelConfig] as SpeedMap[S][K]
case Speed.Slow:
return level.slow[
key as keyof SlowSkillModificationLevelConfig
] as SpeedMap[S][K]
return level.slow[key as keyof SlowSkillModificationLevelConfig] as SpeedMap[S][K]
default:
return assertExhaustive(speed)
}
File diff suppressed because it is too large Load Diff
+303 -544
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -1,6 +1,6 @@
import { Compare } from "@optolith/helpers/compare"
import type { LocaleMeasurementAdjustments } from "optolith-database-schema/gen"
import { Translate, TranslateMap } from "./translate.js"
import { Translate, TranslateMap, type Format } from "./translate.js"
/**
* The type of list to join in a locale-aware way.
@@ -12,6 +12,7 @@ export type LocaleJoinType = "conjunction" | "disjunction" | "unit"
*/
export type LocaleEnvironment = {
id: string
format: Format
translate: Translate
translateMap: TranslateMap
compare: LocaleCompare
+5
View File
@@ -7,6 +7,11 @@ import { ResponsiveTextSize } from "../entities/partial/responsiveText.js"
*/
export type Translations = NonNullable<Locale["translations"]>
/**
* A function that formats a string with the given arguments.
*/
export type Format = (text: string, args?: Record<string, unknown>) => string
/**
* Translates a given key into a string, optionally with parameters.
*/
+2 -1
View File
@@ -42,6 +42,7 @@ import { getOptionalRuleEntityDescription } from "./entities/optionalRule.js"
import type { GetResolvedSelectOptionById } from "./entities/partial/prerequisites/single/activatable.js"
import { getPersonalityTraitEntityDescription } from "./entities/personalityTrait.js"
import { getPoisonEntityDescription } from "./entities/poison.js"
import { getProfessionVersionEntityDescription } from "./entities/profession.js"
import { getRaceEntityDescription } from "./entities/race.js"
import { getSexPracticeEntityDescription } from "./entities/sexPractice.js"
import { getSkillEntityDescription } from "./entities/skill.js"
@@ -260,7 +261,7 @@ const registeredEntityDescriptionCreators = {
DerivedCharacteristic: getDerivedCharacteristicEntityDescription,
Race: getRaceEntityDescription,
Culture: getCultureEntityDescription,
// ProfessionVersion: getProfessionVersionEntityDescription,
ProfessionVersion: getProfessionVersionEntityDescription,
Advantage: getActivatableEntityDescription,
Disadvantage: getActivatableEntityDescription,
// core values
+31 -5
View File
@@ -19,7 +19,12 @@ describe("joinPrerequisiteParts", () => {
},
{ value: "C", sentenceType: undefined, isMeta: false },
]
const result = joinPrerequisiteParts(defaultLocaleEnvironment, parts)
const result = joinPrerequisiteParts(
defaultLocaleEnvironment.translate,
defaultLocaleEnvironment.translateMap,
defaultLocaleEnvironment.compare,
parts.map(part => ({ type: "test", part })),
)
assert.equal(result, "A, Label for B, C")
})
@@ -32,7 +37,12 @@ describe("joinPrerequisiteParts", () => {
{ value: "E", sentenceType: undefined, isMeta: false },
{ value: "F", sentenceType: Case("Connected"), isMeta: false },
]
const result = joinPrerequisiteParts(defaultLocaleEnvironment, parts)
const result = joinPrerequisiteParts(
defaultLocaleEnvironment.translate,
defaultLocaleEnvironment.translateMap,
defaultLocaleEnvironment.compare,
parts.map(part => ({ type: "test", part })),
)
assert.equal(result, "A; B; C; D, E; F")
})
@@ -49,7 +59,12 @@ describe("joinPrerequisiteParts", () => {
{ value: "I", sentenceType: undefined, isMeta: false },
{ value: "J", sentenceType: Case("Standalone"), isMeta: false },
]
const result = joinPrerequisiteParts(defaultLocaleEnvironment, parts)
const result = joinPrerequisiteParts(
defaultLocaleEnvironment.translate,
defaultLocaleEnvironment.translateMap,
defaultLocaleEnvironment.compare,
parts.map(part => ({ type: "test", part })),
)
assert.equal(result, "A. B. C. D, E. F. G. H, I. J.")
})
@@ -59,7 +74,12 @@ describe("joinPrerequisiteParts", () => {
{ value: "B", sentenceType: undefined, isMeta: true },
{ value: "C", sentenceType: undefined, isMeta: true },
]
const result = joinPrerequisiteParts(defaultLocaleEnvironment, parts)
const result = joinPrerequisiteParts(
defaultLocaleEnvironment.translate,
defaultLocaleEnvironment.translateMap,
defaultLocaleEnvironment.compare,
parts.map(part => ({ type: "test", part })),
)
assert.equal(result, "none, A, B, C")
})
@@ -68,7 +88,13 @@ describe("joinPrerequisiteParts", () => {
// @ts-expect-error Testing invalid input
{ value: "A", sentenceType: Case("any"), isMeta: true },
]
const block = () => joinPrerequisiteParts(defaultLocaleEnvironment, parts)
const block = () =>
joinPrerequisiteParts(
defaultLocaleEnvironment.translate,
defaultLocaleEnvironment.translateMap,
defaultLocaleEnvironment.compare,
parts.map(part => ({ type: "test", part })),
)
assert.throws(block)
})
})
+2 -1
View File
@@ -1,6 +1,6 @@
import { assertExhaustive } from "@elyukai/utils/typeSafety"
import { LocaleEnvironment } from "../../src/helpers/locale.js"
import { translateMapMock, translateMock } from "./translate.js"
import { formatMock, translateMapMock, translateMock } from "./translate.js"
const localeId = "en-US"
@@ -21,6 +21,7 @@ const unitListFormat = new Intl.ListFormat(localeId, {
*/
export const defaultLocaleEnvironment: LocaleEnvironment = {
id: "en-US",
format: formatMock,
translate: translateMock,
translateMap: translateMapMock,
compare: (x, y) => collator.compare(x, y),
+11 -1
View File
@@ -1,5 +1,15 @@
import { MessageFormat } from "messageformat"
import { Translate, TranslateMap } from "../../src/helpers/translate.js"
import {
Translate,
TranslateMap,
type Format,
} from "../../src/helpers/translate.js"
/**
* A mocked format function.
*/
export const formatMock: Format = (text, args) =>
new MessageFormat("en", text).format(args)
/**
* A mocked translate function.