@optolith/entity-descriptions 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/lib/creator.d.ts +2 -2
  3. package/lib/entities/activatable.js +35 -64
  4. package/lib/entities/culture.js +18 -26
  5. package/lib/entities/curriculum.js +30 -33
  6. package/lib/entities/derivedCharacteristic.js +1 -1
  7. package/lib/entities/disease.js +3 -6
  8. package/lib/entities/elixir.js +6 -4
  9. package/lib/entities/equipment.js +17 -17
  10. package/lib/entities/equipmentPackage.js +4 -3
  11. package/lib/entities/experienceLevel.js +7 -7
  12. package/lib/entities/focusRule.js +2 -2
  13. package/lib/entities/influence.js +2 -2
  14. package/lib/entities/liturgicalChant.d.ts +1 -1
  15. package/lib/entities/partial/adventurePointsValue.js +14 -19
  16. package/lib/entities/partial/enhancements.d.ts +1 -1
  17. package/lib/entities/partial/enhancements.js +1 -1
  18. package/lib/entities/partial/herbary.js +13 -9
  19. package/lib/entities/partial/map.d.ts +3 -1
  20. package/lib/entities/partial/map.js +21 -8
  21. package/lib/entities/partial/mathOperation.js +12 -17
  22. package/lib/entities/partial/prerequisites/displayOption.d.ts +2 -2
  23. package/lib/entities/partial/prerequisites/index.d.ts +3 -3
  24. package/lib/entities/partial/prerequisites/index.js +10 -4
  25. package/lib/entities/partial/prerequisites/part.d.ts +1 -1
  26. package/lib/entities/partial/prerequisites/prerequisiteGroups.d.ts +4 -4
  27. package/lib/entities/partial/prerequisites/prerequisiteGroups.js +1 -1
  28. package/lib/entities/partial/prerequisites/single/activatable.d.ts +3 -3
  29. package/lib/entities/partial/prerequisites/single/animistPower.d.ts +2 -2
  30. package/lib/entities/partial/prerequisites/single/animistPower.js +1 -1
  31. package/lib/entities/partial/prerequisites/single/blessedTradition.d.ts +2 -2
  32. package/lib/entities/partial/prerequisites/single/commonSuggestedByRCP.d.ts +2 -2
  33. package/lib/entities/partial/prerequisites/single/culture.d.ts +4 -4
  34. package/lib/entities/partial/prerequisites/single/enhancement.d.ts +4 -4
  35. package/lib/entities/partial/prerequisites/single/influence.d.ts +3 -3
  36. package/lib/entities/partial/prerequisites/single/magicalTradition.d.ts +2 -2
  37. package/lib/entities/partial/prerequisites/single/noOtherAncestorBloodAdvantage.d.ts +2 -2
  38. package/lib/entities/partial/prerequisites/single/pact.d.ts +3 -3
  39. package/lib/entities/partial/prerequisites/single/personalityTrait.d.ts +4 -4
  40. package/lib/entities/partial/prerequisites/single/primaryAttribute.d.ts +3 -3
  41. package/lib/entities/partial/prerequisites/single/primaryAttribute.js +1 -1
  42. package/lib/entities/partial/prerequisites/single/profession.d.ts +4 -4
  43. package/lib/entities/partial/prerequisites/single/publication.d.ts +4 -4
  44. package/lib/entities/partial/prerequisites/single/race.d.ts +4 -4
  45. package/lib/entities/partial/prerequisites/single/rated.d.ts +2 -2
  46. package/lib/entities/partial/prerequisites/single/rated.js +2 -2
  47. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.d.ts +3 -3
  48. package/lib/entities/partial/prerequisites/single/ratedMinimumNumber.js +1 -1
  49. package/lib/entities/partial/prerequisites/single/ratedSum.d.ts +4 -4
  50. package/lib/entities/partial/prerequisites/single/rule.d.ts +3 -3
  51. package/lib/entities/partial/prerequisites/single/sex.d.ts +1 -1
  52. package/lib/entities/partial/prerequisites/single/sexualCharacteristic.d.ts +3 -3
  53. package/lib/entities/partial/prerequisites/single/socialStatus.d.ts +3 -3
  54. package/lib/entities/partial/prerequisites/single/state.d.ts +4 -4
  55. package/lib/entities/partial/prerequisites/single/text.d.ts +3 -3
  56. package/lib/entities/partial/rated/activatable/castingTime.d.ts +1 -1
  57. package/lib/entities/partial/rated/activatable/castingTime.js +6 -8
  58. package/lib/entities/partial/rated/activatable/cost.d.ts +1 -1
  59. package/lib/entities/partial/rated/activatable/cost.js +17 -14
  60. package/lib/entities/partial/rated/activatable/duration.js +4 -4
  61. package/lib/entities/partial/rated/activatable/effect.js +1 -1
  62. package/lib/entities/partial/rated/activatable/isMinimumMaximum.js +2 -2
  63. package/lib/entities/partial/rated/activatable/range.js +6 -6
  64. package/lib/entities/partial/rated/activatable/speed.d.ts +1 -1
  65. package/lib/entities/partial/rated/activatable/targetCategory.js +2 -2
  66. package/lib/entities/partial/reader.d.ts +4 -2
  67. package/lib/entities/partial/responsiveText.d.ts +5 -1
  68. package/lib/entities/partial/responsiveText.js +3 -5
  69. package/lib/entities/partial/units/length.d.ts +1 -1
  70. package/lib/entities/partial/units/length.js +2 -10
  71. package/lib/entities/partial/units/timeSpan.d.ts +4 -4
  72. package/lib/entities/partial/units/timeSpan.js +21 -17
  73. package/lib/entities/personalityTrait.js +3 -4
  74. package/lib/entities/poison.js +4 -4
  75. package/lib/entities/profession.d.ts +1 -1
  76. package/lib/entities/profession.js +19 -14
  77. package/lib/entities/race.js +11 -13
  78. package/lib/entities/sexPractice.js +1 -3
  79. package/lib/entities/skill.js +2 -3
  80. package/lib/entities/spell.js +14 -4
  81. package/lib/helpers/getTypes.d.ts +2 -2
  82. package/lib/helpers/locale.d.ts +7 -2
  83. package/lib/helpers/translate.d.ts +1 -1
  84. package/lib/index.d.ts +3 -1
  85. package/lib/index.js +1 -0
  86. package/lib/references/index.d.ts +1 -1
  87. package/lib/references/index.js +1 -1
  88. package/lib/references/page.d.ts +3 -3
  89. package/lib/references/page.js +1 -1
  90. package/lib/references/pageRange.d.ts +2 -2
  91. package/lib/tsconfig.tsbuildinfo +1 -1
  92. package/package.json +4 -3
