diff --git a/client/src/components/Studio/StudioTravelPanel.tsx b/client/src/components/Studio/StudioTravelPanel.tsx
index f17f745c3..30455cd42 100644
--- a/client/src/components/Studio/StudioTravelPanel.tsx
+++ b/client/src/components/Studio/StudioTravelPanel.tsx
@@ -4,8 +4,8 @@ import type {
BookBadgeVariant, BookElement, BookMapScope, BookMetric, BookPageSetup, BookTravelContext, JourneyStats,
} from '@trek/shared'
import {
- BOOK_METRICS, bookBadgeValue, bookCountryName, bookElementId as uid, bookTravelBadge, bookTravelCountries,
- bookTravelMap, bookTravelSize, bookTravelStats,
+ BOOK_METRICS, bookBadgeValue, bookCountryMark, bookCountryName, bookElementId as uid, bookTravelBadge,
+ bookTravelCountries, bookTravelMap, bookTravelSize, bookTravelStats,
} from '@trek/shared'
import { useStudioStore } from '../../store/studioStore'
import { PanelHead } from './StudioPanelHead'
@@ -170,16 +170,14 @@ export function StudioTravelPanel({
* What the book engine's factories read: the journey, its recorded way and
* the words in the reader's language. Every tile below is built by the
* factory the engine's `add` calls. Where it lands is the panel's own:
- * centred across the spread, where `add` takes the right page. Two words are
- * still the panel's too, so a mark placed here and one the assistant adds
- * can read differently: the country mark carries the name in `text`, where
- * the engine's value has it in `sub`, and the day mark says `dayWord`, where
- * the engine says `day` (the two differ in ja, ko, zh and zh-TW).
+ * centred across the spread, where `add` takes the right page. The day word
+ * is the engine's too, handed over as its template: `t` leaves the `{day}`
+ * in it untouched, and `bookDayText` puts the number there.
*/
const travel: BookTravelContext = {
locale, stats, path, entries: [],
labels: {
- day: t('journey.studio.dayWord'), stations: t('journey.studio.stations'), summary: t('journey.studio.summary'),
+ day: t('journey.studio.day'), stations: t('journey.studio.stations'), summary: t('journey.studio.summary'),
countries: t('journey.studio.countries'), distance: t('journey.studio.metric.distance'),
pros: t('journey.editor.pros'), cons: t('journey.editor.cons'),
},
@@ -564,7 +562,7 @@ export function StudioTravelPanel({
*/}
{stats.countries.slice(0, 12).map(c => {
- const el = badgeEl('country', { text: countryName(c.code), sub: '', code: c.code })
+ const el = badgeEl('country', bookCountryMark('country', c.code, countryName(c.code)))
return (
place(el)}>
diff --git a/client/src/pages/journeyStudio/useJourneyStudio.ts b/client/src/pages/journeyStudio/useJourneyStudio.ts
index db561e6f6..f720b10c0 100644
--- a/client/src/pages/journeyStudio/useJourneyStudio.ts
+++ b/client/src/pages/journeyStudio/useJourneyStudio.ts
@@ -296,6 +296,8 @@ export function useJourneyStudio() {
// line over a route somebody had actually walked.
path,
stationsLabel: t('journey.studio.stations'),
+ // The template as it stands ("DAY {day}", "{day}日目"): without params `t`
+ // leaves the `{day}` alone, and the layout puts each chip's number there.
dayLabel: t('journey.studio.day'),
summaryLabel: t('journey.studio.summary'),
countriesLabel: t('journey.studio.countries'),
diff --git a/client/tests/unit/studio/travelPanelEngine.test.tsx b/client/tests/unit/studio/travelPanelEngine.test.tsx
index aabd293ed..c27414e55 100644
--- a/client/tests/unit/studio/travelPanelEngine.test.tsx
+++ b/client/tests/unit/studio/travelPanelEngine.test.tsx
@@ -1,6 +1,6 @@
-// STTRV-001 to STTRV-006: the Travel panel places what the book engine's factories build, so a figure, a map, a
-// country or a mark added by hand is the element the assistant's `add` adds. Only the frame is the panel's own:
-// centred across the spread rather than on the right-hand page.
+// STTRV-001 to STTRV-006 and STTRV-009: the Travel panel places what the book engine's factories build, so a figure,
+// a map, a country or a mark added by hand is the element the assistant's `add` adds. Only the frame is the panel's
+// own: centred across the spread rather than on the right-hand page.
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { BookDocument, BookElement, BookFrame, BookTravelContext, JourneyStats } from '@trek/shared';
@@ -94,7 +94,7 @@ const travel: BookTravelContext = {
path: PATH,
entries: [],
labels: {
- day: 'journey.studio.dayWord',
+ day: 'journey.studio.day',
stations: 'journey.studio.stations',
summary: 'journey.studio.summary',
countries: 'journey.studio.countries',
@@ -117,13 +117,14 @@ const ANY_FRAME: BookFrame = { x: 0, y: 0, w: 1, h: 1 };
/** An element apart from what the panel decides itself: the id it mints and where the frame goes. */
const content = (el: BookElement) => ({ ...el, id: '', frame: ANY_FRAME });
-function open(journey: JourneyStats = stats) {
+/** The panel on a journey, its words answered by `t`: the key itself unless a case says otherwise. */
+function open(journey: JourneyStats = stats, t: (k: string) => string = (k) => k) {
return render(
k}
+ t={t}
locale="en"
canEdit={false}
onToggleStop={async () => true}
@@ -225,7 +226,7 @@ describe('the Travel panel builds through the book engine', () => {
expect(screen.queryByRole('button', { name: 'journey.studio.dateMark' })).toBeNull();
fireEvent.click(card('journey.studio.dayMark'));
- expect(placed()).toMatchObject({ variant: 'day', text: 'journey.studio.dayWord 1', style: 'chip' });
+ expect(placed()).toMatchObject({ variant: 'day', text: 'journey.studio.day 1', style: 'chip' });
const [outline, mark] = screen.getAllByRole('button', { name: 'Iceland' });
fireEvent.click(outline);
@@ -237,4 +238,20 @@ describe('the Travel panel builds through the book engine', () => {
fireEvent.click(mark);
expect(placed()).toMatchObject({ kind: 'badge', variant: 'country', text: 'Iceland', sub: '', code: 'IS' });
});
+
+ it('STTRV-009: places a country mark as the engine makes one, and the day where its word puts it', () => {
+ open(stats, (k) => (k === 'journey.studio.day' ? '{day}日目' : k));
+
+ const [, mark] = screen.getAllByRole('button', { name: 'Iceland' });
+ fireEvent.click(mark);
+ const value = bookBadgeValue(travel, 'country', null);
+ expect(value).toEqual({ text: 'Iceland', sub: '', code: 'IS' });
+ if (!value) throw new Error('the engine has no country mark for this journey');
+ const engine = bookTravelBadge({ id: 'bd', frame: ANY_FRAME, variant: 'country', value });
+ expect(content(placed())).toEqual(content(engine));
+ expect(placed().frame).toEqual(spreadCentre(bookTravelSize('badge', page, { variant: 'country' })));
+
+ fireEvent.click(card('journey.studio.dayMark'));
+ expect(placed()).toMatchObject({ variant: 'day', text: '1日目' });
+ });
});
diff --git a/server/tests/unit/nest/journey-book-edit.book-level.test.ts b/server/tests/unit/nest/journey-book-edit.book-level.test.ts
index b770c334f..9726ec333 100644
--- a/server/tests/unit/nest/journey-book-edit.book-level.test.ts
+++ b/server/tests/unit/nest/journey-book-edit.book-level.test.ts
@@ -214,7 +214,7 @@ describe('what the engine reads', () => {
expect(travel.entries.map((e) => [e.id, e.title, e.mood])).toEqual([[entry.id, 'Alfama', 'amazing']]);
expect(travel.locale).toBe('de-DE');
- expect(travel.labels).toMatchObject({ day: 'TAG', stations: 'Stationen', pros: 'Pro', cons: 'Contra' });
+ expect(travel.labels).toMatchObject({ day: 'TAG {day}', stations: 'Stationen', pros: 'Pro', cons: 'Contra' });
expect(travel.path).toEqual([]);
expect((await edit.wordsFor(owner.id)).locale).toBe('de-DE');
});
diff --git a/server/tests/unit/nest/journey/book-travel-labels.test.ts b/server/tests/unit/nest/journey/book-travel-labels.test.ts
index b91a935d4..046f5b847 100644
--- a/server/tests/unit/nest/journey/book-travel-labels.test.ts
+++ b/server/tests/unit/nest/journey/book-travel-labels.test.ts
@@ -19,12 +19,17 @@ describe('the book words', () => {
it('JBLBL-002: the words come from the locale table, in the locale the book formats in', async () => {
const french = await loadBookWords('fr');
expect(french.locale).toBe('fr-FR');
- expect(french.labels).toMatchObject({ day: 'JOUR', stations: 'Étapes', summary: 'Résumé', countries: 'Pays' });
+ expect(french.labels).toMatchObject({
+ day: 'JOUR {day}',
+ stations: 'Étapes',
+ summary: 'Résumé',
+ countries: 'Pays',
+ });
const english = await loadBookWords('xx');
expect(english).toEqual({
locale: 'en-US',
labels: {
- day: 'DAY',
+ day: 'DAY {day}',
stations: 'Stations',
summary: 'Summary',
countries: 'Countries',
diff --git a/shared/src/book/auto/auto-elements.ts b/shared/src/book/auto/auto-elements.ts
index 45c1438e5..253ef23ec 100644
--- a/shared/src/book/auto/auto-elements.ts
+++ b/shared/src/book/auto/auto-elements.ts
@@ -256,10 +256,12 @@ export function coordsMark(
return badgeEl(newId, frame, 'coords', { text: formatBookCoords(lat, lng) }, { style: 'plain', textScale: 0.7 });
}
-/** The "DAY 5" chip. Small, solid, and the only filled shape on most pages. */
-export function dayChip(newId: NewId, frame: BookFrame, day: number | null | undefined, label: string) {
- if (!day) return null;
- return badgeEl(newId, frame, 'day', { text: `${label} ${day}` }, { style: 'chip', textScale: 0.75 });
+/**
+ * The "DAY 5" chip, its words made by `bookDayText`. Small, solid, and the
+ * only filled shape on most pages.
+ */
+export function dayChip(newId: NewId, frame: BookFrame, text: string) {
+ return badgeEl(newId, frame, 'day', { text }, { style: 'chip', textScale: 0.75 });
}
/** A rule along the foot with a dot showing how far into the journey this is. */
diff --git a/shared/src/book/auto/auto-spreads.ts b/shared/src/book/auto/auto-spreads.ts
index afdad51ac..b36f42b53 100644
--- a/shared/src/book/auto/auto-spreads.ts
+++ b/shared/src/book/auto/auto-spreads.ts
@@ -1,4 +1,5 @@
import { formatBookDate } from '../book-text';
+import { bookDayText } from '../book-travel';
import type { BookElement, BookSpread } from '../book.schema';
import {
ACCENT,
@@ -138,8 +139,7 @@ function footMarks(ctx: EntryContext, els: BookElement[], at: { x: number; y: nu
const t = total && total > 1 ? Math.min(1, Math.max(0, (day - 1) / (total - 1))) : 0;
const cw = 24;
const cx = Math.min(x + w - cw, Math.max(x, x + w * t - cw / 2));
- const chip = dayChip(newId, { x: cx, y: y - 11, w: cw, h: 7 }, day, input.dayLabel);
- if (chip) els.push(chip);
+ els.push(dayChip(newId, { x: cx, y: y - 11, w: cw, h: 7 }, bookDayText(input.dayLabel, day)));
}
/** The story, bound to the entry, set in a column. */
diff --git a/shared/src/book/auto/auto-template-apply.ts b/shared/src/book/auto/auto-template-apply.ts
index ff007008f..fd9f7cc6a 100644
--- a/shared/src/book/auto/auto-template-apply.ts
+++ b/shared/src/book/auto/auto-template-apply.ts
@@ -1,4 +1,5 @@
import { bookCoordValue, formatBookCoords, formatBookDate } from '../book-text';
+import { bookCountryMark, bookCountryName, bookDayText } from '../book-travel';
import {
MAX_TEXT_LENGTH,
type BookBadgeElement,
@@ -32,7 +33,7 @@ export interface AutoTemplateContext {
page: BookPageSetup;
locale: string;
stats: JourneyStats | null;
- /** Already translated, because this module has no translation context. */
+ /** The day mark's label ("DAY {day}"), already translated, because this module has no translation context. */
dayLabel: string;
newId: NewId;
}
@@ -143,14 +144,11 @@ function fillText(
* answer keeps the one it was drawn with.
*/
function fillBadge(el: BookBadgeElement, entry: AutoEntry, ctx: AutoTemplateContext): void {
- if (el.variant === 'day' && entry.dayNumber) el.text = `${ctx.dayLabel} ${entry.dayNumber}`;
+ if (el.variant === 'day' && entry.dayNumber) el.text = bookDayText(ctx.dayLabel, entry.dayNumber);
if (el.variant === 'coords' && entry.lat != null && entry.lng != null) {
el.text = formatBookCoords(entry.lat, entry.lng);
}
- if ((el.variant === 'flag' || el.variant === 'country') && entry.country) {
- el.code = entry.country;
- el.text = countryName(entry.country, ctx).toUpperCase();
- }
+ if ((el.variant === 'flag' || el.variant === 'country') && entry.country) fillCountry(el, entry.country, ctx);
if (el.variant === 'date' && entry.date) {
const d = new Date(`${entry.date}T00:00:00`);
if (!Number.isNaN(d.getTime())) {
@@ -163,11 +161,20 @@ function fillBadge(el: BookBadgeElement, entry: AutoEntry, ctx: AutoTemplateCont
}
}
-function countryName(code: string, ctx: AutoTemplateContext): string {
- const known = ctx.stats?.countries.find((c) => c.code === code);
- try {
- return new Intl.DisplayNames([ctx.locale], { type: 'region' }).of(code) ?? known?.name ?? code;
- } catch {
- return known?.name ?? code;
+/**
+ * A country mark takes the rule the engine and the Travel panel follow
+ * (`bookCountryMark`): the name as its words, and the drawn words beside them
+ * go. A flag keeps the place its template set the name in: in capitals as its
+ * words, the way the auto layout sets a flag, or beside the flag (`sub`) where
+ * it was placed from the panel, so no drawn name is left next to the new one.
+ */
+function fillCountry(el: BookBadgeElement, code: string, ctx: AutoTemplateContext): void {
+ const name = bookCountryName(code, ctx.locale, ctx.stats);
+ if (el.variant === 'country') {
+ Object.assign(el, bookCountryMark('country', code, name));
+ return;
}
+ el.code = code;
+ if (el.text || !el.sub) el.text = name.toUpperCase();
+ else el.sub = name;
}
diff --git a/shared/src/book/auto/auto-types.ts b/shared/src/book/auto/auto-types.ts
index ddf7fbc90..c05857393 100644
--- a/shared/src/book/auto/auto-types.ts
+++ b/shared/src/book/auto/auto-types.ts
@@ -67,7 +67,11 @@ export interface AutoInput {
stats: JourneyStats | null;
/** The heading over a list of bare stops, already translated. */
stationsLabel: string;
- /** The word before a day number on a chip ("DAY 5"). Already translated. */
+ /**
+ * The words of a day chip, already translated: `{day}` stands where the
+ * number goes ("DAY {day}", "{day}日目"), and a word without it is followed
+ * by the number ("DAY 5"). See `bookDayText`.
+ */
dayLabel: string;
/**
* The real geometry of the route, when the journey has any (GPX and KML
diff --git a/shared/src/book/book-element-ops.spec.ts b/shared/src/book/book-element-ops.spec.ts
index 56098ed61..efff436e6 100644
--- a/shared/src/book/book-element-ops.spec.ts
+++ b/shared/src/book/book-element-ops.spec.ts
@@ -183,11 +183,20 @@ describe('add', () => {
});
it('BOOKEL-017: a flag or country mark asked for by its own code is labelled with that country', () => {
- expect(added({ kind: 'badge', variant: 'flag' })).toMatchObject({ code: 'IS', sub: 'Iceland' });
- expect(added({ kind: 'badge', variant: 'flag', code: 'PT' })).toMatchObject({ code: 'PT', sub: 'Portugal' });
+ // A flag sets the name beside the flag (sub); a country mark has it as its words, the outline beside them.
+ expect(added({ kind: 'badge', variant: 'flag' })).toMatchObject({ code: 'IS', text: '', sub: 'Iceland' });
+ const portugal = added({ kind: 'badge', variant: 'flag', code: 'PT' });
+ expect(portugal).toMatchObject({ code: 'PT', text: '', sub: 'Portugal' });
+ expect(added({ kind: 'badge', variant: 'country' })).toMatchObject({ code: 'IS', text: 'Iceland', sub: '' });
const norway = added({ kind: 'badge', variant: 'country', entryId: 7, code: 'NO' });
- expect(norway).toMatchObject({ code: 'NO', sub: 'Norway' });
+ expect(norway).toMatchObject({ code: 'NO', text: 'Norway', sub: '' });
expect(added({ kind: 'badge', variant: 'flag', code: 'PT', sub: 'Lisboa' })).toMatchObject({ sub: 'Lisboa' });
+ const lisboa = added({ kind: 'badge', variant: 'country', code: 'PT', text: 'Lisboa' });
+ expect(lisboa).toMatchObject({ code: 'PT', text: 'Lisboa', sub: '' });
+ // Its own code names the country on a journey whose figures name none; without one there is nothing to show.
+ const noCountries: BookOpsContext = { ...ctx, travel: { ...travel, stats: { ...stats, countries: [] } } };
+ expect(added({ kind: 'badge', variant: 'flag', code: 'PT' }, noCountries)).toMatchObject({ sub: 'Portugal' });
+ expect(addRefusal({ kind: 'badge', variant: 'flag' }, noCountries)).toBe('text_required');
// Another mark's code is its own (a mood, the weather) and names no country.
expect(added({ kind: 'badge', variant: 'mood', code: 'good', text: 'Good' })).toMatchObject({ sub: '' });
});
diff --git a/shared/src/book/book-ops-elements.ts b/shared/src/book/book-ops-elements.ts
index a4a89d21b..39447300f 100644
--- a/shared/src/book/book-ops-elements.ts
+++ b/shared/src/book/book-ops-elements.ts
@@ -1,5 +1,5 @@
import { relayoutSpread } from './auto/auto-book';
-import type { AutoInput } from './auto/auto-types';
+import type { AutoEntry, AutoInput } from './auto/auto-types';
import { bookElementPropsSchema, type BOOK_ELEMENT_OP_SCHEMAS, type BookNewElement } from './book-element-ops.schema';
import { bookGridElements, defaultBookGridBox, findBookGrid } from './book-grids';
import {
@@ -15,6 +15,7 @@ import {
} from './book-ops-core';
import {
bookBadgeValue,
+ bookCountryMark,
bookCountryName,
bookEntryListItems,
bookTravelBadge,
@@ -334,7 +335,7 @@ function newBadge(spec: Extract, id: string, at:
if (spec.entryId !== undefined && !entry) {
throw failOp('entry_not_found', `entry ${spec.entryId} is not in this journey`, env.opIndex);
}
- const resolved = travel ? bookBadgeValue(travel, spec.variant, entry) : null;
+ const resolved = travel ? badgeValue(spec, travel, entry) : null;
const ownCode = spec.variant === 'mood' ? entry?.mood : spec.variant === 'weather' ? entry?.weather : undefined;
const text = spec.text ?? resolved?.text;
if (text === undefined || (!text && !resolved)) {
@@ -343,29 +344,25 @@ function newBadge(spec: Extract, id: string, at:
}
const value = {
text,
- sub: spec.sub ?? badgeSub(spec, resolved?.sub, resolved?.code, travel) ?? '',
+ sub: spec.sub ?? resolved?.sub ?? '',
code: spec.code ?? ownCode ?? resolved?.code ?? null,
};
const frame = at(bookTravelSize('badge', env.page, { variant: spec.variant }));
return bookTravelBadge({ id, frame, variant: spec.variant, value });
}
-/** The marks whose code names a country. */
-const COUNTRY_MARKS: ReadonlySet = new Set(['flag', 'country']);
-
/**
- * A flag or country mark asked for by its own code is labelled with that
- * country; the journey's value names its first country, the one a mark
- * without a code shows.
+ * The words the journey gives a mark. A flag or country mark asked for by its
+ * own code is labelled with that country, its name where the mark's rule puts
+ * it (`bookCountryMark`); without a code it shows the entry's country or the
+ * journey's first.
*/
-function badgeSub(
- spec: Extract,
- sub: string | undefined,
- code: string | null | undefined,
- travel: BookTravelContext | undefined,
-): string | undefined {
- if (!travel || !spec.code || !COUNTRY_MARKS.has(spec.variant)) return sub;
- return spec.code === code ? sub : bookCountryName(spec.code, travel.locale, travel.stats);
+function badgeValue(spec: Extract, travel: BookTravelContext, entry: AutoEntry | null) {
+ const { variant, code } = spec;
+ if (code && (variant === 'flag' || variant === 'country')) {
+ return bookCountryMark(variant, code, bookCountryName(code, travel.locale, travel.stats));
+ }
+ return bookBadgeValue(travel, variant, entry);
}
function newList(spec: Extract, id: string, at: At, env: ElementOpEnv): BookElement {
diff --git a/shared/src/book/book-travel.spec.ts b/shared/src/book/book-travel.spec.ts
index ca59aca41..6bff1b6dc 100644
--- a/shared/src/book/book-travel.spec.ts
+++ b/shared/src/book/book-travel.spec.ts
@@ -1,7 +1,11 @@
+import { buildBook } from './auto/auto-book';
+import { applyAutoTemplate } from './auto/auto-template-apply';
import type { AutoEntry } from './auto/auto-types';
import {
bookBadgeValue,
+ bookCountryMark,
bookCountryName,
+ bookDayText,
bookEntryListItems,
bookTravelCountries,
bookTravelMap,
@@ -13,7 +17,7 @@ import {
routeFor,
type BookTravelContext,
} from './book-travel';
-import { bookElementSchema, bookPageSetupSchema, type BookElement } from './book.schema';
+import { bookBadgeElementSchema, bookElementSchema, bookPageSetupSchema, type BookElement } from './book.schema';
import type { JourneyStats } from './journey-stats.schema';
import { describe, expect, it } from 'vitest';
@@ -173,4 +177,73 @@ describe('book travel', () => {
expect(refreshPatch(map, moved)).toMatchObject({ countries: ['IS'] });
expect(isStale(figures, null)).toBe(false);
});
+
+ it('BOOKTRV-008: puts the day number where the label says, and after a label that does not say', () => {
+ expect(bookDayText('DAY {day}', 5)).toBe('DAY 5');
+ expect(bookDayText('{day}日目', 3)).toBe('3日目');
+ expect(bookDayText('第{day}天', 12)).toBe('第12天');
+ expect(bookDayText('DAY', 5)).toBe('DAY 5');
+ const japanese: BookTravelContext = { ...ctx, labels: { ...ctx.labels, day: '{day}日目' } };
+ expect(bookBadgeValue(japanese, 'day', entry)).toEqual({ text: '5日目', sub: '', code: null });
+ expect(bookBadgeValue(japanese, 'day', null)?.text).toBe('1日目');
+ // The auto layout's chip at the foot of an entry says it the same way.
+ const doc = buildBook({
+ locale: 'ja',
+ title: 'North',
+ subtitle: null,
+ coverPhotoId: null,
+ entries: [{ ...entry, story: 'Whales off the harbour.' }],
+ page: bookPageSetupSchema.parse({}),
+ stats,
+ stationsLabel: 'Stations',
+ dayLabel: '{day}日目',
+ summaryLabel: 'Summary',
+ countriesLabel: 'Countries',
+ templates: [],
+ });
+ const elements = doc.spreads.flatMap((s) => s.elements);
+ const days = elements.flatMap((e) => (e.kind === 'badge' && e.variant === 'day' ? [e.text] : []));
+ expect(days).toEqual(['5日目']);
+ });
+
+ it('BOOKTRV-009: names the country as the words of a country mark and beside the flag of a flag mark', () => {
+ expect(bookBadgeValue(ctx, 'country', entry)).toEqual({ text: 'Iceland', sub: '', code: 'IS' });
+ expect(bookBadgeValue({ ...ctx, locale: 'de' }, 'country', null)).toEqual({ text: 'Island', sub: '', code: 'IS' });
+ expect(bookBadgeValue(ctx, 'flag', null)).toEqual({ text: '', sub: 'Iceland', code: 'IS' });
+ expect(bookBadgeValue({ ...ctx, stats: null }, 'country', null)).toBeNull();
+ expect(bookCountryMark('country', 'PT', 'Portugal')).toEqual({ text: 'Portugal', sub: '', code: 'PT' });
+ expect(bookCountryMark('flag', 'PT', 'Portugal')).toEqual({ text: '', sub: 'Portugal', code: 'PT' });
+ });
+
+ it('BOOKTRV-010: fills the day and country marks of a template by the same rules, a flag where it was drawn', () => {
+ const frame = { x: 0.1, y: 0.1, w: 0.2, h: 0.05 };
+ const mark = (variant: 'day' | 'country' | 'flag', text: string, sub: string) =>
+ bookBadgeElementSchema.parse({ id: variant, kind: 'badge', variant, text, sub, frame });
+ // Flags set the auto layout's way (the name as its words, or no words yet) and one placed from the panel.
+ const template = {
+ id: 'marks',
+ background: null,
+ elements: [
+ mark('day', 'DAY 1', ''),
+ mark('country', 'FRANCE', 'Paris'),
+ mark('flag', 'FRANCE', ''),
+ mark('flag', '', ''),
+ mark('flag', '', 'France'),
+ ],
+ };
+ const fill = {
+ page: bookPageSetupSchema.parse({}),
+ locale: 'en',
+ stats,
+ dayLabel: '{day}日目',
+ newId: (p: string) => p,
+ };
+ expect(applyAutoTemplate(template, entry, fill).elements).toMatchObject([
+ { variant: 'day', text: '5日目' },
+ { variant: 'country', text: 'Iceland', sub: '', code: 'IS' },
+ { variant: 'flag', text: 'ICELAND', sub: '', code: 'IS' },
+ { variant: 'flag', text: 'ICELAND', sub: '', code: 'IS' },
+ { variant: 'flag', text: '', sub: 'Iceland', code: 'IS' },
+ ]);
+ });
});
diff --git a/shared/src/book/book-travel.ts b/shared/src/book/book-travel.ts
index 760903b32..ae63752af 100644
--- a/shared/src/book/book-travel.ts
+++ b/shared/src/book/book-travel.ts
@@ -121,6 +121,7 @@ export function refreshPatch(el: BookElement, stats: JourneyStats): Partial