feat(falukant): enhance notification handling and localization updates
All checks were successful
Deploy to production / deploy (push) Successful in 2m52s

- Updated the `enrichNotificationsWithCharacterNames` method in FalukantService to include region name enrichment and handle additional character IDs.
- Introduced a new `serializeNotificationForClient` function to format notifications for the client, ensuring all relevant data is included.
- Enhanced the MessagesDialog component to merge notification payloads and extract parameters more effectively, improving the clarity of displayed messages.
- Added new localization entries for director resignation risk and regional festival effects in multiple languages, ensuring comprehensive user notifications.
This commit is contained in:
Torsten Schulz (local)
2026-04-14 08:06:56 +02:00
parent 9deda3147e
commit 26daf5fed5
7 changed files with 230 additions and 43 deletions

View File

@@ -138,6 +138,9 @@ export default {
if (!n || typeof n !== 'object') return n || {};
let merged = { ...n };
const raw = n.tr;
if (raw && typeof raw === 'object') {
merged = { ...merged, ...raw };
}
if (typeof raw === 'string') {
const trimmed = raw.trim();
if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
@@ -151,6 +154,9 @@ export default {
}
}
}
if (merged.character_name && merged.characterName == null) {
merged.characterName = merged.character_name;
}
return merged;
},
@@ -228,10 +234,8 @@ export default {
try {
const titleKey = `${eventKey}.title`;
const descKey = `${eventKey}.description`;
// If no params were parsed from JSON, try to extract them from the notification (effects, character_id, etc.)
if ((!params || Object.keys(params).length === 0) && payload) {
params = this.extractParams(payload) || {};
}
const extracted = this.extractParams(payload) || {};
params = { ...extracted, ...params };
if (this.$te(titleKey) && this.$te(descKey)) {
const title = this.$t(titleKey, params);
@@ -243,6 +247,10 @@ export default {
}
}
// Immer Parameter aus dem vollständigen Payload (nach merge), z. B. director.resignation_risk_high
const extractedAll = this.extractParams(payload) || {};
params = { ...extractedAll, ...params };
// Fallback: Alte Methode für andere Notification-Typen
return this.$t(key, params);
},
@@ -312,6 +320,14 @@ export default {
extractParams(n) {
const base = this.mergeNotificationPayload(n);
const params = {};
const locale = this.$i18n?.locale || 'de';
const isGerman = String(locale).startsWith('de');
if (base.characterName) {
params.characterName = base.characterName;
} else if (base.character_name) {
params.characterName = base.character_name;
}
// Parameter aus effects extrahieren (Daemon: effects oft nur im JSON in tr)
if (base.effects && Array.isArray(base.effects)) {
@@ -326,11 +342,27 @@ export default {
} else if (effect.percent !== undefined) {
params.percent = effect.percent;
}
} else if (effect.type === 'price_change') {
if (effect.percent !== undefined && effect.percent !== null) {
const p = Number(effect.percent);
if (Number.isFinite(p)) {
params.priceChangePercent = `${p > 0 ? '+' : ''}${p.toFixed(1)}`;
}
}
} else if (effect.type === 'production_quality_change') {
if (effect.change !== undefined && effect.change !== null) {
const v = Number(effect.change);
if (Number.isFinite(v)) {
params.productionQualityChange = (v > 0 ? '+' : '') + (Number.isInteger(v) ? String(v) : v.toFixed(1));
}
}
} else if (effect.type === 'weather_change') {
params.hasWeatherChange = true;
} else if (effect.type === 'character_health_change') {
if (effect.character_id) {
// Prefer explicit characterName from notification, otherwise fall back to provided name or use id placeholder
params.character_id = effect.character_id;
params.characterName = params.characterName || base.characterName || `#${effect.character_id}`;
params.characterName = params.characterName || base.characterName || base.character_name || `#${effect.character_id}`;
}
if (effect.change !== undefined) {
params.change = effect.change;
@@ -352,9 +384,33 @@ export default {
}
// Weitere Parameter aus der Notification selbst
if (base.region_id && base.regionName) {
params.regionName = base.regionName;
if (base.region_id != null) {
if (base.regionName) {
params.regionName = base.regionName;
} else if (base.region_name) {
params.regionName = base.region_name;
}
}
// Daemon: Meta-Objekte (auch wenn effects[] schon Werte liefert — fehlende Werte ergänzen)
if (params.priceChangePercent == null && base.price_change && typeof base.price_change === 'object') {
const applied = base.price_change.applied !== false;
const pct = base.price_change.percent;
if (applied && pct != null && Number.isFinite(Number(pct))) {
const p = Number(pct);
params.priceChangePercent = `${p > 0 ? '+' : ''}${p.toFixed(1)}`;
}
}
if (params.productionQualityChange == null && base.production_quality && typeof base.production_quality === 'object') {
const applied = base.production_quality.applied !== false;
const ch = base.production_quality.change;
if (applied && ch != null && Number.isFinite(Number(ch))) {
const v = Number(ch);
params.productionQualityChange = (v > 0 ? '+' : '') + (Number.isInteger(v) ? String(v) : v.toFixed(1));
}
}
params.priceEffectLine = this.buildRegionalFestivalEffectsLine(params, isGerman);
if (base.character_id && base.characterName) {
params.characterName = base.characterName;
}
@@ -370,6 +426,13 @@ export default {
if (base.threshold_percent !== undefined) {
params.threshold_percent = base.threshold_percent;
}
const isDirectorResign = base.event === 'director_resignation_risk_high'
|| (typeof base.tr === 'string' && base.tr.includes('director.resignation'));
if (isDirectorResign) {
params.directorName = params.characterName
|| base.character_name
|| (base.director_character_id != null ? `#${base.director_character_id}` : '');
}
if (base.director_id !== undefined) {
params.director_id = base.director_id;
}
@@ -440,7 +503,48 @@ export default {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(amount);
}
},
/**
* Zeilen für regionale Events (Fest o. ä.): Wetter, Preis, Produktionsqualität — aus effects[] und/oder price_change / production_quality.
*/
buildRegionalFestivalEffectsLine(params, isGerman) {
const parts = [];
const kw = 'falukant.notifications.random_event.effects.festival_weather_line';
const kp = 'falukant.notifications.random_event.effects.festival_price_line';
const kq = 'falukant.notifications.random_event.effects.festival_quality_line';
if (params.hasWeatherChange) {
if (this.$te(kw)) parts.push(String(this.$t(kw)));
else parts.push(isGerman ? 'Wetter in der Region schlägt um.' : 'The regional weather shifts.');
}
if (params.priceChangePercent != null) {
if (this.$te(kp)) {
parts.push(String(this.$t(kp, { percent: params.priceChangePercent })));
} else {
const legacy = 'falukant.notifications.random_event.effects.price_effect_suffix';
parts.push(
this.$te(legacy)
? String(this.$t(legacy, { percent: params.priceChangePercent }))
: (isGerman
? `Warenpreise etwa ${params.priceChangePercent} %.`
: `Goods prices about ${params.priceChangePercent}%.`)
);
}
}
if (params.productionQualityChange != null) {
if (this.$te(kq)) {
parts.push(String(this.$t(kq, { change: params.productionQualityChange })));
} else {
parts.push(
isGerman
? `Produktionsqualität etwa ${params.productionQualityChange}.`
: `Production quality about ${params.productionQualityChange}.`
);
}
}
if (!parts.length) return '';
return ` ${parts.join(' ')}`;
},
},
computed: {
totalPages() {

View File

@@ -35,7 +35,7 @@
"notify_election_created": "Giskedyul ang usa ka bag-ong eleksiyon.",
"notify_office_filled": "Na puno ang usa ka politikal nga opisina.",
"director": {
"resignation_risk_high": "Taas ang risgo nga mubiyaa ang direktor: risgo {risk_percent}% (threshold {threshold_percent}%). Karon nga satisfaction {satisfaction}."
"resignation_risk_high": "Taas ang risgo nga mobiya ang direktor nga si {directorName}: risgo {risk_percent}% (threshold {threshold_percent}%). Karon nga satisfaction {satisfaction}."
},
"director_death": "Namatay si {characterName} sa edad nga {ageYears}. Isip amo, kinahanglan kang magtudlo og bag-ong direktor.{regionLabel}{spouses}{children}{lovers}",
"relationship_death": "Namatay si {characterName} sa edad nga {ageYears}.{regionLabel}{spouses}{children}{lovers}",
@@ -93,7 +93,13 @@
},
"regional_festival": {
"title": "Pista sa rehiyon",
"description": "Adunay dakong pista sa rehiyon nga {regionName}."
"description": "Adunay dakong pista sa rehiyon nga {regionName}.{priceEffectLine}"
},
"effects": {
"price_effect_suffix": " Epekto: gipaabot nga pagbag-o sa presyo sa mga paliton nga ~{percent}%.",
"festival_weather_line": "Ang panahon sa rehiyon nagbag-o.",
"festival_price_line": "Presyo sa mga paliton ~{percent}%.",
"festival_quality_line": "Kalidad sa produksyon ~{change}."
},
"regional_epidemic": {
"title": "Epidemya",

View File

@@ -49,7 +49,7 @@
"notify_election_created": "Es wurde eine neue Wahl ausgeschrieben.",
"notify_office_filled": "Ein politisches Amt wurde neu besetzt.",
"director": {
"resignation_risk_high": "Hohe Kündigungsgefahr bei einem Direktor: Risiko {risk_percent}% (Schwelle {threshold_percent}%). Zufriedenheit aktuell {satisfaction}."
"resignation_risk_high": "Hohe Kündigungsgefahr für den Direktor {directorName}: Risiko {risk_percent} % (Schwelle {threshold_percent} %). Zufriedenheit aktuell {satisfaction}."
},
"director_death": "{characterName} ist im Alter von {ageYears} Jahren verstorben. Als Arbeitgeber musst du die Direktion neu besetzen.{regionLabel}{spouses}{children}{lovers}",
"relationship_death": "{characterName} ist im Alter von {ageYears} Jahren verstorben.{regionLabel}{spouses}{children}{lovers}",
@@ -95,7 +95,13 @@
},
"regional_festival": {
"title": "Regionales Fest",
"description": "Ein großes Fest findet in der Region {regionName} statt."
"description": "Ein großes Fest findet in der Region {regionName} statt.{priceEffectLine}"
},
"effects": {
"price_effect_suffix": " Auswirkung: erwartete Warenpreisänderung ca. {percent} %.",
"festival_weather_line": "Wetter in der Region schlägt um.",
"festival_price_line": "Warenpreise etwa {percent} %.",
"festival_quality_line": "Produktionsqualität etwa {change}."
},
"regional_epidemic": {
"title": "Epidemie",

View File

@@ -35,7 +35,7 @@
"notify_election_created": "A new election has been scheduled.",
"notify_office_filled": "A political office has been filled.",
"director": {
"resignation_risk_high": "High resignation risk for a director: risk {risk_percent}% (threshold {threshold_percent}%). Current satisfaction {satisfaction}."
"resignation_risk_high": "High resignation risk for director {directorName}: risk {risk_percent}% (threshold {threshold_percent}%). Current satisfaction {satisfaction}."
},
"director_death": "{characterName} died at the age of {ageYears}. As employer you need to appoint a new director.{regionLabel}{spouses}{children}{lovers}",
"relationship_death": "{characterName} died at the age of {ageYears}.{regionLabel}{spouses}{children}{lovers}",
@@ -93,7 +93,13 @@
},
"regional_festival": {
"title": "Regional Festival",
"description": "A large festival is taking place in the region {regionName}."
"description": "A large festival is taking place in the region {regionName}.{priceEffectLine}"
},
"effects": {
"price_effect_suffix": " Effect: expected goods price change ~{percent}%.",
"festival_weather_line": "The regional weather shifts.",
"festival_price_line": "Goods prices about {percent}%.",
"festival_quality_line": "Production quality about {change}."
},
"regional_epidemic": {
"title": "Epidemic",

View File

@@ -49,7 +49,7 @@
"notify_election_created": "Se ha convocado una nueva elección.",
"notify_office_filled": "Se ha cubierto un cargo político.",
"director": {
"resignation_risk_high": "Alto riesgo de renuncia de un director: riesgo {risk_percent}% (umbral {threshold_percent}%). Satisfacción actual {satisfaction}."
"resignation_risk_high": "Alto riesgo de renuncia del director {directorName}: riesgo {risk_percent}% (umbral {threshold_percent}%). Satisfacción actual {satisfaction}."
},
"director_death": "{characterName} ha fallecido a la edad de {ageYears} años. Como empleador debes nombrar un nuevo director.{regionLabel}{spouses}{children}{lovers}",
"relationship_death": "{characterName} ha fallecido a la edad de {ageYears} años.{regionLabel}{spouses}{children}{lovers}",
@@ -95,7 +95,13 @@
},
"regional_festival": {
"title": "Fiesta regional",
"description": "Se celebra una gran fiesta en la región {regionName}."
"description": "Se celebra una gran fiesta en la región {regionName}.{priceEffectLine}"
},
"effects": {
"price_effect_suffix": " Efecto: variación esperada del precio de las mercancías ~{percent}%.",
"festival_weather_line": "El tiempo en la región cambia.",
"festival_price_line": "Precios de mercancías ~{percent} %.",
"festival_quality_line": "Calidad de producción ~{change}."
},
"regional_epidemic": {
"title": "Epidemia",

View File

@@ -49,7 +49,7 @@
"notify_election_created": "Une nouvelle élection a été déclenchée.",
"notify_office_filled": "Une fonction politique a été pourvue.",
"director": {
"resignation_risk_high": "Risque élevé de démission dun directeur : risque {risk_percent}% (seuil {threshold_percent}%). Satisfaction actuelle {satisfaction}."
"resignation_risk_high": "Risque élevé de démission du directeur {directorName} : risque {risk_percent}% (seuil {threshold_percent}%). Satisfaction actuelle {satisfaction}."
},
"director_death": "{characterName} est décédé à l'âge de {ageYears}. En tant qu'employeur, vous devez remplir le conseil d'administration.{regionLabel}{spouses}{children}{lovers}",
"relationship_death": "{characterName} est décédé à l'âge de {ageYears}.{regionLabel}{spouses}{children}{lovers}",
@@ -95,7 +95,13 @@
},
"regional_festival": {
"title": "Fête régionale",
"description": "Un grand festival a lieu dans la région {regionName}."
"description": "Un grand festival a lieu dans la région {regionName}.{priceEffectLine}"
},
"effects": {
"price_effect_suffix": " Effet : variation attendue des prix des marchandises ~{percent} %.",
"festival_weather_line": "Le temps dans la région change.",
"festival_price_line": "Prix des marchandises ~{percent} %.",
"festival_quality_line": "Qualité de production ~{change}."
},
"regional_epidemic": {
"title": "Épidémie",