@@ -2,24 +2,24 @@ import { Reader } from "@elyukai/utils/reader";
2
2
  import { mapNullable } from "@optolith/helpers/nullable";
3
3
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
4
4
  import { getInstanceByIdFnR, modifiableBySpeedOptionalR, modifiableBySpeedR, translateFnR, translateMapR, translateR, } from "../../reader.js";
5
- import { appendNoteIfNeeded, replaceTextIfNeeded, } from "../../responsiveText.js";
5
+ import { appendNoteIfNeeded, replaceTextIfNeeded } from "../../responsiveText.js";
6
6
  import { formatCombinedLengthR, formatLengthR } from "../../units/length.js";
7
7
  import { MISSING_VALUE } from "../../unknown.js";
8
8
  import { renderCheckResultBasedValue } from "./checkResultBased.js";
9
9
  import { wrapIfMaximum } from "./isMinimumMaximum.js";
10
- import { appendNonModifiableSuffix, ModifiableParameter, } from "./nonModifiableSuffix.js";
10
+ import { appendNonModifiableSuffix, ModifiableParameter } from "./nonModifiableSuffix.js";
11
11
  const deriveModifiableRange = (modificationLevelId) => getInstanceByIdFnR().thenW(getInstanceById => mapNullable(getInstanceById("SkillModificationLevel", modificationLevelId), modificationLevel => modifiableBySpeedR("range", modificationLevel).thenW(value => translateMapR(modificationLevel.translations).thenW(translation => translation === undefined
12
12
  ? Reader.of({ value, translation: undefined })
13
13
  : modifiableBySpeedOptionalR("range", translation).map(valueTranslation => ({
14
14
  value,
15
15
  translation: valueTranslation,
16
16
  }))))) ?? Reader.of(undefined));
17
- const wrapIfRadius = (is_radius, text) => translateFnR.map(translate => is_radius === true ? `${text} ${translate("Radius")}` : text);
17
+ const wrapIfRadius = (is_radius, text) => translateFnR.map(translate => (is_radius === true ? `${text} ${translate("Radius")}` : text));
18
18
  const renderModifiableRange = (value) => deriveModifiableRange(value.initial_modification_level)
19
19
  .thenW(range => range === undefined
20
20
  ? Reader.of(MISSING_VALUE)
21
- : typeof range === "string"
22
- ? Reader.of(range)
21
+ : range.translation !== undefined
22
+ ? Reader.of(range.translation)
23
23
  : formatLengthR("Steps", range.value))
24
24
  .then(text => wrapIfRadius(value.is_radius, text))
25
25
  .then(text => wrapIfMaximum(value.is_maximum, text));
@@ -36,7 +36,7 @@ const getCheckResultBasedRangeTranslation = (value) => renderCheckResultBasedVal
36
36
  export const renderNonModifiableRange = (value, shouldAppendNonModifiableSuffix) => {
37
37
  const appendNonModifiableSuffixIfNeeded = shouldAppendNonModifiableSuffix
38
38
  ? (text) => appendNonModifiableSuffix(ModifiableParameter.Range, text)
39
- : Reader.of;
39
+ : (text) => Reader.of(text);
40
40
  switch (value.kind) {
41
41
  case "Sight":
42
42
  return translateR("Sight");
@@ -1,4 +1,4 @@
1
- import { FastSkillModificationLevelConfig, SkillModificationLevel, SlowSkillModificationLevelConfig } from "optolith-database-schema/gen";
1
+ import type { FastSkillModificationLevelConfig, SkillModificationLevel, SlowSkillModificationLevelConfig } from "optolith-database-schema/gen";
2
2
  /**
3
3
  * The speed of an activatable skill.
4
4
  */
@@ -1,7 +1,7 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
2
  import { mapNullable } from "@optolith/helpers/nullable";
3
3
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
4
- import { getInstanceByIdFnR, translateMapR, translateR, } from "../../reader.js";
4
+ import { getInstanceByIdFnR, translateMapR, translateR } from "../../reader.js";
5
5
  import { MISSING_VALUE } from "../../unknown.js";
6
6
  import { appendInParensIfNotEmpty } from "./parensIf.js";
7
7
  const renderPredefined = (targetCategoryId) => getInstanceByIdFnR()
@@ -32,4 +32,4 @@ export const renderTargetCategory = (values) => translateR("Target Category").th
32
32
  ? translateR("all")
33
33
  : Reader.sequence(values.map(({ id, translations }) => getTargetCategoryTranslationByType(id).then(text => translateMapR(translations)
34
34
  .map(translation => translation?.note)
35
- .map(note => appendInParensIfNotEmpty(note, text)) ?? text))).map(texts => texts.join(", "))).map(value => ({ label, value })));
35
+ .map(note => appendInParensIfNotEmpty(note, text))))).map(texts => texts.join(", "))).map(value => ({ label, value })));
@@ -2,7 +2,7 @@ import { Reader } from "@elyukai/utils/reader";
2
2
  import type { ChildEntityMap, EntityMap, FastSkillModificationLevelConfig, ResponsiveText, ResponsiveTextOptional, SkillModificationLevel, SlowSkillModificationLevelConfig } from "optolith-database-schema/gen";
