@museumwnf/viewer-core 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -415,13 +415,14 @@ viewer-layout's.
415
415
 
416
416
  | Export | Meaning |
417
417
  | --- | --- |
418
- | `useRecordSheet(record, { entity, translations, attribution })` | what `useRecordLanguage` returns, plus `text` (the record's translation in the active language, falling back to English), `ready` (the entity and every named related entity loaded, in the active language and in English), `terms` (`[{ id, word, definition, spellings }]` for the record's `glossary_ids`, in the active language), `glossary` (the `[{ id, spelling }]` list the renderers take) and `attribution` — when `attribution: ['author', 'copy_editor']` names fields that are proper names, the first other language of the record that has them supplies them (`{ from, author, … }`), because the importer files some records' credits on one language only. |
418
+ | `useRecordSheet(record, { entity, translations, attribution })` | what `useRecordLanguage` returns, plus `text` (the record's translation in the active language, falling back to English), `ready` (the entity and every named related entity loaded, in the active language and in English), `terms` (`[{ id, word, definition, spellings }]` for the record's `glossary_ids` that have a row in the active language — none through another language), `glossary` (the `[{ id, spelling }]` list the renderers take) and `attribution` — when `attribution: ['author', 'copy_editor']` names fields that are proper names, the first other language of the record that has them supplies them (`{ from, author, … }`), for a site that asks; no platform sheet does, since legacy printed a language's own credits only. |
419
419
  | `sheetRows(spec, ctx)` | `[{ key, label, render, value, html }]` from `[{ key, label, value, render, when, join }]`: `value` a function of `ctx` or the name of a field of `ctx.text`; `render` one of `inline`, `block`, `plain` (through the pipeline, with `ctx.glossary`), `link`, `custom` (handed back for a slot); `when(ctx)` gates the row; empty values are dropped. "Materials/techniques" on one site and "Type" on another for the same field are both legacy facts, and both are a spec entry. |
420
420
  | `useGlossaryPopup(entries)` | `{ active, onClick, close }` — one delegated click on the sheet's container answers the term rendered as `.gloss-term` |
421
- | `searchGlossary(input, language)` | the terms whose spelling starts with the input, for a glossary search box |
421
+ | `searchGlossary(input, language)` | the terms with a row in `language` whose spelling starts with the input, for a glossary search box |
422
422
  | `glossaryTermsForText(text, language, { entity })` | `glossaryTermsFor`'s counterpart for a text with no `glossary_ids` column — a dynasty history, a theme essay: `[{ id, word, definition, spellings }]` for every glossary term whose spelling occurs in `text`, matched the same word-bounded way `md`/`renderBlock` highlight it. Pass the result through `glossaryEntries` for the `glossary` option the renderers take, the same as `terms`/`glossary` from `useRecordSheet` |
423
423
  | `citation({ author, name, project, publisher, year, permalink, inWord })` | the sentence under a sheet, assembled from parts in order rather than written into a text with holes; `permalink` is the page's own address, e.g. from [`sourceUrl()`](#the-declaration-outside-a-component) |
424
- | `relatedRecords(record, { entity, language })` / `useRelatedRecords` | `{ inPackage: [{ reference, record, justification }], outside: [reference] }` — a reference the package does not hold stays a reference; nothing is dropped and nothing invented |
424
+ | `relatedRecords(record, { entity, language })` / `useRelatedRecords` | `{ inPackage: [{ reference, record, justification }], outside: [reference] }`, the justification in `language` only — a reference the package does not hold stays a reference; nothing is dropped and nothing invented |
425
+ | `pictureCredits(picture, language)` | `{ photographer, copyright }` of an item picture in `language`, from the package's per-language `photographers` / `copyrights` maps, as legacy kept and printed them; the flat `photographer` / `copyright` of a package exported before the maps |
425
426
  | `timelineLinkFor(record, { name, keys, country, round })` | the route location of the timeline results for the record's country and dates, or null |
