diff --git a/client/js/modals/format.js b/client/js/modals/format.js index b6a8ff1..7a1ff1b 100644 --- a/client/js/modals/format.js +++ b/client/js/modals/format.js @@ -70,18 +70,29 @@ export function percent(value) { return `${value < 0 ? "−" : "+"}${(Math.abs(value) * 100).toFixed(1)}%`; } -// Renders one news entry as text, resolving civ indices to names and demonyms. +// Renders one news entry as text. Government actions name the specific actors +// doing them — "French troops", "French scientists" — rather than the nation as +// a whole, and the victim or target is named in the people's form ("the +// Vietnamese"). export function describeNews(entry, civs) { const name = (index) => (civs[index] ? civs[index].name : `Nation ${index + 1}`); + // The attributive form of a nation, for putting before an actor noun. It + // falls back to the demonym, which doubles as an adjective for most of them; + // the few person-nouns (New Zealander, New Hampshirite) carry an explicit one. + const adjective = (index) => { + const civ = civs[index]; + if (!civ) return `Nation ${index + 1}`; + return civ.adjective || civ.demonym || civ.name; + }; const people = (index) => { const civ = civs[index]; - return civ && civ.demonym ? `The ${civ.demonym}` : name(index); + return civ && civ.demonym ? `the ${civ.demonym}` : name(index); }; switch (entry.type) { case "war": - return `${people(entry.actor)} declared war on ${name(entry.target)}`; + return `${adjective(entry.actor)} leaders have declared war on ${name(entry.target)}`; case "capture": - return `${people(entry.to)} captured ${entry.city} from ${name(entry.from)}`; + return `${adjective(entry.to)} troops have captured ${entry.city} from ${people(entry.from)}`; case "policy": { const policy = policyById(entry.policy); const policyName = policy ? policy.name : entry.policy; @@ -89,12 +100,12 @@ export function describeNews(entry, civs) { civs[entry.ethnicity] && civs[entry.ethnicity].demonym ? `the ${civs[entry.ethnicity].demonym}` : name(entry.ethnicity); - return `${name(entry.civ)} enacted ${policyName} for ${who}`; + return `${adjective(entry.civ)} lawmakers have enacted ${policyName} for ${who}`; } case "technology": return entry.repeatable - ? `${name(entry.civ)} improved ${entry.name}` - : `${name(entry.civ)} discovered ${entry.name}`; + ? `${adjective(entry.civ)} scientists have improved ${entry.name}` + : `${adjective(entry.civ)} scientists have discovered ${entry.name}`; case "join": return `${entry.player} took control of ${name(entry.civ)}`; default: diff --git a/tests/modals_test.js b/tests/modals_test.js index 0ed4348..47ddbd8 100644 --- a/tests/modals_test.js +++ b/tests/modals_test.js @@ -1,7 +1,7 @@ import { TestCase } from "./framework/test_case.js"; import { setupDom, teardownDom } from "./framework/dom.js"; import { CityModal, NationModal, ConfirmModal, NewsModal } from "../client/js/modals.js"; -import { buildingEffectText } from "../client/js/modals/format.js"; +import { buildingEffectText, describeNews } from "../client/js/modals/format.js"; import { BUILDINGS, GOVERNMENTS, TECHNOLOGIES, PROTO_UNITS } from "../shared/data.js"; export class CityModalTest extends TestCase { @@ -640,9 +640,13 @@ export class NewsModalTest extends TestCase { modal.show(news, civs); const items = env.$("#news-list .news-item"); this.assertSize(items, 2); - this.assertTrue(items.eq(0).find(".news-text").text().includes("The French captured Lyon from Britain")); + this.assertTrue( + items.eq(0).find(".news-text").text().includes("French troops have captured Lyon from the British") + ); this.assertTrue(items.eq(0).find(".news-date").text().includes("05:00")); - this.assertTrue(items.eq(1).find(".news-text").text().includes("The French declared war on Britain")); + this.assertTrue( + items.eq(1).find(".news-text").text().includes("French leaders have declared war on Britain") + ); modal.refresh([], civs); this.assertEqual(env.$("#news-list .news-item").length, 0); @@ -653,4 +657,41 @@ export class NewsModalTest extends TestCase { teardownDom(env); } } + + test_news_names_the_actors_doing_the_action() { + const civs = [ + { name: "France", demonym: "French" }, + { name: "Vietnam", demonym: "Vietnamese" }, + { name: "Aotearoa", demonym: "New Zealander", adjective: "New Zealand" }, + ]; + this.assertEqual( + describeNews({ type: "capture", city: "Saigon", from: 1, to: 0 }, civs), + "French troops have captured Saigon from the Vietnamese" + ); + this.assertEqual( + describeNews({ type: "technology", civ: 0, name: "Radio" }, civs), + "French scientists have discovered Radio" + ); + this.assertEqual( + describeNews({ type: "technology", civ: 0, name: "Railways", repeatable: true }, civs), + "French scientists have improved Railways" + ); + this.assertEqual( + describeNews({ type: "war", actor: 0, target: 1 }, civs), + "French leaders have declared war on Vietnam" + ); + this.assertEqual( + describeNews({ type: "policy", civ: 0, policy: "some_policy", ethnicity: 1 }, civs), + "French lawmakers have enacted some_policy for the Vietnamese" + ); + this.assertEqual( + describeNews({ type: "join", player: "Alice", civ: 1 }, civs), + "Alice took control of Vietnam" + ); + // Person-noun demonyms carry an explicit attributive form. + this.assertEqual( + describeNews({ type: "technology", civ: 2, name: "Radio" }, civs), + "New Zealand scientists have discovered Radio" + ); + } }