3
3
  import type { IdArgsVariant } from "tsondb/schema/gen";
4
4
  import type { GetAllChildInstancesForParent, GetAllInstances, GetInstanceById } from "../../helpers/getTypes.js";
5
- import type { LocaleCompare, LocaleJoin, LocaleJoinType } from "../../helpers/locale.js";
5
+ import type { FormatNumber, LocaleCompare, LocaleJoin, LocaleJoinType } from "../../helpers/locale.js";
6
6
  import type { Format, LocaleMap, Translate, TranslateMap, TranslationKeyMatchingParamsOfKey, TranslationKeysWithoutParams, TranslationParamsInArray, Translations } from "../../helpers/translate.js";
7
7
  import type { GetResolvedSelectOptionById } from "./prerequisites/single/activatable.js";
8
8
  import type { ModifiableParameter } from "./rated/activatable/nonModifiableSuffix.js";
@@ -14,6 +14,7 @@ import { type EnergyUnit } from "./units/energy.js";
14
14
  */
15
15
  export type EnvMap<E extends keyof EntityMap = never, AE extends keyof EntityMap = never, CE extends keyof ChildEntityMap = never> = {
16
16
  format: Format;
17
+ formatNumber: FormatNumber;
17
18
  translate: Translate;
18
19
  translateMap: TranslateMap;
19
20
  localeJoin: LocaleJoin;
@@ -32,6 +33,7 @@ export type EnvMap<E extends keyof EntityMap = never, AE extends keyof EntityMap
32
33
  */
33
34
  export type EnvMapAbbr = {
34
35
  f: "format";
36
+ fn: "formatNumber";
35
37
  t: "translate";
36
38
  tm: "translateMap";
37
39
  lj: "localeJoin";
@@ -60,7 +62,7 @@ export type StdReader<T, K extends keyof EnvMapAbbr, E extends keyof EntityMap =
60
62
  /**
61
63
  * Treats the text as a translation string and applies supplied arguments.
62
64
  */
63
- export declare const formatR: (text: string, args?: Record<string, unknown> | undefined) => Reader<{
65
+ export declare const formatR: (text: string, args?: Record<string, unknown>) => Reader<{
64
66
  format: Format;
65
67
  }, string>;
66
68
  /**
@@ -29,7 +29,11 @@ export declare const getResponsiveTextOptional: (value: ResponsiveTextOptional |
29
29
  */
30
30
  export declare const replaceTextIfNeeded: (translations: LocaleMap<{
31
31
  replacement?: ResponsiveTextReplace;
32
- }> | undefined, valueToReplace: string) => Reader<StdEnv<"rts" | "tm">, string>;
32
+ }> | undefined, valueToReplace: string, differentValueToInsert?: string) => Reader<{
33
+ translateMap: import("../../helpers/translate.js").TranslateMap;
34
+ } & {
35
+ responsiveTextSize: ResponsiveTextSize;
36
+ }, string>;
33
37
  /**
34
38
  * Appends a note to a given value if a note is requested, otherwise just return
35
39
  * the given value.
@@ -2,7 +2,7 @@ import { identity } from "@elyukai/utils/function";
2
2
  import { Reader } from "@elyukai/utils/reader";
3
3
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
4
4
  import { appendInParensIfNotEmpty } from "./rated/activatable/parensIf.js";
5
- import { responsiveTextOptionalR, responsiveTextR, translateMapR, } from "./reader.js";
5
+ import { responsiveTextOptionalR, responsiveTextR, translateMapR } from "./reader.js";
6
6
  import { MISSING_VALUE } from "./unknown.js";
7
7
  /**
8
8
  * Whether the entry is displayed in a normal or compressed setting. Normal/full
@@ -49,13 +49,11 @@ export const getResponsiveTextOptional = (value, size) => {
49
49
  * Replaces a text with a given value if a replacement is requested, otherwise
50
50
  * just return the given value.
51
51
  */
52
- export const replaceTextIfNeeded = (translations, valueToReplace) => translateMapR(translations)
53
- .with(identity)
54
- .then(translation => {
52
+ export const replaceTextIfNeeded = (translations, valueToReplace, differentValueToInsert = valueToReplace) => translateMapR(translations).thenW(translation => {
55
53
  if (translation?.replacement === undefined) {
56
54
  return Reader.of(valueToReplace);
57
55
  }
58
- return responsiveTextR(translation.replacement).map(note => note.replace("$1", valueToReplace));
56
+ return responsiveTextR(translation.replacement).map(note => note.replace("$1", differentValueToInsert));
59
57
  });
60
58
  /**
61
59
  * Appends a note to a given value if a note is requested, otherwise just return
@@ -1,6 +1,6 @@
1
1
  import type { Translate } from "../../../helpers/translate.js";
2
2
  import type { StdReader } from "../reader.js";
3
- import { ResponsiveTextSize } from "../responsiveText.js";
3
+ import { type ResponsiveTextSize } from "../responsiveText.js";
4
4
  type LengthUnit = "Steps" | "Miles";
5
5
  /**
6
6
  * Returns the text for a length unit.
@@ -1,16 +1,8 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
2
  import { responsive } from "../responsiveText.js";
3
3
  const lengthUnitTranslationKeys = {
4
- Steps: [
5
- ".input {$value :number} {{{$value} yards}}",
6
- "{$value} yards",
7
- "{$value} yd",
8
- ],
9
- Miles: [
10
- ".input {$value :number} {{{$value} miles}}",
11
- "{$value} miles",
12
- "{$value} mi.",
13
- ],
4
+ Steps: [".input {$value :number} {{{$value} yards}}", "{$value} yards", "{$value} yd"],
5
+ Miles: [".input {$value :number} {{{$value} miles}}", "{$value} miles", "{$value} mi."],
14
6
  };
15
7
  /**
16
8
  * Returns the text for a length unit.
@@ -1,7 +1,7 @@
1
1
  import { Reader } from "@elyukai/utils/reader";
2
2
  import type { Translate } from "../../../helpers/translate.js";
3
3
  import type { StdEnv } from "../reader.js";
4
- import { ResponsiveTextSize } from "../responsiveText.js";
4
+ import { type ResponsiveTextSize } from "../responsiveText.js";
5
5
  /**
6
6
  * Possible units to use for time spans.
7
7
  */
@@ -11,13 +11,13 @@ export type TimeSpanUnit = "Seconds" | "Minutes" | "Hours" | "Days" | "Weeks" |
11
11
  */
12
12
  export declare const formatTimeSpan: (translate: Translate, responsiveTextSize: ResponsiveTextSize, unit: {
13
13
  kind: TimeSpanUnit;
14
- } | TimeSpanUnit, value: number | string) => string;
14
+ } | TimeSpanUnit, value: number | string, interval?: boolean) => string;
15
15
  /**
16
16
  * Returns the text for a time span unit.
17
17
  */
18
18
  export declare const formatTimeSpanR: (unit: {
19
19
  kind: TimeSpanUnit;
20
- } | TimeSpanUnit, value: number | string) => Reader<StdEnv<"t" | "rts">, string>;
20
+ } | TimeSpanUnit, value: number | string, interval?: boolean) => Reader<StdEnv<"t" | "rts">, string>;
21
21
  /**
22
22
  * Returns the text for a time span unit.
23
23
  */