426
427
 
427
428
  ### Collection trees
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@museumwnf/viewer-core",
3
- "version": "2.7.0",
3
+ "version": "2.9.0",
4
4
  "description": "Application engine for MWNF websites: turns a dataset.config.js into a mounted Vue application.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -22,7 +22,7 @@ import { useExhibitionSheet } from './useExhibitionSheet.js'
22
22
  * The exhibition's item-page spec, over its data layer's `data` and its
23
23
  * timeline layer's `timeline` (`useExhibitionTimeline`).
24
24
  *
25
- * Reads `data.defaultLang`, `data.dynastyById`, `data.isExploreRecord`,
25
+ * Reads `data.dynastyById`, `data.isExploreRecord`,
26
26
  * `data.isHiddenPartner`, `data.labelOf`, `data.partnerById`,
27
27
  * `data.partnerRoute`, `data.translations`, what `useExhibitionSheet` reads,
28
28
  * and `timeline.countryIdForCode`, `timeline.hasTimeline`,
@@ -30,7 +30,7 @@ import { useExhibitionSheet } from './useExhibitionSheet.js'
30
30
  */
31
31
  export function useExhibitionItemDetail(data, timeline) {
32
32
  const {
33
- defaultLang, dynastyById, isExploreRecord, isHiddenPartner, labelOf, partnerById, partnerRoute, translations,
33
+ dynastyById, isExploreRecord, isHiddenPartner, labelOf, partnerById, partnerRoute, translations,
34
34
  } = data
35
35
  const { countryIdForCode, hasTimeline, timelineEvents, timelineSpec } = timeline
36
36
  const { countries: timelineCountries, findEvents } = timelineEvents
@@ -41,7 +41,7 @@ export function useExhibitionItemDetail(data, timeline) {
41
41
  }
42
42
 
43
43
  function dynastyTr(dynasty, language) {
44
- return translations('dynasties', language)[dynasty.id] ?? translations('dynasties', defaultLang)[dynasty.id] ?? {}
44
+ return translations('dynasties', language)[dynasty.id] ?? {}
45
45
  }
46
46
 
47
47
  // Legacy popped out a dynasty only when it had a history to show.
@@ -1,3 +1,5 @@
1
+ import { pictureCredits } from '../../record/derivations.js'
2
+
1
3
  // The partner list and profile, as specs. The grouping, the A–Z toggle, the
2
4
  // tab strip's mechanics, the language switch and the media gallery are the
3
5
  // platform's (`PartnerListView`/`RecordView`); what stays here is an
@@ -11,12 +13,12 @@
11
13
  /**
12
14
  * The exhibition's partner specs, over its data layer's `data`.
13
15
  *
14
- * Reads `data.defaultLang`, `data.isHiddenPartner`, `data.labelOf`,
16
+ * Reads `data.isHiddenPartner`, `data.labelOf`,
15
17
  * `data.partnerRoute`. `config` carries nothing today.
16
18
  */
17
19
  export function useExhibitionPartner(data, config = {}) {
18
20
  void config
19
- const { defaultLang, isHiddenPartner, labelOf, partnerRoute } = data
21
+ const { isHiddenPartner, labelOf, partnerRoute } = data
20
22
 
21
23
  // ── The list (Partners.vue) ─────────────────────────────────────────
22
24
  //
@@ -52,9 +54,8 @@ export function useExhibitionPartner(data, config = {}) {
52
54
  .map((p) => ({
53
55
  url: p.url,
54
56
  alt: labelOf('partners', record.id),
55
- caption: p.captions?.[language] ?? p.captions?.[defaultLang] ?? '',
56
- photographer: p.photographer ?? '',
57
- copyright: p.copyright ?? '',
57
+ caption: p.captions?.[language] ?? '',
58
+ ...pictureCredits(p, language),
58
59
  })),
59
60
  }
60
61
 
@@ -27,7 +27,7 @@
27
27
  // dynasties stand in when it has none, and keep their own popouts
28
28
  // (decision D5, inventory-app#2233).
29
29
 
30
- import { artistLine, recordArtist } from '../../record/derivations.js'
30
+ import { artistLine, pictureCredits, recordArtist } from '../../record/derivations.js'
31
31
 
32
32
  /**
33
33
  * The exhibition's item-sheet spec, over its data layer's `data`.
@@ -44,7 +44,7 @@ export function useExhibitionSheet(data, config = {}) {
44
44
 
45
45
  const dynastyNames = (c) =>
46
46
  (c.record.dynasty_ids ?? [])
47
- .map((id) => translations('dynasties', c.language)[id]?.name ?? translations('dynasties', defaultLang)[id]?.name ?? '')
47
+ .map((id) => translations('dynasties', c.language)[id]?.name ?? '')
48
48
  .filter(Boolean)
49
49
  .join(', ')
50
50
 
@@ -87,13 +87,8 @@ export function useExhibitionSheet(data, config = {}) {
87
87
  const itemSheet = {
88
88
  entity: 'items',
89
89
  translations: ['glossary', 'dynasties', 'partners'],
90
- // Attribution names are language-independent, but the importer files
91
- // `author` / `copy_editor` for EPM records on the Arabic row only (a
92
- // known gap, recorded in the exporter's README). Legacy printed them on
93
- // every sheet, so the platform reads them off another row when the
94
- // active one has neither — the one place a page reads across
95
- // languages, and only for proper names.
96
- attribution: ['author', 'copy_editor'],
90
+ // Credits are the sheet language's own: dxa-api read them from that
91
+ // language's authors_objects rows and printed nothing when it had none.
97
92
  fields,
98
93
  layout: 'list',
99
94
  shortDescription: 'short_description',
@@ -104,9 +99,8 @@ export function useExhibitionSheet(data, config = {}) {
104
99
  .map((p) => ({
105
100
  url: p.url,
106
101
  alt: labelOf('items', record.id),
107
- caption: p.captions?.[language] ?? p.captions?.[defaultLang] ?? '',
108
- photographer: p.photographer ?? '',
109
- copyright: p.copyright ?? '',
102
+ caption: p.captions?.[language] ?? '',
103
+ ...pictureCredits(p, language),
110
104
  })),
111
105
  citation: {
112
106
  permalink: false,
@@ -121,11 +115,13 @@ export function useExhibitionSheet(data, config = {}) {
121
115
  variant: 'grid',
122
116
  heading: 'exhibition.related.objects',
123
117
  actionLabel: 'catalogue.results.seeDatabaseEntry',
124
- record: ({ record: other, justification }) => ({
118
+ // In the sheet's language when the related record has it, in the main
119
+ // language otherwise, as legacy's relatedObjectsMainLanguage did.
120
+ record: ({ record: other, justification }, { language = defaultLang } = {}) => ({
125
121
  id: other.id,
126
122
  image: other.images?.[0]?.url ?? '',
127
123
  imageAlt: labelOf('items', other.id),
128
- name: mdInline(tr('items', other.id, defaultLang).name ?? other.internal_name ?? ''),
124
+ name: mdInline(translations('items', language)[other.id]?.name ?? tr('items', other.id, defaultLang).name ?? other.internal_name ?? ''),
129
125
  meta: [labelOf('countries', other.country_id), justification ? mdStrip(justification) : ''].filter(Boolean),
130
126
  to: itemRoute(other),
131
127
  }),
@@ -25,13 +25,13 @@ import { useGallerySheet } from './useGallerySheet.js'
25
25
  * The gallery's item-page spec, over its data layer's `data` and its
26
26
  * timeline layer's `timeline` (`useGalleryTimeline`).
27
27
  *
28
- * Reads `data.defaultLang`, `data.dynastyById`, `data.labelOf`,
28
+ * Reads `data.dynastyById`, `data.labelOf`,
29
29
  * `data.partnerById`, `data.partnerRoute`, `data.translations`, what
30
30
  * `useGallerySheet` reads, and `timeline.countryIdForCode`,
31
31
  * `timeline.timelineEvents` (`countries`, `findEvents`).
32
32
  */
33
33
  export function useGalleryItemDetail(data, timeline) {
34
- const { defaultLang, dynastyById, labelOf, partnerById, partnerRoute, translations } = data
34
+ const { dynastyById, labelOf, partnerById, partnerRoute, translations } = data
35
35
  const { countryIdForCode, timelineEvents } = timeline
36
36
  const { countries: timelineCountries, findEvents } = timelineEvents
37
37
  const { itemSheet: platformSheet } = useGallerySheet(data)
@@ -43,8 +43,9 @@ export function useGalleryItemDetail(data, timeline) {
43
43
  return partner ? partnerRoute(partner) : null
44
44
  }
45
45
 
46
+ // The sheet language's own: dxa-api returned a dynasty only in it.
46
47
  function dynastyTranslation(dynasty, language) {
47
- return translations('dynasties', language)[dynasty.id] ?? translations('dynasties', defaultLang)[dynasty.id] ?? {}
48
+ return translations('dynasties', language)[dynasty.id] ?? {}
48
49
  }
49
50
 
50
51
  // Legacy popped out a dynasty only when it had a history to show.
@@ -27,7 +27,7 @@
27
27
  // dynasties stand in when it has none, and keep their own popouts
28
28
  // (decision D5, inventory-app#2233).
29
29
 
30
- import { artistLine, recordArtist } from '../../record/derivations.js'
30
+ import { artistLine, pictureCredits, recordArtist } from '../../record/derivations.js'
31
31
 
32
32
  /**
33
33
  * The gallery's item-sheet spec, over its data layer's `data`.
@@ -44,7 +44,7 @@ export function useGallerySheet(data, config = {}) {
44
44
 
45
45
  const dynastyNames = (c) =>
46
46
  (c.record.dynasty_ids ?? [])
47
- .map((id) => translations('dynasties', c.language)[id]?.name ?? translations('dynasties', defaultLang)[id]?.name ?? '')
47
+ .map((id) => translations('dynasties', c.language)[id]?.name ?? '')
48
48
  .filter(Boolean)
49
49
  .join(', ')
50
50
 
@@ -87,13 +87,8 @@ export function useGallerySheet(data, config = {}) {
87
87
  const itemSheet = {
88
88
  entity: 'items',
89
89
  translations: ['glossary', 'dynasties', 'partners'],
90
- // Attribution names are language-independent, but the importer files
91
- // `author` / `copy_editor` for EPM records on the Arabic row only (a
92
- // known gap, recorded in the exporter's README). Legacy printed them on
93
- // every sheet, so the platform reads them off another row when the
94
- // active one has neither — the one place a page reads across
95
- // languages, and only for proper names.
96
- attribution: ['author', 'copy_editor'],
90
+ // Credits are the sheet language's own: dxa-api read them from that
91
+ // language's authors_objects rows and printed nothing when it had none.
97
92
  fields,
98
93
  layout: 'list',
99
94
  shortDescription: 'short_description',
@@ -104,20 +99,21 @@ export function useGallerySheet(data, config = {}) {
104
99
  .map((p) => ({
105
100
  url: p.url,
106
101
  alt: labelOf('items', record.id),
107
- caption: p.captions?.[language] ?? p.captions?.[defaultLang] ?? '',
108
- photographer: p.photographer ?? '',
109
- copyright: p.copyright ?? '',
102
+ caption: p.captions?.[language] ?? '',
103
+ ...pictureCredits(p, language),
110
104
  })),
111
105
  citation: { permalink: false, heading: 'record.citation.ofThisPage' },
112
106
  related: {
113
107
  variant: 'grid',
114
108
  heading: 'record.related.items',
115
109
  actionLabel: 'catalogue.results.seeDatabaseEntry',
116
- record: ({ record: other, justification }) => ({
110
+ // In the sheet's language when the related record has it, in the main
111
+ // language otherwise, as legacy's relatedObjectsMainLanguage did.
112
+ record: ({ record: other, justification }, { language = defaultLang } = {}) => ({
117
113
  id: other.id,
118
114
  image: other.images?.[0]?.url ?? '',
119
115
  imageAlt: labelOf('items', other.id),
120
- name: mdInline(tr('items', other.id, defaultLang).name ?? other.internal_name ?? ''),
116
+ name: mdInline(translations('items', language)[other.id]?.name ?? tr('items', other.id, defaultLang).name ?? other.internal_name ?? ''),
121
117
  meta: [labelOf('countries', other.country_id), justification ? mdStrip(justification) : ''].filter(Boolean),
122
118
  to: itemRoute(other),
123
119
  }),
package/src/index.js CHANGED
@@ -39,7 +39,7 @@ export {
39
39
  glossaryEntries, glossaryTermsFor, glossaryTermsForText, sheetRows, useRecordSheet,
40
40
  } from './record/recordSheet.js'
41
41
  export {
42
- artistLine, citation, recordArtist, relatedRecords, removeEndingPoint, searchGlossary, timelineLinkFor, useGlossaryPopup,
42
+ artistLine, citation, pictureCredits, recordArtist, relatedRecords, removeEndingPoint, searchGlossary, timelineLinkFor, useGlossaryPopup,
43
43
  useRelatedRecords,
44
44
  } from './record/derivations.js'
45
45
  export { buildCollectionTree, collectionTreeFromThemes, useCollectionTree } from './record/collectionTree.js'
@@ -43,8 +43,9 @@ export function useGlossaryPopup(entries) {
43
43
  }
44
44
 
45
45
  /**
46
- * The glossary terms whose spelling starts with `input`, in `language`
47
- * (falling back to English), for a glossary search box: `[{ id, word,
46
+ * The glossary terms whose spelling starts with `input`, among those that have
47
+ * a row in `language` (legacy searched that language's glossary only), for a
48
+ * glossary search box: `[{ id, word,
48
49
  * spelling, definition }]`, one per term, sorted by spelling.
49
50
  */
50
51
  export function searchGlossary(input, language, { entity = 'glossary' } = {}) {
@@ -52,10 +53,10 @@ export function searchGlossary(input, language, { entity = 'glossary' } = {}) {
52
53
  if (!needle) return []
53
54
  const { translations } = useDataPackage()
54
55
  const rows = translations(entity, language)
55
- const fallback = translations(entity, BASE_LANGUAGE)
56
56
  const out = []
57
57
  for (const term of entityRef(entity).value ?? []) {
58
- const t = rows[term.id] ?? fallback[term.id] ?? {}
58
+ const t = rows[term.id]
59
+ if (!t) continue
59
60
  const spellings = t.spellings?.length ? t.spellings : [term.word]
60
61
  for (const spelling of spellings) {
61
62
  if (String(spelling ?? '').toLowerCase().startsWith(needle)) {
@@ -95,7 +96,7 @@ export function citation({ author, name, project, publisher = 'Museum With No Fr
95
96
  /**
96
97
  * A record's `related_items`, split by whether the package carries them:
97
98
  * `inPackage` is `[{ reference, record, justification }]` with the
98
- * justification in `language` (falling back to English); `outside` is the
99
+ * justification in `language` only, as legacy printed it; `outside` is the
99
100
  * references the package does not hold, kept as the references they are — a
100
101
  * link is never dropped for being unresolvable, and never invented either.
101
102
  */
@@ -116,7 +117,7 @@ export function relatedRecords(record, { entity = 'items', language = BASE_LANGU
116
117
  inPackage.push({
117
118
  reference,
118
119
  record: found,
119
- justification: justifications[language] ?? justifications[BASE_LANGUAGE] ?? '',
120
+ justification: justifications[language] ?? '',
120
121
  })
121
122
  }
122
123
  return { inPackage, outside }
@@ -179,16 +180,32 @@ export function removeEndingPoint(text) {
179
180
  return body.slice(0, start)
180
181
  }
181
182
 
183
+ /**
184
+ * A picture's photographer and copyright in `language`. Legacy kept them per
185
+ * language, like the caption, and printed only the page language's. A package
186
+ * exported before the per-language `photographers` and `copyrights` maps
187
+ * existed has only the flat fields, which it reads instead.
188
+ */
189
+ export function pictureCredits(picture, language) {
190
+ if (picture?.photographers || picture?.copyrights) {
191
+ return { photographer: picture.photographers?.[language] ?? '', copyright: picture.copyrights?.[language] ?? '' }
192
+ }
193
+ return { photographer: picture?.photographer ?? '', copyright: picture?.copyright ?? '' }
194
+ }
195
+
182
196
  const lifePart = (value) => (typeof value === 'string' ? value.trim() : '')
183
197
 
184
198
  /**
185
- * The artist the sheet names: the translation's own `artist` when it has one,
186
- * else the record's `artist_names`. A Sharing History record has no artist
187
- * entity, and legacy wrote its artist per language ("Unknown", "Inconnu"),
188
- * so the package carries it on each translation (inventory-app#2233, C22).
199
+ * The artist the sheet names: the translation's own `artist`. Legacy wrote the
200
+ * artist line per language ("Unknown", "Inconnu", or a sentence in Arabic) and
201
+ * printed a language's own line, or nothing when that language had none, so
202
+ * the package carries it on each translation (inventory-app#2233). The
203
+ * record's `artist_names` come from one language's line and stand for none;
204
+ * the record stays a parameter so callers need not change.
189
205
  */
206
+ // eslint-disable-next-line no-unused-vars
190
207
  export function recordArtist(record, text) {
191
- return lifePart(text?.artist) || (record?.artist_names ?? []).join(', ')
208
+ return lifePart(text?.artist)
192
209
  }
193
210
 
194
211
  /**
@@ -14,15 +14,15 @@ const BASE_LANGUAGE = 'en'
14
14
  // Seven pages wrote that choreography by hand. This is the one place it is
15
15
  // written, on top of `useRecordLanguage`, which decides the language.
16
16
 
17
- // A term's row in one language: its spellings and definition from
18
- // `rows`/`fallback` (English), its headword from `term` (the glossary
19
- // record itself, language-independent) — `null` when the term has neither,
20
- // which happens for a headword-only fixture row with no translation file at
21
- // all. Shared by `glossaryTermsFor` (walks a record's own `glossary_ids`)
17
+ // A term's row in one language: its spellings and definition from `rows`,
18
+ // its headword from `term` (the glossary record itself, language-independent)
19
+ // — `null` when the term has no row in that language. Legacy linked a term
20
+ // only in a language its glossary had it in, never through another. Shared by `glossaryTermsFor` (walks a record's own `glossary_ids`)
22
21
  // and `glossaryTermsForText` (walks the whole glossary, filtered by what
23
22
  // the text contains), so the two agree on what a term row looks like.
24
- function termRow(id, term, rows, fallback) {
25
- const t = rows[id] ?? fallback[id] ?? {}
23
+ function termRow(id, term, rows) {
24
+ const t = rows[id]
25
+ if (!t) return null
26
26
  const spellings = (t.spellings ?? []).map((s) => String(s).trim()).filter(Boolean)
27
27
  const word = term?.word ?? spellings[0] ?? ''
28
28
  if (!word && spellings.length === 0) return null
@@ -31,17 +31,16 @@ function termRow(id, term, rows, fallback) {
31
31
 
32
32
  /**
33
33
  * `{ id, word, definition, spellings }` for each glossary term of `record`
34
- * (its `glossary_ids`), with the spellings and definition in `language`,
35
- * falling back to English, then to the term's own headword.
34
+ * (its `glossary_ids`) that has a row in `language`, with its spellings and
35
+ * definition in it, its headword standing in for a row without spellings.
36
36
  */
37
37
  export function glossaryTermsFor(record, language, { entity = 'glossary', translations, byId } = {}) {
38
38
  const ids = record?.glossary_ids ?? []
39
39
  if (ids.length === 0) return []
40
40
  const rows = translations(entity, language)
41
- const fallback = translations(entity, BASE_LANGUAGE)
42
41
  const out = []
43
42
  for (const id of ids) {
44
- const row = termRow(id, byId?.get(id), rows, fallback)
43
+ const row = termRow(id, byId?.get(id), rows)
45
44
  if (row) out.push(row)
46
45
  }
47
46
  return out
@@ -53,7 +52,7 @@ export function glossaryTermsFor(record, language, { entity = 'glossary', transl
53
52
  * spelling occurs in `text`, matched the same word-bounded way `md`/
54
53
  * `renderBlock` highlight it, so what this returns and what the text is
55
54
  * rendered with agree. The candidate list — the whole glossary in
56
- * `language`, English behind it — is the same for every text asked about
55
+ * `language` — is the same for every text asked about
57
56
  * in that language; `glossaryIdsIn` is what carries the caching (see
58
57
  * `markdown.js`), so building it here costs one pass over the glossary,
59
58
  * not a rebuilt regex.
@@ -63,11 +62,10 @@ export function glossaryTermsForText(text, language, { entity = 'glossary' } = {
63
62
  if (!value) return []
64
63
  const { translations } = useDataPackage()
65
64
  const rows = translations(entity, language)
66
- const fallback = translations(entity, BASE_LANGUAGE)
67
65
  const byId = new Map()
68
66
  const candidates = []
69
67
  for (const term of entityRef(entity).value ?? []) {
70
- const row = termRow(term.id, term, rows, fallback)
68
+ const row = termRow(term.id, term, rows)
71
69
  if (!row) continue
72
70
  byId.set(term.id, row)
73
71
  for (const spelling of row.spellings) candidates.push({ id: term.id, spelling })
@@ -98,9 +96,9 @@ export function glossaryEntries(terms) {
98
96
  *
99
97
  * `attribution: ['author', 'copy_editor']` names fields that are proper
100
98
  * names rather than text: when the active language's row lacks them, the
101
- * first other language of the record that has them supplies them. This is
102
- * the one place a page reads across languages, and it exists because the
103
- * importer files some records' credits on one language only.
99
+ * first other language of the record that has them supplies them. No
100
+ * platform sheet asks for it any more: legacy printed a language's own
101
+ * credits only, and nothing where it had none.
104
102
  */
105
103
  export function useRecordSheet(record, { entity, translations: related = [], attribution: attributionFields = [], languages } = {}) {
106
104
  if (!entity) throw new Error('useRecordSheet: entity is required')
@@ -143,8 +141,9 @@ export function useRecordSheet(record, { entity, translations: related = [], att
143
141
  return
144
142
  }
145
143
  // The record language and English both: `tr` falls back to English for a
146
- // record or a term that has no row in the active language, and a fallback
147
- // that has not been loaded is a blank, not a fallback.
144
+ // record that has no row in the active language, as legacy's PHP sheets
145
+ // did, and a fallback that has not been loaded is a blank, not a fallback.
146
+ // Glossary terms, dynasties and the rest stay in the active language.
148
147
  const languages = lang === BASE_LANGUAGE ? [lang] : [lang, BASE_LANGUAGE]
149
148
  await Promise.all(
150
149
  [entity, ...related].flatMap((name) => languages.map((code) => pkg.loadTranslations(name, code))),