@museumwnf/viewer-core 2.10.0 → 2.12.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 +1 -1
- package/src/catalogue/catalogue.js +18 -11
- package/src/catalogue/fieldSearch.js +12 -3
- package/src/composables/useCatalogueData.js +51 -17
- package/src/createViewer.js +2 -0
- package/src/displayLanguage.js +18 -0
- package/src/dxa/exhibition/useExhibitionData.js +3 -0
- package/src/dxa/gallery/useGalleryData.js +3 -0
- package/src/index.js +2 -1
package/package.json
CHANGED
|
@@ -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
|
|
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),
|
|
@@ -105,6 +105,14 @@ export function searchRows(filters, count = 4) {
|
|
|
105
105
|
return rows
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* The query key of the search language: legacy's own `searchlanguage`. Never
|
|
110
|
+
* `lang`, which carries the language the website is read in: a visitor
|
|
111
|
+
* reading in French still searches every language, as on legacy, unless
|
|
112
|
+
* they pick one.
|
|
113
|
+
*/
|
|
114
|
+
export const SEARCH_LANGUAGE_KEY = 'searchlanguage'
|
|
115
|
+
|
|
108
116
|
/** The query keys `searchRows` reads, for a results spec's `keys`. */
|
|
109
117
|
export function searchRowKeys(count = 4) {
|
|
110
118
|
const keys = ['q', 'field']
|
|
@@ -125,7 +133,8 @@ export function searchedFor(filters, t, { rows = 4, extras = [] } = {}) {
|
|
|
125
133
|
.map((row, i) => `${i > 0 ? `${row.cond} ` : ''}${searchFieldLabel(row.field, t)}: "${row.keyword}"`)
|
|
126
134
|
if (filters.from) parts.push(`${t('catalogue.filter.from')} ${filters.from}`)
|
|
127
135
|
if (filters.to) parts.push(`${t('catalogue.filter.to')} ${filters.to}`)
|
|
128
|
-
|
|
136
|
+
const language = filters[SEARCH_LANGUAGE_KEY]
|
|
137
|
+
if (language) parts.push(`${t('catalogue.search.language')}: ${String(language).toUpperCase()}`)
|
|
129
138
|
for (const extra of extras) {
|
|
130
139
|
const part = extra(filters, t)
|
|
131
140
|
if (part) parts.push(part)
|
|
@@ -149,7 +158,7 @@ export function searchSummary({ filters, t, pageInfo }, options = {}) {
|
|
|
149
158
|
* The index behind the results page: one long-lived `useKeywordIndex` over
|
|
150
159
|
* `entity` (`fields` grammar, legacy's `rank: 'hits'` order, the glossary
|
|
151
160
|
* and country expansions of `database_results.php`), its search language
|
|
152
|
-
* kept in step with the query's `
|
|
161
|
+
* kept in step with the query's `searchlanguage`. `narrow(list, filters)` is a results
|
|
153
162
|
* spec's `narrow`: the matches in rank order, kept to what `list` (the
|
|
154
163
|
* records the spec's `scope` let through) carries. Pass `sort: false` to the
|
|
155
164
|
* spec, so the rank order stands.
|
|
@@ -165,7 +174,7 @@ export function useFieldSearch({
|
|
|
165
174
|
const index = useKeywordIndex(entity, { grammar: 'fields', fields, language, rank, expand })
|
|
166
175
|
|
|
167
176
|
function narrow(list, filters) {
|
|
168
|
-
language.value = filters
|
|
177
|
+
language.value = filters[SEARCH_LANGUAGE_KEY] || ''
|
|
169
178
|
const matches = index.search(searchRows(filters, rows))
|
|
170
179
|
if (!list) return matches
|
|
171
180
|
const allowed = new Set(list.map((record) => record.id))
|
|
@@ -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
|
-
//
|
|
30
|
-
// own language, which is why it is not reused: a catalogue list
|
|
31
|
-
//
|
|
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
|
|
53
|
-
* the glossary binding read
|
|
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({
|
|
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 =
|
|
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(
|
|
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
|
-
/**
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
}
|
package/src/createViewer.js
CHANGED
|
@@ -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
|
@@ -5,7 +5,7 @@ export {
|
|
|
5
5
|
CATALOGUE_DATE_MODE, CATALOGUE_PAGE_SIZE, objectsAndMonumentsSummary, useCatalogue,
|
|
6
6
|
} from './catalogue/catalogue.js'
|
|
7
7
|
export {
|
|
8
|
-
searchFieldLabel, searchFieldOptions, searchFields, searchRowKeys, searchRows, searchSummary, searchedFor,
|
|
8
|
+
SEARCH_LANGUAGE_KEY, searchFieldLabel, searchFieldOptions, searchFields, searchRowKeys, searchRows, searchSummary, searchedFor,
|
|
9
9
|
useFieldSearch, useSearchFieldOptions,
|
|
10
10
|
} from './catalogue/fieldSearch.js'
|
|
11
11
|
export { useDataPackage } from './composables/useDataPackage.js'
|
|
@@ -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.
|