@@ -26,4 +26,4 @@ export declare const formatCombinedTimeSpanR: (object: {
26
26
  kind: TimeSpanUnit;
27
27
  };
28
28
  value: number | string;
29
- }) => Reader<StdEnv<"t" | "rts">, string>;
29
+ }, interval?: boolean) => Reader<StdEnv<"t" | "rts">, string>;
@@ -2,33 +2,37 @@ import { Reader } from "@elyukai/utils/reader";
2
2
  import { responsive } from "../responsiveText.js";
3
3
  // prettier-ignore
4
4
  const timeSpanUnitTranslationKeys = {
5
- Seconds: [".input {$value :number} {{{$value} seconds}}", "{$value} seconds", "{$value} s"],
6
- Minutes: [".input {$value :number} {{{$value} minutes}}", "{$value} minutes", "{$value} min"],
7
- Hours: [".input {$value :number} {{{$value} hours}}", "{$value} hours", "{$value} h"],
8
- Days: [".input {$value :number} {{{$value} days}}", "{$value} days", "{$value} d"],
9
- Weeks: [".input {$value :number} {{{$value} weeks}}", "{$value} weeks", ".input {$value :number} {{{$value} wks.}}"],
10
- Months: [".input {$value :number} {{{$value} months}}", "{$value} months", ".input {$value :number} {{{$value} mos.}}"],
11
- Years: [".input {$value :number} {{{$value} years}}", "{$value} years", ".input {$value :number} {{{$value} yrs.}}"],
12
- Centuries: [".input {$value :number} {{{$value} centuries}}", "{$value} centuries", "{$value} cent."],
13
- Actions: [".input {$value :number} {{{$value} actions}}", "{$value} actions", "{$value} act"],
14
- CombatRounds: [".input {$value :number} {{{$value} combat rounds}}", "{$value} combat rounds", "{$value} CR"],
15
- SeductionActions: [".input {$value :number} {{{$value} seduction actions}}", "{$value} seduction actions", "{$value} SA"],
16
- Rounds: [".input {$value :number} {{{$value} rounds}}", "{$value} rounds", "{$value} rnds"],
5
+ Seconds: [".input {$value :number} .input {$style :string} {{{$value} seconds}}", "{$value} seconds", ".input {$value :number} .input {$style :string} {{{$value} s}}", "{$value} s"],
6
+ Minutes: [".input {$value :number} .input {$style :string} {{{$value} minutes}}", "{$value} minutes", ".input {$value :number} .input {$style :string} {{{$value} min}}", "{$value} min"],
7
+ Hours: [".input {$value :number} .input {$style :string} {{{$value} hours}}", "{$value} hours", ".input {$value :number} .input {$style :string} {{{$value} h}}", "{$value} h"],
8
+ Days: [".input {$value :number} .input {$style :string} {{{$value} days}}", "{$value} days", ".input {$value :number} .input {$style :string} {{{$value} d}}", "{$value} d"],
9
+ Weeks: [".input {$value :number} .input {$style :string} {{{$value} weeks}}", "{$value} weeks", ".input {$value :number} .input {$style :string} {{{$value} wks.}}", "{$value} wks."],
10
+ Months: [".input {$value :number} .input {$style :string} {{{$value} months}}", "{$value} months", ".input {$value :number} .input {$style :string} {{{$value} mos.}}", "{$value} mos."],
11
+ Years: [".input {$value :number} .input {$style :string} {{{$value} years}}", "{$value} years", ".input {$value :number} .input {$style :string} {{{$value} yrs.}}", "{$value} yrs."],
12
+ Centuries: [".input {$value :number} .input {$style :string} {{{$value} centuries}}", "{$value} centuries", ".input {$value :number} .input {$style :string} {{{$value} cent.}}", "{$value} cent."],
13
+ Actions: [".input {$value :number} .input {$style :string} {{{$value} actions}}", "{$value} actions", ".input {$value :number} .input {$style :string} {{{$value} act}}", "{$value} act"],
14
+ CombatRounds: [".input {$value :number} .input {$style :string} {{{$value} combat rounds}}", "{$value} combat rounds", ".input {$value :number} .input {$style :string} {{{$value} CR}}", "{$value} CR"],
15
+ SeductionActions: [".input {$value :number} .input {$style :string} {{{$value} seduction actions}}", "{$value} seduction actions", ".input {$value :number} .input {$style :string} {{{$value} SA}}", "{$value} SA"],
16
+ Rounds: [".input {$value :number} .input {$style :string} {{{$value} rounds}}", "{$value} rounds", ".input {$value :number} .input {$style :string} {{{$value} rnds}}", "{$value} rnds"],
17
17
  };
