Give the interpreter real Celtic Cross meanings and --format/--lang support

- guidance.c now prints last, after the full spread; both it and main.c's
  new Celtic Cross readout pull real Waite card meanings via tarot_data.c
  instead of just naming cards.
- interpreter-cli gains --format text|html|json and --lang en|de, matching
  deck-engine - required linking engine/src/tarot_data.c and i18n.c into
  the interpreter binary (a narrow, documented exception to its earlier
  "no engine .c files" policy) and writing German translations for
  narrative.c's/guidance.c's own text.
This commit is contained in:
ml
2026-07-11 19:39:30 +02:00
parent 41422adf98
commit 1662d550c7
23 changed files with 1894 additions and 296 deletions
+90 -59
View File
@@ -1,86 +1,103 @@
#include "narrative.h"
#include <stdbool.h>
#include <stdio.h>
/* i18n_get() only - narrative.h already pulls in astro.h; this is the
* one additional engine header needed to route k_narratives/
* k_house_area through the same translation catalog tarot_data.c uses
* (see the Makefile's INTERP_TAROT_TEXT_OBJS comment for why i18n.c is
* linked into this binary). */
#include "../../engine/src/i18n.h"
typedef struct {
const char *slug; /* builds "narrative.<slug>.*" i18n keys */
const char *base; /* always relevant, may be "" (see the Sun) */
const char *harmonious; /* shown only under a trine/sextile; may be "" */
const char *discordant; /* shown only under a square/opposition; may be "" */
} TransitNarrative;
/* See narrative.h's doc comment for sourcing (Sepharial, "Transits and
* Planetary Periods", 1920, Chapter VIII) and the Moon/Pluto exception. */
/* Fallback (English) text, also the source of truth when no translation
* catalog is loaded - see narrative.h's doc comment for sourcing
* (Sepharial, "Transits and Planetary Periods", 1920, Chapter VIII) and
* the Moon/Pluto exception. German translations live in
* engine/i18n/de.lang under the same "narrative.<slug>.*" keys. */
static const TransitNarrative k_narratives[NUM_BODIES] = {
[PLANET_SUN] = {
.base = "",
.harmonious = "Benefits from superiors and advancement in your sphere of "
"life and work - honours, emoluments, and successful new "
"associations.",
.discordant = "Degradation and dishonour, loss of position, and adverse "
"judgement from superiors.",
"sun", "",
"Benefits from superiors and advancement in your sphere of "
"life and work - honours, emoluments, and successful new "
"associations.",
"Degradation and dishonour, loss of position, and adverse "
"judgement from superiors.",
},
[PLANET_MOON] = {
/* Original text, not from Sepharial - see narrative.h. */
.base = "Colours the everyday and domestic sphere of life, often "
"coinciding with the opening of new avenues.",
.harmonious = "These changes tend to be advantageous.",
.discordant = "These changes tend to be adverse, with some indisposition "
"or domestic friction.",
"moon",
"Colours the everyday and domestic sphere of life, often "
"coinciding with the opening of new avenues.",
"These changes tend to be advantageous.",
"These changes tend to be adverse, with some indisposition "
"or domestic friction.",
},
[PLANET_MERCURY] = {
.base = "Affects writings, journeys, commerce, and everyday activities - "
"a neutral messenger whose effect follows the nature of the "
"aspect it makes.",
.harmonious = "",
.discordant = "",
"mercury",
"Affects writings, journeys, commerce, and everyday activities - "
"a neutral messenger whose effect follows the nature of the "
"aspect it makes.",
"", "",
},
[PLANET_VENUS] = {
.base = "Brings domestic and social affairs to the fore - happiness, "
"comforts, and favours.",
.harmonious = "Success in love affairs and artistic pursuits is likely.",
.discordant = "Grief and disappointment are more likely.",
"venus",
"Brings domestic and social affairs to the fore - happiness, "
"comforts, and favours.",
"Success in love affairs and artistic pursuits is likely.",
"Grief and disappointment are more likely.",
},
[PLANET_MARS] = {
.base = "A strenuous time of quarrels, contention, strife and anger, with "
"some risk of hurts or injuries depending on the sign it "
"occupies.",
.harmonious = "Can bring benefits from doctors, surgeons, or new projects "
"and enterprises.",
.discordant = "",
"mars",
"A strenuous time of quarrels, contention, strife and anger, with "
"some risk of hurts or injuries depending on the sign it "
"occupies.",
"Can bring benefits from doctors, surgeons, or new projects "
"and enterprises.",
"",
},
[PLANET_JUPITER] = {
.base = "Brings increase and expansion - fullness of fortune and health, "
"and a generally fortunate time.",
.harmonious = "",
.discordant = "",
"jupiter",
"Brings increase and expansion - fullness of fortune and health, "
"and a generally fortunate time.",
"", "",
},
[PLANET_SATURN] = {
.base = "Brings depression, stagnation, hindrances and obstacles, and "
"some deprivation of the usual benefits.",
.harmonious = "Favours from older connections and past associations are "
"still possible.",
.discordant = "",
"saturn",
"Brings depression, stagnation, hindrances and obstacles, and "
"some deprivation of the usual benefits.",
"Favours from older connections and past associations are "
"still possible.",
"",
},
[PLANET_URANUS] = {
.base = "Brings separations, estrangements, sudden dislocations and "
"violent upsets.",
.harmonious = "Success through official or civic channels, and "
"beneficial changes or appointments, are possible.",
.discordant = "",
"uranus",
"Brings separations, estrangements, sudden dislocations and "
"violent upsets.",
"Success through official or civic channels, and "
"beneficial changes or appointments, are possible.",
"",
},
[PLANET_NEPTUNE] = {
.base = "Brings a state of chaos and confusion - an involved, uncertain "
"condition of affairs, with plots, subtlety, or unseen "
"influences at work.",
.harmonious = "",
.discordant = "",
"neptune",
"Brings a state of chaos and confusion - an involved, uncertain "
"condition of affairs, with plots, subtlety, or unseen "
"influences at work.",
"", "",
},
[PLANET_PLUTO] = {
/* Original text, not from Sepharial - see narrative.h. */
.base = "Brings deep, often hidden transformation - the surfacing or "
"dismantling of something that has outgrown its old form.",
.harmonious = "",
.discordant = "",
"pluto",
"Brings deep, often hidden transformation - the surfacing or "
"dismantling of something that has outgrown its old form.",
"", "",
},
};
@@ -96,7 +113,8 @@ static bool aspect_is_discordant(AspectType type) {
* house governs. This is centuries-old, uncredited astrological
* convention (the same status as the sign/aspect names already baked
* into engine/src/astro.c), not text drawn from Sepharial or any other
* single source. Indexed house - 1. */
* single source. Indexed house - 1; fallback (English) text only, keyed
* as "narrative.house.<1-12>" in engine/i18n's .lang files. */
static const char *const k_house_area[12] = {
"your sense of self, identity, and outward appearance",
"money, possessions, and personal values",
@@ -112,22 +130,35 @@ static const char *const k_house_area[12] = {
"solitude, the subconscious, and hidden matters",
};
static const char *narrative_field(const char *slug, const char *field, const char *fallback) {
char key[48];
snprintf(key, sizeof key, "narrative.%s.%s", slug, field);
return i18n_get(key, fallback);
}
void narrative_print(FILE *out, const char *indent, const Aspect *aspect,
int transiting_house) {
const TransitNarrative *n = &k_narratives[aspect->transiting_planet];
const char *base = narrative_field(n->slug, "base", n->base);
const char *extra = "";
if (aspect_is_harmonious(aspect->type)) extra = n->harmonious;
else if (aspect_is_discordant(aspect->type)) extra = n->discordant;
if (aspect_is_harmonious(aspect->type)) extra = narrative_field(n->slug, "harmonious", n->harmonious);
else if (aspect_is_discordant(aspect->type)) extra = narrative_field(n->slug, "discordant", n->discordant);
if (n->base[0] == '\0' && extra[0] == '\0') return;
if (base[0] == '\0' && extra[0] == '\0') return;
fprintf(out, "%s", indent);
if (transiting_house >= 1 && transiting_house <= 12) {
fprintf(out, "In matters of %s (house %d). ",
k_house_area[transiting_house - 1], transiting_house);
char house_key[24];
snprintf(house_key, sizeof house_key, "narrative.house.%d", transiting_house);
const char *area = i18n_get(house_key, k_house_area[transiting_house - 1]);
char frame[256];
snprintf(frame, sizeof frame, i18n_get("narrative.house_frame", "In matters of %s (house %d)."),
area, transiting_house);
fprintf(out, "%s ", frame);
}
if (n->base[0] != '\0') fprintf(out, "%s", n->base);
if (extra[0] != '\0') fprintf(out, "%s%s", n->base[0] != '\0' ? " " : "", extra);
if (base[0] != '\0') fprintf(out, "%s", base);
if (extra[0] != '\0') fprintf(out, "%s%s", base[0] != '\0' ? " " : "", extra);
fprintf(out, "\n");
}