@museumwnf/viewer-core 2.10.0 → 2.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@museumwnf/viewer-core",
3
- "version": "2.10.0",
3
+ "version": "2.11.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",
@@ -34,15 +34,20 @@ export const CATALOGUE_DATE_MODE = 'overlap'
34
34
  * - `itemById`: every item, the visible rule not applied — a page that
35
35
  * deliberately shows a hidden item (a timeline's illustration) reads it.
36
36
  * `partnerById`, `countryById`, `dynastyById`: the visible records.
37
- * - `itemLabel(item)`, `countryLabel(id)`, `partnerLabel(id)`,
38
- * `dynastyLabel(id)`: plain text, `''` for nothing.
37
+ * - `itemLabel(item)`, `countryLabel(id, lang)`, `partnerLabel(id, lang)`,
38
+ * `dynastyLabel(id, lang)`: plain text, `''` for nothing, in `lang` when
39
+ * given (the language of the record the name stands next to), in the
40
+ * display language otherwise.
39
41
  * - `itemRoute(item)`, `partnerRoute(partner)`: the `item` and `partner`
40
42
  * routes by name.
41
43
  * - `itemRow(item, meta)`: the row `RecordList`/`RecordGrid`/
42
44
  * `RelatedRecords` share. `meta` lists what follows the name, in order:
43
45
  * `'country'`, `'dates'`, `'location'`, `'dynasties'`, `'holder'` (the
44
46
  * partner's name, only when the package carries the partner, so a label
45
- * is never an id), or a function `(item, text) => string`.
47
+ * is never an id), or a function `(item, text, lang) => string`. The row
48
+ * reads the item in the display language when it has text there, in
49
+ * English otherwise, and names its country, dynasties and partner in that
50
+ * same language.
46
51
  */
