From 5d06d97737cf2f15ebb5232264ccaa5b956640dc Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 2 Apr 2026 15:27:05 +0200 Subject: [PATCH] feat(politics): add new political office benefits and enhance database migration - Introduced new benefits including 'reputation_periodic', 'appoint_politicians', 'set_regional_tax', 'free_lover_slots', 'guard_protection', and 'court_immunity' to the political office system. - Updated database migration to add and remove the 'last_political_daily_salary_on' column using SQL queries for better performance. - Enhanced localization files for multiple languages to support new benefits, improving user experience across the application. - Updated UI components to display new benefits correctly in the PoliticsView, ensuring accurate representation of political office functionalities. --- ...000-politics-benefits-and-daily-salary.cjs | 26 ++--- backend/services/falukantService.js | 19 +++ ...nt_user_last_political_daily_salary_on.sql | 9 ++ ...lukant_political_office_extra_benefits.sql | 108 ++++++++++++++++++ .../utils/falukant/initializeFalukantTypes.js | 73 +++++++++++- frontend/src/i18n/locales/ceb/falukant.json | 12 ++ frontend/src/i18n/locales/de/falukant.json | 12 ++ frontend/src/i18n/locales/en/falukant.json | 12 ++ frontend/src/i18n/locales/es/falukant.json | 12 ++ frontend/src/views/falukant/PoliticsView.vue | 39 +++++++ 10 files changed, 303 insertions(+), 19 deletions(-) create mode 100644 backend/sql/add_falukant_user_last_political_daily_salary_on.sql create mode 100644 backend/sql/falukant_political_office_extra_benefits.sql diff --git a/backend/migrations/20260401120000-politics-benefits-and-daily-salary.cjs b/backend/migrations/20260401120000-politics-benefits-and-daily-salary.cjs index 0bf4821..6f0a4ae 100644 --- a/backend/migrations/20260401120000-politics-benefits-and-daily-salary.cjs +++ b/backend/migrations/20260401120000-politics-benefits-and-daily-salary.cjs @@ -2,17 +2,10 @@ module.exports = { up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn( - { - tableName: 'falukant_user', - schema: 'falukant_data' - }, - 'last_political_daily_salary_on', - { - type: Sequelize.DATEONLY, - allowNull: true - } - ); + await queryInterface.sequelize.query(` + ALTER TABLE falukant_data.falukant_user + ADD COLUMN IF NOT EXISTS last_political_daily_salary_on date NULL; + `); await queryInterface.sequelize.query(` DO $$ @@ -51,13 +44,10 @@ module.exports = { }, down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn( - { - tableName: 'falukant_user', - schema: 'falukant_data' - }, - 'last_political_daily_salary_on' - ); + await queryInterface.sequelize.query(` + ALTER TABLE falukant_data.falukant_user + DROP COLUMN IF EXISTS last_political_daily_salary_on; + `); await queryInterface.sequelize.query(` DO $$ diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 1be0886..84ea1a2 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -6101,6 +6101,25 @@ class FalukantService extends BaseService { if (amount > 0) { out.push({ tr: 'daily_salary', params: { amount } }); } + } else if (tr === 'reputation_periodic' || (tr === 'reputation' && (v.intervalDays != null || v.everyDays != null))) { + const intervalDays = Math.max(1, Number(v.intervalDays ?? v.everyDays ?? 7)); + const gain = Math.max(1, Number(v.gain ?? v.reputationGain ?? 1)); + out.push({ tr: 'reputation_periodic', params: { intervalDays, gain } }); + } else if (tr === 'appoint_politicians' || (tr === 'influence' && Array.isArray(v.officeTrs) && v.officeTrs.length)) { + const officeTrs = Array.isArray(v.officeTrs) ? v.officeTrs.filter((x) => typeof x === 'string') : []; + if (officeTrs.length) { + out.push({ tr: 'appoint_politicians', params: { officeTrs } }); + } + } else if (tr === 'set_regional_tax' || tr === 'set_regionl_tax') { + const scope = typeof v.scope === 'string' && v.scope ? v.scope : 'local'; + out.push({ tr: 'set_regional_tax', params: { scope } }); + } else if (tr === 'free_lover_slots') { + const count = Math.max(1, Number(v.count ?? 1)); + out.push({ tr: 'free_lover_slots', params: { count } }); + } else if (tr === 'guard_protection') { + out.push({ tr: 'guard_protection', params: {} }); + } else if (tr === 'court_immunity') { + out.push({ tr: 'court_immunity', params: {} }); } else if (tr) { out.push({ tr: 'generic_benefit', params: { code: tr } }); } diff --git a/backend/sql/add_falukant_user_last_political_daily_salary_on.sql b/backend/sql/add_falukant_user_last_political_daily_salary_on.sql new file mode 100644 index 0000000..3b15cfb --- /dev/null +++ b/backend/sql/add_falukant_user_last_political_daily_salary_on.sql @@ -0,0 +1,9 @@ +-- Spalte für tägliches Politik-Gehalt (Sequelize: FalukantUser.lastPoliticalDailySalaryOn). +-- Entspricht Migration: backend/migrations/20260401120000-politics-benefits-and-daily-salary.cjs +-- +-- Bevorzugt: Migration per CLI ausführen (führt auch die political_office_benefit-Anpassung aus). +-- Nur diese Spalte ohne CLI: dieses Skript einmal ausführen; die Migration nutzt ADD COLUMN IF NOT EXISTS +-- und kann danach trotzdem noch sauber durchlaufen. + +ALTER TABLE falukant_data.falukant_user + ADD COLUMN IF NOT EXISTS last_political_daily_salary_on date NULL; diff --git a/backend/sql/falukant_political_office_extra_benefits.sql b/backend/sql/falukant_political_office_extra_benefits.sql new file mode 100644 index 0000000..24268a6 --- /dev/null +++ b/backend/sql/falukant_political_office_extra_benefits.sql @@ -0,0 +1,108 @@ +-- ============================================================================= +-- Zusätzliche politische Amtsvorteile (reputation_periodic, Ernennungen, Steuerkompetenz, …) +-- ============================================================================= +-- Voraussetzung: falukant_political_office_benefits.sql (oder gleichwertiges Schema) ist angelegt. +-- Idempotent: mehrfach ausführbar. +-- +-- Hinweis: Frische Dev-DBs bekommen dieselben Zeilen über initializePoliticalOfficeBenefits (Node). + +INSERT INTO falukant_type.political_office_benefit_type (tr) +SELECT 'reputation_periodic' WHERE NOT EXISTS (SELECT 1 FROM falukant_type.political_office_benefit_type t WHERE t.tr = 'reputation_periodic'); +INSERT INTO falukant_type.political_office_benefit_type (tr) +SELECT 'appoint_politicians' WHERE NOT EXISTS (SELECT 1 FROM falukant_type.political_office_benefit_type t WHERE t.tr = 'appoint_politicians'); +INSERT INTO falukant_type.political_office_benefit_type (tr) +SELECT 'set_regional_tax' WHERE NOT EXISTS (SELECT 1 FROM falukant_type.political_office_benefit_type t WHERE t.tr = 'set_regional_tax'); +INSERT INTO falukant_type.political_office_benefit_type (tr) +SELECT 'free_lover_slots' WHERE NOT EXISTS (SELECT 1 FROM falukant_type.political_office_benefit_type t WHERE t.tr = 'free_lover_slots'); +INSERT INTO falukant_type.political_office_benefit_type (tr) +SELECT 'guard_protection' WHERE NOT EXISTS (SELECT 1 FROM falukant_type.political_office_benefit_type t WHERE t.tr = 'guard_protection'); +INSERT INTO falukant_type.political_office_benefit_type (tr) +SELECT 'court_immunity' WHERE NOT EXISTS (SELECT 1 FROM falukant_type.political_office_benefit_type t WHERE t.tr = 'court_immunity'); + +-- reputation_periodic +INSERT INTO falukant_predefine.political_office_benefit (office_type_id, benefit_type_id, value) +SELECT ot.id, bt.id, v::jsonb FROM falukant_type.political_office_type ot +JOIN falukant_type.political_office_benefit_type bt ON bt.tr = 'reputation_periodic' +CROSS JOIN (VALUES + ('assessor', '{"intervalDays":28,"gain":1}'::text), + ('master-builder', '{"intervalDays":21,"gain":1}'::text), + ('village-major', '{"intervalDays":21,"gain":1}'::text), + ('councillor', '{"intervalDays":28,"gain":1}'::text), + ('council', '{"intervalDays":21,"gain":1}'::text), + ('mayor', '{"intervalDays":14,"gain":2}'::text), + ('town-clerk', '{"intervalDays":21,"gain":1}'::text), + ('judge', '{"intervalDays":14,"gain":2}'::text), + ('bailif', '{"intervalDays":21,"gain":1}'::text), + ('sheriff', '{"intervalDays":14,"gain":2}'::text), + ('taxman', '{"intervalDays":21,"gain":2}'::text), + ('treasurer', '{"intervalDays":14,"gain":3}'::text), + ('consultant', '{"intervalDays":21,"gain":2}'::text), + ('hangman', '{"intervalDays":21,"gain":1}'::text), + ('territorial-council', '{"intervalDays":14,"gain":2}'::text), + ('territorial-council-speaker', '{"intervalDays":10,"gain":3}'::text), + ('ruler-consultant', '{"intervalDays":10,"gain":3}'::text), + ('state-administrator', '{"intervalDays":10,"gain":3}'::text), + ('super-state-administrator', '{"intervalDays":7,"gain":4}'::text), + ('governor', '{"intervalDays":7,"gain":4}'::text), + ('minister', '{"intervalDays":10,"gain":3}'::text), + ('ministry-helper', '{"intervalDays":14,"gain":2}'::text), + ('chancellor', '{"intervalDays":7,"gain":5}'::text) +) AS s(office_name, v) +WHERE ot.name = s.office_name +AND NOT EXISTS (SELECT 1 FROM falukant_predefine.political_office_benefit x WHERE x.office_type_id = ot.id AND x.benefit_type_id = bt.id); + +-- appoint_politicians +INSERT INTO falukant_predefine.political_office_benefit (office_type_id, benefit_type_id, value) +SELECT ot.id, bt.id, v::jsonb FROM falukant_type.political_office_type ot +JOIN falukant_type.political_office_benefit_type bt ON bt.tr = 'appoint_politicians' +CROSS JOIN (VALUES + ('mayor', '{"officeTrs":["beadle","town-clerk"]}'::text), + ('judge', '{"officeTrs":["bailif"]}'::text), + ('governor', '{"officeTrs":["state-administrator","consultant"]}'::text), + ('super-state-administrator', '{"officeTrs":["territorial-council","hangman"]}'::text), + ('chancellor', '{"officeTrs":["minister","ministry-helper","super-state-administrator"]}'::text) +) AS s(office_name, v) +WHERE ot.name = s.office_name +AND NOT EXISTS (SELECT 1 FROM falukant_predefine.political_office_benefit x WHERE x.office_type_id = ot.id AND x.benefit_type_id = bt.id); + +-- set_regional_tax +INSERT INTO falukant_predefine.political_office_benefit (office_type_id, benefit_type_id, value) +SELECT ot.id, bt.id, v::jsonb FROM falukant_type.political_office_type ot +JOIN falukant_type.political_office_benefit_type bt ON bt.tr = 'set_regional_tax' +CROSS JOIN (VALUES + ('taxman', '{"scope":"local"}'::text), + ('treasurer', '{"scope":"shire"}'::text), + ('super-state-administrator', '{"scope":"duchy"}'::text), + ('chancellor', '{"scope":"national"}'::text) +) AS s(office_name, v) +WHERE ot.name = s.office_name +AND NOT EXISTS (SELECT 1 FROM falukant_predefine.political_office_benefit x WHERE x.office_type_id = ot.id AND x.benefit_type_id = bt.id); + +-- free_lover_slots +INSERT INTO falukant_predefine.political_office_benefit (office_type_id, benefit_type_id, value) +SELECT ot.id, bt.id, v::jsonb FROM falukant_type.political_office_type ot +JOIN falukant_type.political_office_benefit_type bt ON bt.tr = 'free_lover_slots' +CROSS JOIN (VALUES + ('councillor', '{"count":1}'::text), + ('mayor', '{"count":1}'::text), + ('minister', '{"count":1}'::text), + ('governor', '{"count":2}'::text), + ('chancellor', '{"count":3}'::text) +) AS s(office_name, v) +WHERE ot.name = s.office_name +AND NOT EXISTS (SELECT 1 FROM falukant_predefine.political_office_benefit x WHERE x.office_type_id = ot.id AND x.benefit_type_id = bt.id); + +-- guard_protection / court_immunity +INSERT INTO falukant_predefine.political_office_benefit (office_type_id, benefit_type_id, value) +SELECT ot.id, bt.id, '{}'::jsonb FROM falukant_type.political_office_type ot +JOIN falukant_type.political_office_benefit_type bt ON bt.tr = 'guard_protection' +CROSS JOIN (VALUES ('sheriff'), ('hangman'), ('minister')) AS s(office_name) +WHERE ot.name = s.office_name +AND NOT EXISTS (SELECT 1 FROM falukant_predefine.political_office_benefit x WHERE x.office_type_id = ot.id AND x.benefit_type_id = bt.id); + +INSERT INTO falukant_predefine.political_office_benefit (office_type_id, benefit_type_id, value) +SELECT ot.id, bt.id, '{}'::jsonb FROM falukant_type.political_office_type ot +JOIN falukant_type.political_office_benefit_type bt ON bt.tr = 'court_immunity' +CROSS JOIN (VALUES ('judge'), ('councillor'), ('chancellor')) AS s(office_name) +WHERE ot.name = s.office_name +AND NOT EXISTS (SELECT 1 FROM falukant_predefine.political_office_benefit x WHERE x.office_type_id = ot.id AND x.benefit_type_id = bt.id); diff --git a/backend/utils/falukant/initializeFalukantTypes.js b/backend/utils/falukant/initializeFalukantTypes.js index c569061..bbc031b 100644 --- a/backend/utils/falukant/initializeFalukantTypes.js +++ b/backend/utils/falukant/initializeFalukantTypes.js @@ -356,6 +356,7 @@ const politicalOfficeBenefitTypes = [ { tr: 'salary' }, { tr: 'daily_salary' }, { tr: 'reputation' }, + { tr: 'reputation_periodic' }, { tr: 'influence' }, { tr: 'access_level' }, { tr: 'housing_allowance' }, @@ -363,6 +364,60 @@ const politicalOfficeBenefitTypes = [ { tr: 'guard_protection' }, { tr: 'court_immunity' }, { tr: 'set_regionl_tax' }, + { tr: 'set_regional_tax' }, + { tr: 'appoint_politicians' }, + { tr: 'free_lover_slots' }, +]; + +/** Zusätzliche Amtsvorteile (Anzeige; Spielmechanik z. T. noch offen). SQL-Pendant: backend/sql/falukant_political_office_extra_benefits.sql */ +const politicalOfficeExtraBenefitSeeds = [ + { officeTr: 'assessor', benefitTr: 'reputation_periodic', value: { intervalDays: 28, gain: 1 } }, + { officeTr: 'master-builder', benefitTr: 'reputation_periodic', value: { intervalDays: 21, gain: 1 } }, + { officeTr: 'village-major', benefitTr: 'reputation_periodic', value: { intervalDays: 21, gain: 1 } }, + { officeTr: 'councillor', benefitTr: 'reputation_periodic', value: { intervalDays: 28, gain: 1 } }, + { officeTr: 'council', benefitTr: 'reputation_periodic', value: { intervalDays: 21, gain: 1 } }, + { officeTr: 'mayor', benefitTr: 'reputation_periodic', value: { intervalDays: 14, gain: 2 } }, + { officeTr: 'town-clerk', benefitTr: 'reputation_periodic', value: { intervalDays: 21, gain: 1 } }, + { officeTr: 'judge', benefitTr: 'reputation_periodic', value: { intervalDays: 14, gain: 2 } }, + { officeTr: 'bailif', benefitTr: 'reputation_periodic', value: { intervalDays: 21, gain: 1 } }, + { officeTr: 'sheriff', benefitTr: 'reputation_periodic', value: { intervalDays: 14, gain: 2 } }, + { officeTr: 'taxman', benefitTr: 'reputation_periodic', value: { intervalDays: 21, gain: 2 } }, + { officeTr: 'treasurer', benefitTr: 'reputation_periodic', value: { intervalDays: 14, gain: 3 } }, + { officeTr: 'consultant', benefitTr: 'reputation_periodic', value: { intervalDays: 21, gain: 2 } }, + { officeTr: 'hangman', benefitTr: 'reputation_periodic', value: { intervalDays: 21, gain: 1 } }, + { officeTr: 'territorial-council', benefitTr: 'reputation_periodic', value: { intervalDays: 14, gain: 2 } }, + { officeTr: 'territorial-council-speaker', benefitTr: 'reputation_periodic', value: { intervalDays: 10, gain: 3 } }, + { officeTr: 'ruler-consultant', benefitTr: 'reputation_periodic', value: { intervalDays: 10, gain: 3 } }, + { officeTr: 'state-administrator', benefitTr: 'reputation_periodic', value: { intervalDays: 10, gain: 3 } }, + { officeTr: 'super-state-administrator', benefitTr: 'reputation_periodic', value: { intervalDays: 7, gain: 4 } }, + { officeTr: 'governor', benefitTr: 'reputation_periodic', value: { intervalDays: 7, gain: 4 } }, + { officeTr: 'minister', benefitTr: 'reputation_periodic', value: { intervalDays: 10, gain: 3 } }, + { officeTr: 'ministry-helper', benefitTr: 'reputation_periodic', value: { intervalDays: 14, gain: 2 } }, + { officeTr: 'chancellor', benefitTr: 'reputation_periodic', value: { intervalDays: 7, gain: 5 } }, + + { officeTr: 'mayor', benefitTr: 'appoint_politicians', value: { officeTrs: ['beadle', 'town-clerk'] } }, + { officeTr: 'judge', benefitTr: 'appoint_politicians', value: { officeTrs: ['bailif'] } }, + { officeTr: 'governor', benefitTr: 'appoint_politicians', value: { officeTrs: ['state-administrator', 'consultant'] } }, + { officeTr: 'super-state-administrator', benefitTr: 'appoint_politicians', value: { officeTrs: ['territorial-council', 'hangman'] } }, + { officeTr: 'chancellor', benefitTr: 'appoint_politicians', value: { officeTrs: ['minister', 'ministry-helper', 'super-state-administrator'] } }, + + { officeTr: 'taxman', benefitTr: 'set_regional_tax', value: { scope: 'local' } }, + { officeTr: 'treasurer', benefitTr: 'set_regional_tax', value: { scope: 'shire' } }, + { officeTr: 'super-state-administrator', benefitTr: 'set_regional_tax', value: { scope: 'duchy' } }, + { officeTr: 'chancellor', benefitTr: 'set_regional_tax', value: { scope: 'national' } }, + + { officeTr: 'councillor', benefitTr: 'free_lover_slots', value: { count: 1 } }, + { officeTr: 'mayor', benefitTr: 'free_lover_slots', value: { count: 1 } }, + { officeTr: 'minister', benefitTr: 'free_lover_slots', value: { count: 1 } }, + { officeTr: 'governor', benefitTr: 'free_lover_slots', value: { count: 2 } }, + { officeTr: 'chancellor', benefitTr: 'free_lover_slots', value: { count: 3 } }, + + { officeTr: 'sheriff', benefitTr: 'guard_protection', value: {} }, + { officeTr: 'hangman', benefitTr: 'guard_protection', value: {} }, + { officeTr: 'minister', benefitTr: 'guard_protection', value: {} }, + { officeTr: 'judge', benefitTr: 'court_immunity', value: {} }, + { officeTr: 'councillor', benefitTr: 'court_immunity', value: {} }, + { officeTr: 'chancellor', benefitTr: 'court_immunity', value: {} }, ]; const politicalOffices = [ @@ -1092,8 +1147,24 @@ export const initializePoliticalOfficeBenefits = async () => { if (wasCreated) dailyCreated += 1; } + let extraCreated = 0; + for (const { officeTr, benefitTr, value } of politicalOfficeExtraBenefitSeeds) { + const office = await PoliticalOfficeType.findOne({ where: { name: officeTr } }); + const bType = await PoliticalOfficeBenefitType.findOne({ where: { tr: benefitTr } }); + if (!office || !bType) continue; + const [, wasCreated] = await PoliticalOfficeBenefit.findOrCreate({ + where: { officeTypeId: office.id, benefitTypeId: bType.id }, + defaults: { + officeTypeId: office.id, + benefitTypeId: bType.id, + value: value && typeof value === 'object' ? value : {} + } + }); + if (wasCreated) extraCreated += 1; + } + console.log( - `[Falukant] PoliticalOfficeBenefits: Steuer neu=${taxCreated}, Tageslohn neu=${dailyCreated} (gesamt Ämter=${allOffices.length})` + `[Falukant] PoliticalOfficeBenefits: Steuer neu=${taxCreated}, Tageslohn neu=${dailyCreated}, weitere Vorteile neu=${extraCreated} (Ämter gesamt=${allOffices.length})` ); }; diff --git a/frontend/src/i18n/locales/ceb/falukant.json b/frontend/src/i18n/locales/ceb/falukant.json index 6ea7f54..361031f 100644 --- a/frontend/src/i18n/locales/ceb/falukant.json +++ b/frontend/src/i18n/locales/ceb/falukant.json @@ -438,6 +438,18 @@ "daily_salary": "Adlaw-adlaw nga suhol (usa ra kada adlaw): mga {amount}", "tax_exemption": "Wa’y buhis: {regions}", "tax_exemption_all": "Wa’y buhis: tanang lebel sa rehiyon", + "reputation_periodic": "+{gain} reputasyon matag {days} ka adlaw (benepisyo sa opisina)", + "appoint_politicians": "Katungod sa pagtudlo: {offices}", + "set_regional_tax": "Maka-set sa mga rate sa buhis ({scope})", + "tax_scope": { + "local": "lokal sa hurisdiksyon", + "shire": "lebel sa shire", + "duchy": "lebel sa duchy", + "national": "tibuok nasud" + }, + "free_lover_slots": "{count} dugang relasyon nga walay bulan nga gasto", + "guard_protection": "Opisyal nga panalipod / escort", + "court_immunity": "Limitado nga immune sa korte sa opisina", "generic": "Benepisyo ({code})" }, "regionLevels": { diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index 9be9d57..cae6c30 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -1357,6 +1357,18 @@ "daily_salary": "Tagesamtshonorar (einmal pro Tag): ca. {amount}", "tax_exemption": "Steuerbefreiung: {regions}", "tax_exemption_all": "Steuerbefreiung: alle Regionsebenen", + "reputation_periodic": "+{gain} Ansehen alle {days} Tage (Amtsbonus)", + "appoint_politicians": "Ernennungsrecht für folgende Ämter: {offices}", + "set_regional_tax": "Darf Steuersätze festlegen ({scope})", + "tax_scope": { + "local": "lokal im Amtsgebiet", + "shire": "auf Grafschaftsebene", + "duchy": "auf Herzogtumsebene", + "national": "reichsweit" + }, + "free_lover_slots": "{count} zusätzliche Liebschaft(en) ohne monatlichen Unterhalt", + "guard_protection": "Amtsschutz / Eskorte", + "court_immunity": "Eingeschränkte gerichtliche Immunität im Amtsbereich", "generic": "Vorteil ({code})" }, "regionLevels": { diff --git a/frontend/src/i18n/locales/en/falukant.json b/frontend/src/i18n/locales/en/falukant.json index 2ba024f..b54081c 100644 --- a/frontend/src/i18n/locales/en/falukant.json +++ b/frontend/src/i18n/locales/en/falukant.json @@ -578,6 +578,18 @@ "daily_salary": "Daily office stipend (once per day): about {amount}", "tax_exemption": "Tax exemption: {regions}", "tax_exemption_all": "Tax exemption: all regional levels", + "reputation_periodic": "+{gain} reputation every {days} days (office bonus)", + "appoint_politicians": "Power to appoint: {offices}", + "set_regional_tax": "May set tax rates ({scope})", + "tax_scope": { + "local": "locally in jurisdiction", + "shire": "shire level", + "duchy": "duchy level", + "national": "nationwide" + }, + "free_lover_slots": "{count} additional affair(s) with no monthly upkeep", + "guard_protection": "Official guard / escort", + "court_immunity": "Limited judicial immunity in office matters", "generic": "Benefit ({code})" }, "regionLevels": { diff --git a/frontend/src/i18n/locales/es/falukant.json b/frontend/src/i18n/locales/es/falukant.json index e4baaa7..8bd74f3 100644 --- a/frontend/src/i18n/locales/es/falukant.json +++ b/frontend/src/i18n/locales/es/falukant.json @@ -1265,6 +1265,18 @@ "daily_salary": "Estipendio diario (una vez al día): unos {amount}", "tax_exemption": "Exención fiscal: {regions}", "tax_exemption_all": "Exención fiscal: todos los niveles regionales", + "reputation_periodic": "+{gain} reputación cada {days} días (bono de cargo)", + "appoint_politicians": "Derecho a nombrar: {offices}", + "set_regional_tax": "Puede fijar tipos impositivos ({scope})", + "tax_scope": { + "local": "localmente en la jurisdicción", + "shire": "nivel de condado (shire)", + "duchy": "nivel de ducado", + "national": "ámbito nacional" + }, + "free_lover_slots": "{count} relación(es) adicional(es) sin mantenimiento mensual", + "guard_protection": "Protección oficial / escolta", + "court_immunity": "Inmunidad judicial limitada en asuntos del cargo", "generic": "Ventaja ({code})" }, "regionLevels": { diff --git a/frontend/src/views/falukant/PoliticsView.vue b/frontend/src/views/falukant/PoliticsView.vue index 72143fe..f252424 100644 --- a/frontend/src/views/falukant/PoliticsView.vue +++ b/frontend/src/views/falukant/PoliticsView.vue @@ -198,6 +198,19 @@ export default { return t !== path ? t : k; }, + formatPoliticsAppointOfficeLabels(officeTrs) { + if (!Array.isArray(officeTrs) || !officeTrs.length) { + return ''; + } + return officeTrs + .map((tr) => { + const path = `falukant.politics.offices.${tr}`; + const t = this.$t(path); + return t !== path ? t : String(tr); + }) + .join(', '); + }, + formatPoliticsBenefitItem(b) { if (b == null) { return ''; @@ -216,6 +229,32 @@ export default { if (b.tr === 'daily_salary') { return this.$t('falukant.politics.benefits.daily_salary', { amount: b.params?.amount ?? '—' }); } + if (b.tr === 'reputation_periodic') { + return this.$t('falukant.politics.benefits.reputation_periodic', { + days: b.params?.intervalDays ?? '—', + gain: b.params?.gain ?? '—' + }); + } + if (b.tr === 'appoint_politicians' && Array.isArray(b.params?.officeTrs)) { + const labels = this.formatPoliticsAppointOfficeLabels(b.params.officeTrs); + return this.$t('falukant.politics.benefits.appoint_politicians', { offices: labels }); + } + if (b.tr === 'set_regional_tax') { + const sk = String(b.params?.scope || 'local'); + const scopePath = `falukant.politics.benefits.tax_scope.${sk}`; + const st = this.$t(scopePath); + const scopeLabel = st !== scopePath ? st : sk; + return this.$t('falukant.politics.benefits.set_regional_tax', { scope: scopeLabel }); + } + if (b.tr === 'free_lover_slots') { + return this.$t('falukant.politics.benefits.free_lover_slots', { count: b.params?.count ?? 1 }); + } + if (b.tr === 'guard_protection') { + return this.$t('falukant.politics.benefits.guard_protection'); + } + if (b.tr === 'court_immunity') { + return this.$t('falukant.politics.benefits.court_immunity'); + } if (b.tr === 'generic_benefit') { return this.$t('falukant.politics.benefits.generic', { code: b.params?.code || '' }); }