18
18
  /**
19
19
  * Returns the text for a time span unit.
20
20
  */
21
- export const formatTimeSpan = (translate, responsiveTextSize, unit, value) => {
22
- const [fullNumberKey, fullKey, compressedKey] = timeSpanUnitTranslationKeys[typeof unit === "string" ? unit : unit.kind];
21
+ export const formatTimeSpan = (translate, responsiveTextSize, unit, value, interval = false) => {
22
+ const [fullNumberKey, fullKey, compressedNumberKey, compressedKey] = timeSpanUnitTranslationKeys[typeof unit === "string" ? unit : unit.kind];
23
23
  return responsive(responsiveTextSize, () => translate(typeof value === "number" ? fullNumberKey : fullKey, {
24
24
  value,
25
- }), () => translate(compressedKey, { value }));
25
+ style: interval ? "interval" : "default",
26
+ }), () => translate(typeof value === "number" ? compressedNumberKey : compressedKey, {
27
+ value,
28
+ style: interval ? "interval" : "default",
29
+ }));
26
30
  };
27
31
  /**
28
32
  * Returns the text for a time span unit.
29
33
  */
30
- export const formatTimeSpanR = (unit, value) => Reader.asks(({ translate, responsiveTextSize }) => formatTimeSpan(translate, responsiveTextSize, unit, value));
34
+ export const formatTimeSpanR = (unit, value, interval) => Reader.asks(({ translate, responsiveTextSize }) => formatTimeSpan(translate, responsiveTextSize, unit, value, interval));
31
35
  /**
32
36
  * Returns the text for a time span unit.
33
37
  */