47
52
  export function useCatalogue(options = {}) {
48
53
  const data = useCatalogueData(options)
@@ -59,30 +64,32 @@ export function useCatalogue(options = {}) {
59
64
  const dynastyById = data.index('dynasties')
60
65
 
61
66
  const itemLabel = (item) => (item ? labelOf('items', item.id) : '')
62
- const countryLabel = (id) => (id ? labelOf('countries', id) : '')
63
- const partnerLabel = (id) => (id ? labelOf('partners', id) : '')
64
- const dynastyLabel = (id) => (id ? labelOf('dynasties', id) : '')
67
+ const countryLabel = (id, lang) => (id ? labelOf('countries', id, { lang }) : '')
68
+ const partnerLabel = (id, lang) => (id ? labelOf('partners', id, { lang }) : '')
69
+ const dynastyLabel = (id, lang) => (id ? labelOf('dynasties', id, { lang }) : '')
65
70
 
66
71
  const itemRoute = (item) => ({ name: 'item', params: { id: item.id } })
67
72
  const partnerRoute = (partner) => ({ name: 'partner', params: { id: partner.id } })
68
73
 
74
+ // Each takes the language the row's record is shown in, so its names match it.
69
75
  const META = {
70
- country: (item) => countryLabel(item.country_id),
76
+ country: (item, text, lang) => countryLabel(item.country_id, lang),
71
77
  dates: (item, text) => text.dates,
72
78
  location: (item, text) => text.location,
73
- dynasties: (item) => (item.dynasty_ids ?? []).map(dynastyLabel).filter(Boolean).join(', '),
74
- holder: (item) => (partnerById.value.has(item.partner_id) ? partnerLabel(item.partner_id) : ''),
79
+ dynasties: (item, text, lang) => (item.dynasty_ids ?? []).map((id) => dynastyLabel(id, lang)).filter(Boolean).join(', '),
80
+ holder: (item, text, lang) => (partnerById.value.has(item.partner_id) ? partnerLabel(item.partner_id, lang) : ''),
75
81
  }
76
82
 
77
83
  function itemRow(item, meta = ['country', 'dates']) {
78
- const text = tr('items', item.id)
84
+ const lang = data.languageOf('items', item.id)
85
+ const text = tr('items', item.id, lang)
79
86
  return {
80
87
  id: item.id,
81
88
  image: item.images?.[0]?.url ?? '',
82
89
  imageAlt: itemLabel(item),
83
90
  name: mdInline(text.name ?? item.internal_name ?? item.id),
84
91
  meta: meta
85
- .map((part) => (typeof part === 'function' ? part(item, text) : META[part]?.(item, text)))
92
+ .map((part) => (typeof part === 'function' ? part(item, text, lang) : META[part]?.(item, text, lang)))
86
93
  .filter(Boolean),
87
94
  badge: item.type ?? '',
88
95
  to: itemRoute(item),
@@ -1,4 +1,5 @@
1
- import { computed } from 'vue'
1
+ import { computed, watch } from 'vue'
2
+ import { displayLanguage } from '../displayLanguage.js'
2
3
  import { useDataPackage } from './useDataPackage.js'
3
4
  import { byId as coreById, entityRef } from './useEntities.js'
4
5
  import { md as coreMd, mdInline as coreMdInline, mdStrip as coreMdStrip } from '../i18n/markdown.js'
@@ -25,10 +26,10 @@ const BASE_LANGUAGE = 'en'
25
26
 
26
27
  // A term's spellings and definition in one language, falling back to
27
28
  // English, then to the term's own headword — the same row shape
28
- // `useRecordSheet`'s `termRow` builds, but for the whole glossary in
29
- // `defaultLanguage` rather than one record's `glossary_ids` in the record's
30
- // own language, which is why it is not reused: a catalogue list or a label
31
- // is always shown in `defaultLanguage`, a record sheet's text is not.
29
+ // `useRecordSheet`'s `termRow` builds, but for the whole glossary in the
30
+ // display language rather than one record's `glossary_ids` in the record's
31
+ // own language, which is why it is not reused: a catalogue list is shown in
32
+ // the display language, a record sheet in the language the record is read in.
32
33
  function glossaryRows(language, translations) {
33
34
  const rows = translations('glossary', language)
34
35
  const fallback = translations('glossary', BASE_LANGUAGE)
@@ -49,27 +50,46 @@ function glossaryRows(language, translations) {
49
50
  * visible form of an entity and its index, and the package re-exports.
50
51
  *
51
52
  * `eager` names the entities `loadEnglish` loads — every entity the package
52
- * carries by default. `defaultLanguage` is the language `tr`, the labels and
53
- * the glossary binding read (`'en'` for every site so far). `visible` names,
53
+ * carries by default — in `defaultLanguage` and in the display language.
54
+ * `tr`, the labels and the glossary binding read the display language (the
55
+ * language the website is shown in, reference data included: a country is
56
+ * named in the language its record is shown in), and fall back, record by
57
+ * record, to `defaultLanguage` (`'en'` for every site so far). `visible` names,
54
58
  * per entity, the predicate `entity()`/`index()` apply — a per-build
55
59
  * language filter, a hidden-partner rule, a display-status check are all
56
60
  * "which records this build shows", declared here once instead of coded
57
61
  * into every page that lists that entity. `glossary` turns off the default
58
62
  * binding on `md`/`mdInline` (a caller may still pass its own `glossary` to
59
- * either, on or off).
63
+ * either, on or off). `followDisplayLanguage: false` keeps everything in
64
+ * `defaultLanguage`, for the DXA families until their pages are checked
65
+ * against legacy in other languages.
60
66
  */
61
- export function useCatalogueData({ eager, defaultLanguage = BASE_LANGUAGE, visible = {}, glossary = true } = {}) {
67
+ export function useCatalogueData({
68
+ eager, defaultLanguage = BASE_LANGUAGE, visible = {}, glossary = true, followDisplayLanguage = true,
69
+ } = {}) {
62
70
  const pkg = useDataPackage()
63
71
  const { availableLanguages, loadTranslations, translations } = pkg
64
72
  const eagerEntities = eager ?? pkg.entityNames
73
+ const shown = computed(() => (followDisplayLanguage ? displayLanguage.value : defaultLanguage))
65
74
 
66
- /** One record's translated fields, falling back to `defaultLanguage` then `{}`. */
67
- function tr(entity, id, lang = defaultLanguage) {
75
+ /** One record's translated fields, in `lang` (the display language), falling back to `defaultLanguage` then `{}`. */
76
+ function tr(entity, id, lang = shown.value) {
68
77
  return pkg.tr(entity, id, lang, defaultLanguage)
69
78
  }
70
79
 
80
+ /**
81
+ * The language one record is shown in: the display language when the
82
+ * record has text in it, `defaultLanguage` otherwise. The reference names
83
+ * shown next to it (its country, its partner, its dynasties) take this
84
+ * language, so a record left in English is not printed with French names.
85
+ */
86
+ function languageOf(entity, id) {
87
+ const lang = shown.value
88
+ return id && translations(entity, lang)[id] ? lang : defaultLanguage
89
+ }
90
+
71
91
  const defaultGlossary = computed(() =>
72
- glossary ? glossaryEntries(glossaryRows(defaultLanguage, translations)) : [],
92
+ glossary ? glossaryEntries(glossaryRows(shown.value, translations)) : [],
73
93
  )
74
94
 
75
95
  // A caller's own `glossary` — a record's own terms, not every term in the
@@ -84,20 +104,34 @@ export function useCatalogueData({ eager, defaultLanguage = BASE_LANGUAGE, visib
84
104
  const mdInline = bindGlossary(coreMdInline)
85
105
  const mdStrip = coreMdStrip
86
106
 
87
- /** `mdStrip(tr(entity, id).name ?? record[fallback] ?? id)` — the one label shape every site wrote. */
88
- function labelOf(entity, id, { fallback = 'internal_name' } = {}) {
107
+ /**
108
+ * `mdStrip(tr(entity, id, lang).name ?? record[fallback] ?? id)` — the one
109
+ * label shape every site wrote. `lang` is the display language unless the
110
+ * caller names the language of the record the label stands next to.
111
+ */
112
+ function labelOf(entity, id, { fallback = 'internal_name', lang } = {}) {
89
113
  if (!id) return ''
90
- const name = tr(entity, id).name
114
+ // A label passed as a callback (`.map(countryLabel)`, a facet's
115
+ // `label(value, ctx)`) receives an index or a context here, not a language.
116
+ const name = tr(entity, id, typeof lang === 'string' ? lang : undefined).name
91
117
  if (name) return mdStrip(name)
92
118
  const record = coreById(entity).value.get(id)
93
119
  return mdStrip(record?.[fallback] ?? id)
94
120
  }
95
121
 
96
122
  let englishReady = null
97
- /** Loads `defaultLanguage` for every entity in `eager`, once. */
123
+ /**
124
+ * Loads `defaultLanguage` for every entity in `eager`, once, and the display
125
+ * language too, again each time it changes. Resolves with the
126
+ * `defaultLanguage` files: a page renders in English until the other
127
+ * language arrives, and re-renders then.
128
+ */
98
129
  function loadEnglish() {
99
130
  if (!englishReady) {
100
131
  englishReady = Promise.all(eagerEntities.map((name) => loadTranslations(name, defaultLanguage)))
132
+ watch(shown, (lang) => {
133
+ if (lang !== defaultLanguage) eagerEntities.forEach((name) => loadTranslations(name, lang))
134
+ }, { immediate: true })
101
135
  }
102
136
  return englishReady
103
137
  }
@@ -135,7 +169,7 @@ export function useCatalogueData({ eager, defaultLanguage = BASE_LANGUAGE, visib
135
169
  }
136
170
 
137
171
  return {
138
- tr, md, mdInline, mdStrip, loadEnglish, labelOf, entity, index,
172
+ tr, md, mdInline, mdStrip, loadEnglish, labelOf, languageOf, entity, index,
139
173
  availableLanguages, loadTranslations, translations,
140
174
  }
141
175
  }
@@ -1,6 +1,7 @@
1
1
  import { createApp } from 'vue'
2
2
  import AppRoot from './AppRoot.vue'
3
3
  import { useDataPackage } from './composables/useDataPackage.js'
4
+ import { bindDisplayLanguage } from './displayLanguage.js'
4
5
  import { createI18n } from './i18n/index.js'
5
6
  import { connectLanguageToRouter, resolveInitialLanguage } from './i18n/language.js'
6
7
  import { VIEWER_CONFIG } from './injectionKeys.js'
@@ -24,6 +25,7 @@ export function createViewer(config = {}) {
24
25
  app.use(i18n)
25
26
 
26
27
  connectLanguageToRouter({ locale: i18n.locale, offered: languages, router })
28
+ bindDisplayLanguage(i18n.locale)
27
29
 
28
30
  return app
29
31
  }
@@ -0,0 +1,18 @@
1
+ import { computed, shallowRef } from 'vue'
2
+
3
+ // The language the website is shown in, for code that runs outside a
4
+ // component: the catalogue data layer is built once, at module scope, where
5
+ // `useI18n()` cannot inject. `createViewer` binds the application's locale
6
+ // here, the way it hands its configuration to `setSiteConfig`; until it does
7
+ // (a test mounting one component), the website is in English.
8
+
9
+ const BASE_LANGUAGE = 'en'
10
+ const bound = shallowRef(null)
11
+
12
+ /** The active display language, read-only. */
13
+ export const displayLanguage = computed(() => bound.value?.value ?? BASE_LANGUAGE)
14
+
15
+ /** Binds the application's locale ref; `createViewer` calls it once. */
16
+ export function bindDisplayLanguage(locale) {
17
+ bound.value = locale ?? null
18
+ }
@@ -61,6 +61,9 @@ export function useExhibitionData(config = {}) {
61
61
 
62
62
  const catalogue = useCatalogueData({
63
63
  eager: config.eager ?? DEFAULT_EAGER,
64
+ // English-first until these pages are checked against legacy in other
65
+ // languages (the display-language rule, inventory-app#2414).
66
+ followDisplayLanguage: false,
64
67
  defaultLanguage: defaultLang,
65
68
  visible: {
66
69
  // An item's `languages` is what it has TRANSLATIONS in, so a
@@ -28,6 +28,9 @@ export function useGalleryData(config = {}) {
28
28
 
29
29
  const catalogue = useCatalogueData({
30
30
  eager: config.eager ?? DEFAULT_EAGER,
31
+ // English-first until these pages are checked against legacy in other
32
+ // languages (the display-language rule, inventory-app#2414).
33
+ followDisplayLanguage: false,
31
34
  })
32
35
 
33
36
  const {
package/src/index.js CHANGED
@@ -17,6 +17,7 @@ export { md, mdInline, mdStrip, renderBlock, renderInline, renderPlain } from '.
17
17
  export { languageLabels, offeredLanguages } from './languages.js'
18
18
  export { defaultScrollBehavior, resolveViews } from './router/index.js'
19
19
  export { mediaUrl, useSiteConfig } from './siteConfig.js'
20
+ export { bindDisplayLanguage, displayLanguage } from './displayLanguage.js'
20
21
  export { sourceUrl, useSiteRights } from './rights.js'
21
22
 
22
23
  // The list pages: query state, pagination, facets, dates, keyword search.