34
- export const formatCombinedTimeSpanR = (object) => formatTimeSpanR(object.unit, object.value);
38
+ export const formatCombinedTimeSpanR = (object, interval) => formatTimeSpanR(object.unit, object.value, interval);
@@ -23,10 +23,10 @@ export const getPersonalityTraitEntityDescription = createEntityDescriptionCreat
23
23
  {
24
24
  type: "definitionList",
25
25
  items: [
26
- ...(translation.effects.map(effect => ({
26
+ ...translation.effects.map(effect => ({
27
27
  label: effect.label,
28
28
  value: effect.text,
29
- })) ?? []),
29
+ })),
30
30
  entry.combination_options === undefined
31
31
  ? undefined
32
32
  : {
@@ -37,8 +37,7 @@ export const getPersonalityTraitEntityDescription = createEntityDescriptionCreat
37
37
  .toSorted(on(group => group[0], compareNullish(numAsc)))
38
38
  .map(([level, options]) => level === null
39
39
  ? MISSING_VALUE
40
- : `${translate("Level {$level}", { level: romanize(level) })} ${localeJoin(options.map(option => translateMap(option?.translations)?.name ??
41
- MISSING_VALUE), "disjunction")}`)
40
+ : `${translate("Level {$level}", { level: romanize(level) })} ${localeJoin(options.map(option => translateMap(option?.translations)?.name ?? MISSING_VALUE), "disjunction")}`)
42
41
  .join(", "),
43
42
  },
44
43
  entry.prerequisites === undefined
@@ -37,7 +37,7 @@ const renderLevel = (translate, translateMap, level) => {
37
37
  case "Constant":
38
38
  return typeof level.Constant === "number" ? level.Constant : level.Constant.value;
39
39
  case "BySubtype":
40
- return level.BySubtype.map(subtype => `${subtype.value} (${translateMap(subtype.translations)?.name ?? MISSING_VALUE})`).join(", ");
40
+ return level.BySubtype.map(subtype => `${subtype.value.toFixed()} (${translateMap(subtype.translations)?.name ?? MISSING_VALUE})`).join(", ");
41
41
  default:
42
42
  return assertExhaustive(level);
43
43
  }
@@ -82,7 +82,7 @@ const renderIntoxicantValues = (translate, translateMap, getInstanceById, intoxi
82
82
  overdose: translation?.overdose ?? MISSING_VALUE,
83
83
  legality: intoxicant.legality.is_legal ? translate("legal") : translate("illegal"),
84
84
  special: translation?.special,
85
- addiction: intoxicant?.addiction === undefined
85
+ addiction: intoxicant.addiction === undefined
86
86
  ? undefined
87
87
  : renderAddiction(translate, translateMap, getInstanceById, intoxicant.addiction),
88
88
  };
@@ -178,7 +178,7 @@ const renderDuration = (translate, translateMap, duration) => {
178
178
  return renderMathOperation(duration.ExpressionBased.value, value => {
179
179
  switch (value.kind) {
180
180
  case "Constant":
181
- return value.Constant.toString();
181
+ return value.Constant.toFixed();
182
182
  case "Dice":
183
183
  return renderDice(translate, value.Dice);
184
184
  case "CircleOfDamnation":
@@ -247,7 +247,7 @@ export const getPoisonEntityDescription = createEntityDescriptionCreator(({ getI
247
247
  renderAlternativeNames(translate, translation.alternative_names),
248
248
  {
249
249
  label: translate("Level"),
250
- value: level.toString(),
250
+ value: typeof level === "number" ? level.toFixed() : level,
251
251
  },
252
252
  {
253
253
  label: translate("Type"),
@@ -1,4 +1,4 @@
1
- import { type MagicalActionIdentifier, type RatedIdentifier, type SpecialAbilityIdentifier } from "optolith-database-schema/gen";
1
+ import type { MagicalActionIdentifier, RatedIdentifier, SpecialAbilityIdentifier } from "optolith-database-schema/gen";
2
2
  import type { GetAllChildInstancesForParent, GetAllInstances, GetInstanceById } from "../helpers/getTypes.js";
3
3
  import { type GetResolvedSelectOptionById } from "./partial/prerequisites/single/activatable.js";
4
4
  /**
@@ -24,6 +24,7 @@ const renderNumericListAcrossPackages = (packages, selector, equalityFn, renderT
24
24
  .reduce((accR, pkg, pkgIndex) => accR.map2(selector(pkg), (acc, selected) => selected.reduce((filledAcc, [values, number]) => {
25
25
  const existing = filledAcc.findIndex(([value]) => equalityFn(value, values));
26
26
  if (existing >= 0) {
27
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- existing is checked to be >= 0
27
28
  filledAcc[existing][1][pkgIndex] = number + defaultValue;
28
29
  return filledAcc;
29
30
  }
@@ -32,9 +33,7 @@ const renderNumericListAcrossPackages = (packages, selector, equalityFn, renderT
32
33
  ...filledAcc,
33
34
  [
34
35
  values,
35
- Array(pkgIndex)
36
- .fill(defaultValue)
37
- .concat(number + defaultValue),
36
+ Array.from({ length: pkgIndex }, () => defaultValue).concat(number + defaultValue),
38
37
  ],
39
38
  ];
40
39
  }
@@ -117,12 +116,11 @@ const getTotalingAPValues = (professionPackages, selector) => {
117
116
  return undefined;
118
117
  }
119
118
  return allSame(options, deepEqual)
120
- ? options[0].ap_value
119
+ ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- not all are nullish and because all are the same, none is nullish
120
+ options[0].ap_value
121
121
  : options.map(option => option?.ap_value ?? 0).join("/");
122
122
  };
123
- const renderSkillsOption = (professionPackages, skillGroup) => mapNullable(getTotalingAPValues(professionPackages, pkg => pkg.content.options?.skills?.group === skillGroup.id
124
- ? pkg.content.options?.skills
125
- : undefined), apValue => translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
123
+ const renderSkillsOption = (professionPackages, skillGroup) => mapNullable(getTotalingAPValues(professionPackages, pkg => pkg.content.options?.skills?.group === skillGroup.id ? pkg.content.options.skills : undefined), apValue => translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
126
124
  apValue,
127
125
  skillsOfGroup: skillGroup.nameOfSkillsOfGroup,
128
126
  })) ?? Reader.of(undefined);
@@ -171,6 +169,7 @@ const renderCantripsOption = (professionPackages) => {
171
169
  return Reader.of(undefined);
172
170
  }
173
171
  if (allSame(cantripsOptions, deepEqual)) {
172
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- not all are nullish and because all are the same, none is nullish
174
173
  return renderSingleCantripsOption(cantripsOptions[0]);
175
174
  }
176
175
  else {
@@ -245,7 +244,7 @@ const renderVariantSkillsOption = (baseOptions, variantOptions) => renderVariant
245
244
  apValue: override.ap_value,
246
245
  skillsOfGroup: nameOfSkillsOfGroup,
247
246
  })),
248
- update: (base, override) => plainOrInsteadOfR(base, override, b => b.ap_value, equal, Reader.of).thenW(apValue => plainOrInsteadOfR(base, override, b => b.group, equal, nameOfSkillsOfGroupR).thenW(nameOfSkillsOfGroup => translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
247
+ update: (base, override) => plainOrInsteadOfR(base, override, b => b.ap_value, equal, b => Reader.of(b)).thenW(apValue => plainOrInsteadOfR(base, override, b => b.group, equal, nameOfSkillsOfGroupR).thenW(nameOfSkillsOfGroup => translateR("{$apValue} AP to improve other {$skillsOfGroup}", {
249
248
  apValue,
250
249
  skillsOfGroup: nameOfSkillsOfGroup,
251
250
  }))),
@@ -389,10 +388,11 @@ const renderBlessings = (professionPackages) => {
389
388
  return Reader.of(undefined);
390
389
  }
391
390
  else if (allSame(traditions, deepEqual)) {
391
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- not all are empty and because all are the same, none is empty
392
392
  return renderSingleBlessings(traditions[0]);
393
393
  }
394
394
  else {
395
- return Reader.traverse(traditions, option => option === undefined ? Reader.of("—") : renderSingleBlessings(option)).map(list => list.join(" / "));
395
+ return Reader.traverse(traditions, option => option.length === 0 ? Reader.of("—") : renderSingleBlessings(option)).map(list => list.join(" / "));
396
396
  }
397
397
  };
398
398
  const renderLiturgicalChantName = (liturgyIds) => Reader.traverse(liturgyIds, strictNameR).thenW(list => localeJoinR(list, "disjunction"));
@@ -404,7 +404,7 @@ const renderLiturgicalChants = (professionPackages) => Reader.sequence([
404
404
  const renderRatedVariantChanges = (baseList, variantList, renderInstance) => Reader.traverse(variantList ?? [], ({ id, rating_modifier }) => {
405
405
  const baseValue = baseList?.find(item => deepEqual(item.id, id))?.rating_modifier ?? 0;
406
406
  return renderInstance(id).thenW(name => translateR("{$replacement} instead of {$base}", {
407
- replacement: `${name} ${baseValue + rating_modifier}`,
407
+ replacement: `${name} ${(baseValue + rating_modifier).toFixed()}`,
408
408
  base: baseValue,
409
409
  }));
410
410
  }).thenW(localeSortR);
@@ -495,9 +495,14 @@ export const getProfessionVersionEntityDescription = createEntityDescriptionCrea
495
495
  }
496
496
  return {
497
497
  title: translation.name.default +
498
- (professionPackages.length > 1
499
- ? ` (${professionPackages.map(pkg => translateMap(pkg.experienceLevel.translations)?.name).join("/")})`
500
- : ""),
498
+ parensIf(ensureNonEmpty([
499
+ translation.specification?.default,
500
+ professionPackages.length > 1
501
+ ? professionPackages
502
+ .map(pkg => translateMap(pkg.experienceLevel.translations)?.name)
503
+ .join("/")
504
+ : undefined,
505
+ ].filter(isNotNullish))?.join(", ")),
501
506
  className: "profession",
502
507
  body: [
503
508
  {
@@ -529,7 +534,7 @@ export const getProfessionVersionEntityDescription = createEntityDescriptionCrea
529
534
  curses: pkg.content.options?.curses,
530
535
  list: pkg.content.special_abilities,
531
536
  }), deepEqual, specialAbilityLists => Reader.of(specialAbilityLists
532
- .map(specialAbilities => specialAbilities === undefined
537
+ .map(specialAbilities => Object.values(specialAbilities).every(list => list === undefined)
533
538
  ? translate("none")
534
539
  : renderSpecialAbilities(specialAbilities).run(env))
535
540
  .join(" / "))).run(env),
@@ -10,11 +10,10 @@ import { renderCommonnessRatedAdvantagesAndDisadvantages, renderCommonnessRatedA
10
10
  import { MISSING_VALUE } from "./partial/unknown.js";
11
11
  const renderBaseValueItem = (label, value) => Reader.asks(({ translate }) => ({
12
12
  label: translate(label),
13
- value: value < 0 ? sign(value) : value.toString(),
13
+ value: value < 0 ? sign(value) : value.toFixed(),
14
14
  }));
15
15
  const renderAttributeAdjustmentsItem = (totalAttributesCount, adjustments) => Reader.asks(({ translate, translateMap, localeJoin, getInstanceById, }) => {
16
- const getAttributeAbbreviation = (id) => translateMap(getInstanceById("Attribute", id)?.translations)
17
- ?.abbreviation ?? MISSING_VALUE;
16
+ const getAttributeAbbreviation = (id) => translateMap(getInstanceById("Attribute", id)?.translations)?.abbreviation ?? MISSING_VALUE;
18
17
  return {
19
18
  label: translate("Attribute Adjustments"),
20
19
  value: [
@@ -35,7 +34,9 @@ const renderVariantValues = (label, variants, selector, renderValue, translation
35
34
  };
36
35
  });
37
36
  const sameValues = anySameIndices(values, on(value => [value.value, value.valueTranslation], deepEqual));
38
- if (sameValues.length === 1 && sameValues[0].length === values.length) {
37
+ if (isNotEmpty(sameValues) &&
38
+ sameValues.length === 1 &&
39
+ sameValues[0].length === values.length) {
39
40
  return {
40
41
  label: translate(label),
41
42
  value: values[0] === undefined
@@ -50,9 +51,7 @@ const renderVariantValues = (label, variants, selector, renderValue, translation
50
51
  {
51
52
  type: "definitionList",
52
53
  style: "nested",
53
- items: Map.groupBy(values
54
- .toSorted(on(item => item.name, localeCompare))
55
- .map(value => ({
54
+ items: Map.groupBy(values.toSorted(on(item => item.name, localeCompare)).map(value => ({
56
55
  label: value.name,
57
56
  value: value.valueTranslation ?? renderValue(value.value),
58
57
  })), item => item.value)
@@ -72,17 +71,14 @@ const renderAutomaticAdvantagesOrDisadvantages = (entity, items) => Reader.asks(
72
71
  : items
73
72
  .map(item => {
74
73
  const instanceTranslation = translateMap(getInstanceById(entity, item.id)?.translations);
75
- return (instanceTranslation?.name_in_library ??
76
- instanceTranslation?.name ??
77
- MISSING_VALUE);
74
+ return (instanceTranslation?.name_in_library ?? instanceTranslation?.name ?? MISSING_VALUE);
78
75
  })
79
76
  .toSorted(localeCompare)
80
77
  .join(", "));
81
78
  const renderCommonCultures = (items) => Reader.asks(({ translate, translateMap, getInstanceById, localeCompare }) => items === undefined || !isNotEmpty(items)
82
79
  ? translate("none")
83
80
  : items
84
- .map(itemId => translateMap(getInstanceById("Culture", itemId)?.translations)
85
- ?.name)
81
+ .map(itemId => translateMap(getInstanceById("Culture", itemId)?.translations)?.name)
86
82
  .filter(isNotNullish)
87
83
  .toSorted(localeCompare)
88
84
  .join(", "));
@@ -112,7 +108,9 @@ export const getRaceEntityDescription = createEntityDescriptionCreator(({ getIns
112
108
  items: [
113
109
  {
114
110
  label: translate("AP Value"),
115
- value: translate(".input {$value :number} {{{$value} Adventure Points}}", { value: entry.ap_value }),
111
+ value: translate(".input {$value :number} {{{$value} Adventure Points}}", {
112
+ value: entry.ap_value,
113
+ }),
116
114
  },
117
115
  renderBaseValueItem("Life Point Base Value", entry.base_values.life_points).run(env),
118
116
  renderBaseValueItem("Spirit Base Value", entry.base_values.spirit).run(env),
@@ -22,9 +22,7 @@ export const getSexPracticeEntityDescription = createEntityDescriptionCreator((_
22
22
  label: translate("Prerequisites"),
23
23
  value: translation.prerequisites,
24
24
  },
25
- translation.failed === undefined
26
- ? undefined
27
- : { label: translate("Failed"), value: translation.failed },
25
+ { label: translate("Failed"), value: translation.failed },
28
26
  ],
29
27
  },
30
28
  ],
@@ -109,10 +109,9 @@ export const getSkillEntityDescription = createEntityDescriptionCreator(({ getIn
109
109
  ? translate("Yes")
110
110
  : entry.encumbrance.kind === "No"
111
111
  ? translate("No")
112
- : (translation.encumbrance_description ??
113
- translate("Maybe")),
112
+ : (translation.encumbrance_description ?? translate("Maybe")),
114
113
  },
115
- translation?.tools === undefined
114
+ translation.tools === undefined
116
115
  ? undefined
117
116
  : {
118
117
  label: translate("Tools"),
@@ -7,6 +7,7 @@ import { romanize } from "@elyukai/utils/roman";
7
7
  import { numAsc } from "@optolith/helpers/compare";
8
8
  import { isNotNullish, mapNullable } from "@optolith/helpers/nullable";
9
9
  import { assertExhaustive } from "@optolith/helpers/typeSafety";
10
+ import { diffWordsWithSpace } from "diff";
10
11
  import { Case } from "tsondb/schema/gen";
11
12
  import { createEntityDescriptionCreator } from "../creator.js";
12
13
  import { renderAnimalTypesSection } from "./partial/animalTypes.js";
@@ -33,11 +34,20 @@ const combineGeneratedTextWithStaticTranslation = (label, generatedText, staticT
33
34
  return undefined;
34
35
  }
35
36
  const normalizedStaticText = typeof staticText === "string" ? staticText : staticText?.full;
37
+ const diff = normalizedStaticText === undefined
38
+ ? undefined
39
+ : diffWordsWithSpace(normalizedStaticText, generatedText);
36
40
  return {
37
41
  label,
38
- value: normalizedStaticText !== undefined && generatedText !== normalizedStaticText
39
- ? `***${generatedText}*** (${normalizedStaticText})`
40
- : generatedText,
42
+ value: diff === undefined
43
+ ? generatedText
44
+ : diff
45
+ .map(part => part.added
46
+ ? `<ins>${part.value}</ins>`
47
+ : part.removed
48
+ ? `<del>${part.value}</del>`
49
+ : part.value)
50
+ .join(""),
41
51
  };
42
52
  };
43
53
  const renderProperty = (id) => Reader.asks(({ translate, translateMap, getInstanceById }) => {
@@ -854,7 +864,7 @@ const renderMagicalRuneCost = (options, cost) => {
854
864
  case "Disjunction":
855
865
  return Reader.sequence(cost.Disjunction.list.map(costItem => formatEnergyR(costItem.value).thenW(text => appendNoteIfNeeded(costItem.translations, text)))).thenW(text => localeJoinR(text, "disjunction"));
856
866
  case "DerivedFromOption":
857
- return deriveValueGroupsFromMagicalRuneOptions(options, option => option.cost?.value, compareNullish(numAsc), num => num?.toString() ?? MISSING_VALUE).thenW(formatEnergyR);
867
+ return deriveValueGroupsFromMagicalRuneOptions(options, option => option.cost?.value, compareNullish(numAsc), num => num?.toFixed() ?? MISSING_VALUE).thenW(formatEnergyR);
858
868
  default:
859
869
  return assertExhaustive(cost);
860
870
  }
@@ -1,5 +1,5 @@
1
- import * as Database from "optolith-database-schema/gen";
2
- import { IdArgsVariant } from "tsondb/schema/gen";
1
+ import type * as Database from "optolith-database-schema/gen";
2
+ import type { IdArgsVariant } from "tsondb/schema/gen";
3
3
  export type GetInstanceById<in T extends Extract<keyof Database.EntityMap, string>> = <U extends T>(...args: IdArgsVariant<Database.EntityMap, U>) => Database.EntityMap[U] | undefined;
4
4
  export type GetAllInstances<T extends keyof Database.EntityMap> = <U extends T>(entity: U) => {
5
5
  id: string;
@@ -1,6 +1,6 @@
1
- import { Compare } from "@optolith/helpers/compare";
1
+ import type { Compare } from "@optolith/helpers/compare";
2
2
  import type { LocaleMeasurementAdjustments } from "optolith-database-schema/gen";
3
- import { Translate, TranslateMap, type Format } from "./translate.js";
3
+ import type { Format, Translate, TranslateMap } from "./translate.js";
4
4
  /**
5
5
  * The type of list to join in a locale-aware way.
6
6
  */
@@ -12,6 +12,7 @@ export type LocaleEnvironment = {
12
12
  id: string;
13
13
  format: Format;
14
14
  formatDate: (date: Date) => string;
15
+ formatNumber: FormatNumber;
15
16
  translate: Translate;
16
17
  translateMap: TranslateMap;
17
18
  compare: LocaleCompare;
@@ -26,3 +27,7 @@ export type LocaleCompare = Compare<string>;
26
27
  * A function that joins a list of strings according to the locale's rules for the given type.
27
28
  */
28
29
  export type LocaleJoin = (list: string[], type: LocaleJoinType) => string;
30
+ /**
31
+ * A function that formats a number according to the locale’s rules.
32
+ */
33
+ export type FormatNumber = (value: number) => string;
@@ -1,4 +1,4 @@
1
- import { Locale } from "optolith-database-schema/gen";
1
+ import type { Locale } from "optolith-database-schema/gen";
2
2
  import { ResponsiveTextSize } from "../entities/partial/responsiveText.js";
3
3
  /**
4
4
  * The type of all translations
package/lib/index.d.ts CHANGED
@@ -6,13 +6,14 @@ import type { EntityDescriptionCreator } from "./creator.js";
6
6
  import type { GetResolvedSelectOptionById } from "./entities/partial/prerequisites/single/activatable.js";
7
7
  import type { CountInstances, GetAllChildInstancesForParent, GetAllInstances, GetInstanceById } from "./helpers/getTypes.js";
8
8
  import type { LocaleEnvironment } from "./helpers/locale.js";
9
- export { LocaleEnvironment };
9
+ export type { LocaleEnvironment };
10
10
  /**
11
11
  * A JSON representation of the rules text for a library entry.
12
12
  */
13
13
  export type EntityDescription = {
14
14
  title: string;
15
15
  subtitle?: string;
16
+ badge?: number;
16
17
  className: string;
17
18
  body: EntityDescriptionSection[];
18
19
  errata?: {
@@ -44,6 +45,7 @@ export type EntityDescriptionSectionContent<DL = DefinitionListEntityDescription
44
45
  export type RawEntityDescription = {
45
46
  title: string;
46
47
  subtitle?: string;
48
+ badge?: number;
47
49
  className: string;
48
50
  body: (RawEntityDescriptionSection | undefined)[];
49
51
  errata?: Errata;