@glossarist/concept-browser 0.7.0 → 0.7.4
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/cli/index.mjs +3 -0
- package/package.json +1 -1
- package/scripts/generate-data.mjs +19 -3
- package/src/__tests__/app-footer.test.ts +1 -1
- package/src/__tests__/app-sidebar.test.ts +0 -5
- package/src/adapters/types.ts +1 -0
- package/src/assets/glossarist-logo.svg +1 -0
- package/src/components/AppFooter.vue +4 -2
- package/src/components/AppHeader.vue +7 -0
- package/src/components/AppSidebar.vue +66 -12
- package/src/components/ConceptCard.vue +2 -2
- package/src/components/ConceptDetail.vue +36 -28
- package/src/components/FormatDownloads.vue +4 -1
- package/src/config/types.ts +5 -0
- package/src/config/use-site-config.ts +2 -2
- package/src/i18n/locales/eng.yml +39 -0
- package/src/i18n/locales/fra.yml +39 -0
- package/src/style.css +6 -0
- package/src/utils/lang.ts +24 -22
- package/src/views/ConceptView.vue +9 -6
- package/src/views/DatasetView.vue +8 -4
package/cli/index.mjs
CHANGED
|
@@ -81,6 +81,9 @@ Environment:
|
|
|
81
81
|
const { config } = loadSiteConfig(named.site ? [named.site] : []);
|
|
82
82
|
|
|
83
83
|
if (cmd === 'build' || cmd === 'site') {
|
|
84
|
+
if (!process.env.BASE_PATH && config?.basePath) {
|
|
85
|
+
process.env.BASE_PATH = config.basePath;
|
|
86
|
+
}
|
|
84
87
|
for (const step of ['fetch', 'generate', 'edges']) {
|
|
85
88
|
console.log(`\n=== ${step.toUpperCase()} ===\n`);
|
|
86
89
|
await commands[step]();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@glossarist/concept-browser",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "Vue SPA for browsing Glossarist terminology datasets with cross-reference resolution, graph visualization, and multi-language support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -667,6 +667,7 @@ function processDataset(dir, register, opts) {
|
|
|
667
667
|
|
|
668
668
|
const summary = concepts.map(c => ({
|
|
669
669
|
id: c.id,
|
|
670
|
+
designations: c.designations,
|
|
670
671
|
eng: c.designations.eng || Object.values(c.designations)[0] || '',
|
|
671
672
|
status: c.status,
|
|
672
673
|
}));
|
|
@@ -770,6 +771,7 @@ function processDataset(dir, register, opts) {
|
|
|
770
771
|
hasImages: opts.hasImages,
|
|
771
772
|
};
|
|
772
773
|
if (opts.languageOrder) manifest.languageOrder = opts.languageOrder;
|
|
774
|
+
if (opts.ref) manifest.ref = opts.ref;
|
|
773
775
|
writeJson(path.join(DATA, register, 'manifest.json'), manifest);
|
|
774
776
|
|
|
775
777
|
// Copy bibliography.yaml → bibliography.json
|
|
@@ -834,6 +836,7 @@ for (let i = 0; i < config.datasets.length; i++) {
|
|
|
834
836
|
languages: dsLanguages,
|
|
835
837
|
sourceRepo: ds.sourceRepo,
|
|
836
838
|
languageOrder: ds.languageOrder,
|
|
839
|
+
ref: ds.ref,
|
|
837
840
|
tags: ds.tags,
|
|
838
841
|
color: ds.color || DS_PALETTE[i % DS_PALETTE.length],
|
|
839
842
|
datasetUri: ds.uri,
|
|
@@ -885,6 +888,14 @@ async function processLogo(logoConfig, filename) {
|
|
|
885
888
|
await processLogo(config.branding?.logo, `${config.id}-logo.svg`);
|
|
886
889
|
await processLogo(config.branding?.footerLogo, `${config.id}-footer-logo.svg`);
|
|
887
890
|
|
|
891
|
+
// Process light/dark logo variants
|
|
892
|
+
if (config.branding?.logo?.localLight) {
|
|
893
|
+
await processLogo({ localPath: config.branding.logo.localLight }, `${config.id}-logo-light.svg`);
|
|
894
|
+
}
|
|
895
|
+
if (config.branding?.logo?.localDark) {
|
|
896
|
+
await processLogo({ localPath: config.branding.logo.localDark }, `${config.id}-logo-dark.svg`);
|
|
897
|
+
}
|
|
898
|
+
|
|
888
899
|
// === Page processors ===
|
|
889
900
|
|
|
890
901
|
function processNewsPage(config, page) {
|
|
@@ -1107,9 +1118,14 @@ const basePathPrefix = process.env.BASE_PATH?.replace(/\/+$/, '') || '';
|
|
|
1107
1118
|
for (const key of ['logo', 'footerLogo']) {
|
|
1108
1119
|
const suffix = key === 'logo' ? 'logo.svg' : 'footer-logo.svg';
|
|
1109
1120
|
if (siteBranding[key]) {
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1121
|
+
const updated = { ...siteBranding[key], path: `${basePathPrefix}/logos/${config.id}-${suffix}` };
|
|
1122
|
+
if (siteBranding[key].localLight) updated.light = `${basePathPrefix}/logos/${config.id}-${suffix.replace('.svg', '-light.svg')}`;
|
|
1123
|
+
if (siteBranding[key].localDark) updated.dark = `${basePathPrefix}/logos/${config.id}-${suffix.replace('.svg', '-dark.svg')}`;
|
|
1124
|
+
delete updated.localPath;
|
|
1125
|
+
delete updated.remoteUrl;
|
|
1126
|
+
delete updated.localLight;
|
|
1127
|
+
delete updated.localDark;
|
|
1128
|
+
siteBranding[key] = updated;
|
|
1113
1129
|
}
|
|
1114
1130
|
}
|
|
1115
1131
|
|
|
@@ -33,6 +33,6 @@ describe('AppFooter', () => {
|
|
|
33
33
|
const wrapper = mountFooter();
|
|
34
34
|
const link = wrapper.findAll('a').find(a => a.text().includes('Glossarist Concept Browser'));
|
|
35
35
|
expect(link).toBeDefined();
|
|
36
|
-
expect(link!.attributes('href')).toContain('
|
|
36
|
+
expect(link!.attributes('href')).toContain('glossarist.org');
|
|
37
37
|
});
|
|
38
38
|
});
|
|
@@ -151,9 +151,4 @@ describe('AppSidebar', () => {
|
|
|
151
151
|
expect(wrapper.text()).toContain('Test test');
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
-
it('shows powered by link', async () => {
|
|
155
|
-
seedStore();
|
|
156
|
-
const wrapper = await mountSidebar();
|
|
157
|
-
expect(wrapper.text()).toContain('Glossarist Concept Browser');
|
|
158
|
-
});
|
|
159
154
|
});
|
package/src/adapters/types.ts
CHANGED
|
@@ -57,6 +57,7 @@ export interface Manifest {
|
|
|
57
57
|
color?: string;
|
|
58
58
|
shortname?: string;
|
|
59
59
|
languageOrder?: string[];
|
|
60
|
+
ref?: string;
|
|
60
61
|
languageStats?: Record<string, { terms: number; definitions: number }>;
|
|
61
62
|
availableFormats?: string[];
|
|
62
63
|
bulkFormats?: { file: string; format: string; size: number }[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?><svg id="uuid-e2e4f56e-6766-4eaf-8ba1-80c056213369" xmlns="http://www.w3.org/2000/svg" width="183.1" height="172.48" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 183.1 172.48"><defs><linearGradient id="uuid-526bfbac-cffb-4a71-b92d-c02fefae5080" x1="51.31" y1="161.8" x2="102.32" y2="73.02" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#3fb6b0"/><stop offset=".07" stop-color="#5cc5be"/><stop offset=".15" stop-color="#78d3cc"/><stop offset=".24" stop-color="#8fe0d8"/><stop offset=".34" stop-color="#a1e9e1"/><stop offset=".47" stop-color="#aef0e7"/><stop offset=".63" stop-color="#b5f3ea"/><stop offset="1" stop-color="#b8f5ec"/></linearGradient><linearGradient id="uuid-1b1e62d8-3f5c-498e-8428-628e5706abf6" x1="0" y1="59.58" x2="183.1" y2="59.58" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2d4164"/><stop offset=".47" stop-color="#2d4164" stop-opacity=".94"/><stop offset="1" stop-color="#2d4164" stop-opacity=".8"/></linearGradient><linearGradient id="uuid-b44d9113-c74c-48d0-8db3-7071ba63ee91" x1="71.68" y1="31.06" x2="111.41" y2="31.06" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2d4164"/><stop offset=".28" stop-color="#2c4365"/><stop offset="1" stop-color="#588ac1"/></linearGradient><linearGradient id="uuid-ef83f354-a6ba-455c-a262-42ad1af8aab0" x1="76.66" y1="69.37" x2="183.1" y2="69.37" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#34798a" stop-opacity=".7"/><stop offset="1" stop-color="#62c4dd"/></linearGradient><linearGradient id="uuid-9c4f3d11-41f0-40bc-8383-a054e148eefb" x1="81.4" y1="129.46" x2="127.22" y2="50.28" gradientUnits="userSpaceOnUse"><stop offset=".2" stop-color="#456399"/><stop offset="1" stop-color="#3a5c80" stop-opacity=".7"/></linearGradient><linearGradient id="uuid-75c138e5-3eb7-4084-88c8-ea7b28bec669" x1="88.72" y1="146.48" x2="54.34" y2="146.48" xlink:href="#uuid-b44d9113-c74c-48d0-8db3-7071ba63ee91"/><linearGradient id="uuid-648153de-b6a2-4cdb-bf68-e5b0950e981e" x1="86.16" y1="99.24" x2="47.06" y2="25.85" gradientUnits="userSpaceOnUse"><stop offset=".2" stop-color="#456399"/><stop offset="1" stop-color="#3a5c80" stop-opacity=".7"/></linearGradient><linearGradient id="uuid-e1874ccf-ad77-48dc-8e9c-b19bc34aaaf5" x1="89.58" y1="107.94" x2="63.88" y2="57.26" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#456399" stop-opacity=".5"/><stop offset="1" stop-color="#97a4b7" stop-opacity=".8"/></linearGradient></defs><path d="m87.41,59.48l-61.18,106.11c-1.63,2.85.6,6.85,3.98,6.88h37.88l63.56-112.75-44.23-.25Z" style="fill:url(#uuid-526bfbac-cffb-4a71-b92d-c02fefae5080); stroke-width:0px;"/><path d="m40.39,141.03c.29.28,1.01,2.7,1.61,4.91.67,2.49,2.77,4.38,5.33,4.68,21.2,2.5,21.4,20.71,21.4,20.71l22.5-39.92c-23.71,14.02-41.88,6.43-40.64-8.09-2.71,4.71-7.5,12.99-10.21,17.7Z" style="fill:#3a5c80; stroke-width:0px;"/><path d="m75.5,80.4s-14.96,25.41-18.41,31.59c8.71-8.42,17.69,2.79,24.11-8.66,8.13-14.5-5.7-22.93-5.7-22.93Z" style="fill:#60abbc; stroke-width:0px;"/><path d="m113.82,172.48h38.28c4.52-.01,6.66-3.57,4.43-7.48-6.55-11.51-59.96-105.41-59.96-105.41l-43.04-.37,60.29,113.27Z" style="fill:#b8f5ec; stroke-width:0px;"/><path d="m106.47,91.98c-9.24,2.3-34.69,12.42-21.32,20.21,12.57,7.31,28.11.2,32.05-11.73,1.79-4.66-2.29-10.67-10.74-8.47Z" style="fill:#3fb6b0; stroke-width:0px;"/><path d="m137.32,131.2l-13.09-23.07c.37,24.63-35.5,17.21-35.5,17.21l24.95,46.87c-1-25.77,10.54-35.8,23.65-41Z" style="fill:#7ee0d4; stroke-width:0px;"/><path d="m57.35,132.11s0-.01.01-.02c0,0,0,0,0,0" style="fill:#3a5c80; stroke-width:0px;"/><path d="m183.1,52.52c-.1-3.25-2.07-5.56-6-5.59-13.09-.07-163.15,0-163.15,0L0,72.26h183.1v-19.75Z" style="fill:url(#uuid-1b1e62d8-3f5c-498e-8428-628e5706abf6); stroke-width:0px;"/><path d="m71.68,47s19.67-31.93,27.9-31.9c10.36,0,15.93,31.9,8.24,31.83l-36.15.07Z" style="fill:url(#uuid-b44d9113-c74c-48d0-8db3-7071ba63ee91); stroke-width:0px;"/><path d="m183.1,59.91l-.13,10.94c.07,6.28-2.04,8.39-8.33,8.31h-97.99l10.97-19.59,95.47.33Z" style="fill:url(#uuid-ef83f354-a6ba-455c-a262-42ad1af8aab0); opacity:.78; stroke-width:0px;"/><polygon points="7.1 59.58 0 72.26 80.52 72.26 87.63 59.58 7.1 59.58" style="fill:#3a5c80; opacity:.8; stroke-width:0px;"/><path d="m117.54,47s-65.24,112.78-69.85,120.82c-1.1,1.92.38,4.63,2.65,4.65.71,0,17.74,0,17.74,0l70.73-125.47h-21.27Z" style="fill:url(#uuid-9c4f3d11-41f0-40bc-8383-a054e148eefb); opacity:.8; stroke-width:0px;"/><path d="m84.52,142.79c.55-.64.79-1.48,1.27-2.18,10.98-16.02-12.03-16.74-17.61-8.28l-12.79,23.17c-.14.24-.26.48-.38.72-3.39,7.29,6.71,13.38,11.92,7.26l17.59-20.69Z" style="fill:url(#uuid-75c138e5-3eb7-4084-88c8-ea7b28bec669); opacity:.8; stroke-width:0px;"/><path d="m124.71,126.57s0-.01-.01-.02c0,0,0,0,0,0,0,0,0,.02,0,.02Z" style="fill:#2e4365; stroke-width:0px;"/><polygon points="136.69 172.48 113.82 172.48 53.53 59.58 73.24 59.58 136.69 172.48" style="fill:url(#uuid-648153de-b6a2-4cdb-bf68-e5b0950e981e); opacity:.75; stroke-width:0px;"/><path d="m73.39,59.58l33.44,59.63c2.82,7.65-7.17,26.67-12.28,17.06L53.53,59.58h19.86Z" style="fill:url(#uuid-e1874ccf-ad77-48dc-8e9c-b19bc34aaaf5); stroke-width:0px;"/><path d="m115.87,135.34l18.22,32.5s-36.61-14.66-18.22-32.5Z" style="fill:#4977a4; stroke-width:0px;"/></svg>
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
import { computed } from 'vue';
|
|
3
3
|
import { useSiteConfig } from '../config/use-site-config';
|
|
4
4
|
import { useI18n } from '../i18n';
|
|
5
|
+
import glossaristLogo from '../assets/glossarist-logo.svg';
|
|
5
6
|
|
|
6
7
|
const { config } = useSiteConfig();
|
|
7
8
|
const { t } = useI18n();
|
|
8
9
|
|
|
9
10
|
const poweredBy = computed(() => {
|
|
10
11
|
const pb = config.value?.features?.poweredBy as { message?: string; url?: string } | undefined;
|
|
11
|
-
return { message: pb?.message || t('footer.builtWith'), url: pb?.url || 'https://
|
|
12
|
+
return { message: pb?.message || t('footer.builtWith'), url: pb?.url || 'https://glossarist.org' };
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
const socialLinks = computed(() => {
|
|
@@ -56,7 +57,8 @@ const ownerUrl = computed(() => config.value?.branding?.ownerUrl || '/');
|
|
|
56
57
|
class="hover:text-ink-700 transition-colors"
|
|
57
58
|
>{{ link.label }}</a>
|
|
58
59
|
<span class="text-ink-200">|</span>
|
|
59
|
-
<span class="text-xs">
|
|
60
|
+
<span class="text-xs inline-flex items-center gap-1.5">
|
|
61
|
+
<img :src="glossaristLogo" alt="" class="w-4 h-4 opacity-80" />
|
|
60
62
|
<a :href="poweredBy.url" target="_blank" rel="noopener" class="concept-link">{{ poweredBy.message }}</a>
|
|
61
63
|
</span>
|
|
62
64
|
</div>
|
|
@@ -64,6 +64,13 @@ onBeforeUnmount(() => document.removeEventListener('click', closeLangOnOutside))
|
|
|
64
64
|
<button @click="goHome" class="flex items-center gap-2 hover:opacity-80 transition flex-shrink-0 group">
|
|
65
65
|
<div v-if="siteConfig?.branding?.logo" class="h-8 flex items-center">
|
|
66
66
|
<img
|
|
67
|
+
v-if="siteConfig.branding.logo.light && siteConfig.branding.logo.dark"
|
|
68
|
+
:src="ui.isDark ? siteConfig.branding.logo.dark : siteConfig.branding.logo.light"
|
|
69
|
+
:alt="siteConfig.branding.logo.alt"
|
|
70
|
+
class="h-8 max-w-[48px] object-contain rounded"
|
|
71
|
+
/>
|
|
72
|
+
<img
|
|
73
|
+
v-else
|
|
67
74
|
:src="siteConfig.branding.logo.path"
|
|
68
75
|
:alt="siteConfig.branding.logo.alt"
|
|
69
76
|
class="h-8 max-w-[48px] object-contain rounded"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue';
|
|
2
|
+
import { computed, ref } from 'vue';
|
|
3
3
|
import { useVocabularyStore } from '../stores/vocabulary';
|
|
4
4
|
import { useUiStore } from '../stores/ui';
|
|
5
5
|
import { useRoute, useRouter } from 'vue-router';
|
|
@@ -86,6 +86,22 @@ const datasetEntries = computed(() => {
|
|
|
86
86
|
const currentManifest = computed(() => store.manifests.get(currentDataset.value));
|
|
87
87
|
const showDatasetNav = computed(() => !!currentManifest.value || !!siteConfig.value?.defaultDataset);
|
|
88
88
|
|
|
89
|
+
const provenance = computed(() => {
|
|
90
|
+
const manifest = currentManifest.value;
|
|
91
|
+
return {
|
|
92
|
+
owner: manifest?.owner || (siteConfig.value as any)?.branding?.ownerName,
|
|
93
|
+
ownerUrl: (siteConfig.value as any)?.branding?.ownerUrl,
|
|
94
|
+
ref: manifest?.ref,
|
|
95
|
+
status: manifest?.status,
|
|
96
|
+
lastUpdated: manifest?.lastUpdated,
|
|
97
|
+
conceptCount: manifest?.conceptCount,
|
|
98
|
+
languageCount: manifest?.languages?.length,
|
|
99
|
+
sourceRepo: manifest?.sourceRepo,
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const ontologyExpanded = ref(true);
|
|
104
|
+
|
|
89
105
|
function closeMobile() { ui.sidebarOpen = false; }
|
|
90
106
|
|
|
91
107
|
function goToDataset(id: string) {
|
|
@@ -107,7 +123,10 @@ function isActive(page: { route: string; datasetScoped?: boolean }): boolean {
|
|
|
107
123
|
if (page.datasetScoped) return route.name === 'dataset' || route.name === 'concept';
|
|
108
124
|
return route.name === 'home';
|
|
109
125
|
}
|
|
110
|
-
|
|
126
|
+
const target = pageRoute(page);
|
|
127
|
+
if (route.path === target) return true;
|
|
128
|
+
if (page.datasetScoped) return route.name === page.route;
|
|
129
|
+
return route.name === page.route || route.name === `${page.route}-global`;
|
|
111
130
|
}
|
|
112
131
|
|
|
113
132
|
function selectClass(id: string) {
|
|
@@ -162,7 +181,14 @@ function navTitle(page: { route: string }): string {
|
|
|
162
181
|
</router-link>
|
|
163
182
|
|
|
164
183
|
<!-- Ontology entity sections nested under Ontology nav item -->
|
|
165
|
-
<div v-if="page.route === 'ontology' && isOntologyRoute" class="ml-3 mt-1 mb-2
|
|
184
|
+
<div v-if="page.route === 'ontology' && isOntologyRoute" class="ml-3 mt-1 mb-2">
|
|
185
|
+
<button @click="ontologyExpanded = !ontologyExpanded"
|
|
186
|
+
class="w-full flex items-center gap-1.5 px-2 py-1 rounded-lg text-[10px] uppercase tracking-wide text-ink-400 hover:text-ink-600 hover:bg-ink-50 transition-colors mb-1"
|
|
187
|
+
>
|
|
188
|
+
<span class="w-3 text-[10px]">{{ ontologyExpanded ? '▾' : '▸' }}</span>
|
|
189
|
+
<span class="flex-1 text-left">{{ t('nav.ontology') }}</span>
|
|
190
|
+
</button>
|
|
191
|
+
<div v-if="ontologyExpanded" class="space-y-0.5">
|
|
166
192
|
<!-- Search input -->
|
|
167
193
|
<div class="relative mb-1.5">
|
|
168
194
|
<input
|
|
@@ -425,6 +451,7 @@ function navTitle(page: { route: string }): string {
|
|
|
425
451
|
</div>
|
|
426
452
|
</div>
|
|
427
453
|
</template>
|
|
454
|
+
</div>
|
|
428
455
|
</div>
|
|
429
456
|
</template>
|
|
430
457
|
</nav>
|
|
@@ -469,15 +496,42 @@ function navTitle(page: { route: string }): string {
|
|
|
469
496
|
</button>
|
|
470
497
|
</nav>
|
|
471
498
|
|
|
472
|
-
<!--
|
|
473
|
-
<div class="mt-6 pt-4 border-t border-ink-100/60">
|
|
474
|
-
<div class="text-[11px] text-ink-300">
|
|
475
|
-
<
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
499
|
+
<!-- Dataset provenance -->
|
|
500
|
+
<div v-if="provenance.owner" class="mt-6 pt-4 border-t border-ink-100/60">
|
|
501
|
+
<div class="text-[11px] text-ink-300 space-y-1.5">
|
|
502
|
+
<div class="font-medium text-ink-400">{{ t('sidebar.provenance') }}</div>
|
|
503
|
+
|
|
504
|
+
<div v-if="provenance.ref" class="text-xs font-semibold text-ink-700">
|
|
505
|
+
{{ provenance.ref }}
|
|
506
|
+
</div>
|
|
507
|
+
|
|
508
|
+
<div class="flex items-center gap-1">
|
|
509
|
+
<span class="text-ink-400">{{ t('sidebar.publishedBy') }}</span>
|
|
510
|
+
<a v-if="provenance.ownerUrl" :href="provenance.ownerUrl" target="_blank" rel="noopener" class="concept-link font-medium">{{ provenance.owner }}</a>
|
|
511
|
+
<span v-else class="text-ink-600 font-medium">{{ provenance.owner }}</span>
|
|
512
|
+
</div>
|
|
513
|
+
|
|
514
|
+
<div v-if="provenance.status" class="flex items-center gap-1.5">
|
|
515
|
+
<span class="inline-block w-1.5 h-1.5 rounded-full" :class="provenance.status === 'valid' ? 'bg-emerald-500' : 'bg-amber-400'"></span>
|
|
516
|
+
<span class="text-[10px] uppercase tracking-wide font-medium" :class="provenance.status === 'valid' ? 'text-emerald-600' : 'text-amber-600'">
|
|
517
|
+
{{ provenance.status }}
|
|
518
|
+
</span>
|
|
519
|
+
</div>
|
|
520
|
+
|
|
521
|
+
<div v-if="provenance.lastUpdated" class="text-ink-300">
|
|
522
|
+
{{ t('sidebar.updated') }} {{ provenance.lastUpdated }}
|
|
523
|
+
</div>
|
|
524
|
+
|
|
525
|
+
<div v-if="provenance.conceptCount" class="text-ink-400">
|
|
526
|
+
{{ provenance.conceptCount.toLocaleString() }} {{ t('sidebar.concepts').toLowerCase() }}
|
|
527
|
+
<template v-if="provenance.languageCount">
|
|
528
|
+
· {{ provenance.languageCount }} {{ t('sidebar.languages').toLowerCase() }}
|
|
529
|
+
</template>
|
|
530
|
+
</div>
|
|
531
|
+
|
|
532
|
+
<div v-if="provenance.sourceRepo">
|
|
533
|
+
<a :href="provenance.sourceRepo" target="_blank" rel="noopener" class="concept-link">{{ t('sidebar.viewSource') }}</a>
|
|
534
|
+
</div>
|
|
481
535
|
</div>
|
|
482
536
|
</div>
|
|
483
537
|
</div>
|
|
@@ -15,7 +15,7 @@ const props = defineProps<{
|
|
|
15
15
|
const router = useRouter();
|
|
16
16
|
const { getColor } = useDsStyle();
|
|
17
17
|
const store = useVocabularyStore();
|
|
18
|
-
const { locale } = useI18n();
|
|
18
|
+
const { locale, t } = useI18n();
|
|
19
19
|
|
|
20
20
|
function viewConcept() {
|
|
21
21
|
router.push({
|
|
@@ -69,7 +69,7 @@ const langCount = computed(() => {
|
|
|
69
69
|
</div>
|
|
70
70
|
<!-- Language coverage -->
|
|
71
71
|
<div class="flex items-center gap-1.5 mt-2.5">
|
|
72
|
-
<span class="text-[11px] text-ink-300">{{ langCount }} lang</span>
|
|
72
|
+
<span class="text-[11px] text-ink-300">{{ langCount }} {{ t('concept.lang') }}</span>
|
|
73
73
|
<div class="flex gap-0.5">
|
|
74
74
|
<span
|
|
75
75
|
v-for="lang in manifestLanguages"
|
|
@@ -23,7 +23,7 @@ import NonVerbalRepDisplay from './NonVerbalRepDisplay.vue';
|
|
|
23
23
|
import CitationDisplay from './CitationDisplay.vue';
|
|
24
24
|
import { useI18n } from '../i18n';
|
|
25
25
|
|
|
26
|
-
const { t } = useI18n();
|
|
26
|
+
const { t, locale } = useI18n();
|
|
27
27
|
|
|
28
28
|
const props = defineProps<{
|
|
29
29
|
concept: Concept;
|
|
@@ -36,7 +36,7 @@ const props = defineProps<{
|
|
|
36
36
|
const router = useRouter();
|
|
37
37
|
const store = useVocabularyStore();
|
|
38
38
|
const { getColor } = useDsStyle();
|
|
39
|
-
const { config: siteConfig } = useSiteConfig();
|
|
39
|
+
const { config: siteConfig, localizedDatasetField } = useSiteConfig();
|
|
40
40
|
const factory = getFactory();
|
|
41
41
|
|
|
42
42
|
const activeTab = ref<'rdf' | 'definition' | 'history'>('definition');
|
|
@@ -62,7 +62,15 @@ function copyUri() {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
const languages = computed(() => {
|
|
65
|
-
|
|
65
|
+
const sorted = sortLanguages(props.concept.languages, props.manifest.languageOrder);
|
|
66
|
+
// Put current UI locale first
|
|
67
|
+
const current = locale.value;
|
|
68
|
+
const idx = sorted.indexOf(current);
|
|
69
|
+
if (idx > 0) {
|
|
70
|
+
sorted.splice(idx, 1);
|
|
71
|
+
sorted.unshift(current);
|
|
72
|
+
}
|
|
73
|
+
return sorted;
|
|
66
74
|
});
|
|
67
75
|
|
|
68
76
|
// Collapsible language sections — expand all with content, collapse those without
|
|
@@ -369,11 +377,11 @@ const nonVerbalReps = computed(() => {
|
|
|
369
377
|
<router-link :to="{ name: 'home' }" class="hover:text-ink-700 transition-colors whitespace-nowrap">{{ t('nav.home') }}</router-link>
|
|
370
378
|
<span class="text-ink-200">/</span>
|
|
371
379
|
<router-link :to="{ name: 'dataset', params: { registerId: manifest.id }}" class="hover:text-ink-700 transition-colors truncate max-w-[180px]">
|
|
372
|
-
{{ manifest.title }}
|
|
380
|
+
{{ localizedDatasetField(manifest.id, 'title', manifest.title) }}
|
|
373
381
|
</router-link>
|
|
374
382
|
<span class="text-ink-200">/</span>
|
|
375
383
|
<span class="text-ink-600 font-mono text-xs">{{ conceptId }}</span>
|
|
376
|
-
<span v-if="conceptPosition" class="text-[10px] text-ink-300 tabular-nums ml-1 whitespace-nowrap">({{ conceptPosition.index }} of {{ conceptPosition.total.toLocaleString() }})</span>
|
|
384
|
+
<span v-if="conceptPosition" class="text-[10px] text-ink-300 tabular-nums ml-1 whitespace-nowrap">({{ conceptPosition.index }} {{ t('concept.of') }} {{ conceptPosition.total.toLocaleString() }})</span>
|
|
377
385
|
</nav>
|
|
378
386
|
<!-- Prev/Next navigation -->
|
|
379
387
|
<div v-if="adjacent.prev || adjacent.next" class="flex items-center gap-1 flex-shrink-0">
|
|
@@ -381,7 +389,7 @@ const nonVerbalReps = computed(() => {
|
|
|
381
389
|
v-if="adjacent.prev"
|
|
382
390
|
@click="goAdjacent(adjacent.prev)"
|
|
383
391
|
class="p-2.5 rounded-lg text-ink-300 hover:text-ink-600 hover:bg-ink-50 transition-colors min-w-[44px] min-h-[44px] flex items-center justify-center"
|
|
384
|
-
title="
|
|
392
|
+
:title="t('concept.previous') + ' (←)'"
|
|
385
393
|
>
|
|
386
394
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/></svg>
|
|
387
395
|
</button>
|
|
@@ -389,7 +397,7 @@ const nonVerbalReps = computed(() => {
|
|
|
389
397
|
v-if="adjacent.next"
|
|
390
398
|
@click="goAdjacent(adjacent.next)"
|
|
391
399
|
class="p-2.5 rounded-lg text-ink-300 hover:text-ink-600 hover:bg-ink-50 transition-colors min-w-[44px] min-h-[44px] flex items-center justify-center"
|
|
392
|
-
title="
|
|
400
|
+
:title="t('concept.next') + ' (→)'"
|
|
393
401
|
>
|
|
394
402
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
|
|
395
403
|
</button>
|
|
@@ -410,7 +418,7 @@ const nonVerbalReps = computed(() => {
|
|
|
410
418
|
{{ entryStatusLabel(engConcept.entryStatus) }}
|
|
411
419
|
</span>
|
|
412
420
|
<span class="badge badge-gray" v-if="manifest.owner">{{ manifest.owner }}</span>
|
|
413
|
-
<span class="badge badge-purple">{{ languages.length }} languages</span>
|
|
421
|
+
<span class="badge badge-purple">{{ languages.length }} {{ t('concept.languages') }}</span>
|
|
414
422
|
</div>
|
|
415
423
|
</div>
|
|
416
424
|
|
|
@@ -426,7 +434,7 @@ const nonVerbalReps = computed(() => {
|
|
|
426
434
|
? 'bg-blue-600 text-white shadow-sm md:bg-transparent md:text-blue-600 md:border-blue-500 md:shadow-none'
|
|
427
435
|
: 'text-ink-500 hover:text-ink-700 md:text-ink-400 md:border-transparent md:hover:text-ink-600'"
|
|
428
436
|
>
|
|
429
|
-
|
|
437
|
+
{{ t('concept.definition') }}
|
|
430
438
|
</button>
|
|
431
439
|
<button
|
|
432
440
|
role="tab"
|
|
@@ -437,7 +445,7 @@ const nonVerbalReps = computed(() => {
|
|
|
437
445
|
? 'bg-blue-600 text-white shadow-sm md:bg-transparent md:text-blue-600 md:border-blue-500 md:shadow-none'
|
|
438
446
|
: 'text-ink-500 hover:text-ink-700 md:text-ink-400 md:border-transparent md:hover:text-ink-600'"
|
|
439
447
|
>
|
|
440
|
-
|
|
448
|
+
{{ t('concept.rdf') }}
|
|
441
449
|
</button>
|
|
442
450
|
<button
|
|
443
451
|
role="tab"
|
|
@@ -448,7 +456,7 @@ const nonVerbalReps = computed(() => {
|
|
|
448
456
|
? 'bg-blue-600 text-white shadow-sm md:bg-transparent md:text-blue-600 md:border-blue-500 md:shadow-none'
|
|
449
457
|
: 'text-ink-500 hover:text-ink-700 md:text-ink-400 md:border-transparent md:hover:text-ink-600'"
|
|
450
458
|
>
|
|
451
|
-
|
|
459
|
+
{{ t('concept.history') }}
|
|
452
460
|
</button>
|
|
453
461
|
</div>
|
|
454
462
|
|
|
@@ -456,9 +464,9 @@ const nonVerbalReps = computed(() => {
|
|
|
456
464
|
<div v-if="activeTab === 'definition'" role="tabpanel">
|
|
457
465
|
<!-- Expand/Collapse all toggle -->
|
|
458
466
|
<div v-if="allLangContent.length > 1" class="flex items-center justify-between mb-3">
|
|
459
|
-
<span class="text-xs text-ink-400">{{ languages.length }} languages</span>
|
|
467
|
+
<span class="text-xs text-ink-400">{{ languages.length }} {{ t('concept.languages') }}</span>
|
|
460
468
|
<button @click="toggleAll" class="text-xs text-ink-400 hover:text-ink-600 transition-colors px-3 py-2">
|
|
461
|
-
{{ allCollapsed ? '
|
|
469
|
+
{{ allCollapsed ? t('concept.expandAll') : t('concept.collapseAll') }}
|
|
462
470
|
</button>
|
|
463
471
|
</div>
|
|
464
472
|
<div class="lg:flex lg:gap-8">
|
|
@@ -487,7 +495,7 @@ const nonVerbalReps = computed(() => {
|
|
|
487
495
|
<div v-else class="w-full flex items-center gap-2.5 px-3 sm:px-4 py-3">
|
|
488
496
|
<span class="text-xs font-semibold text-ink-500 bg-ink-50 px-1.5 py-0.5 rounded">{{ langName(lc.lang) }}</span>
|
|
489
497
|
<span class="font-medium text-ink-800 text-sm" v-html="renderMath(getTermForLang(lc.lang))"></span>
|
|
490
|
-
<span class="text-xs text-ink-200 ml-2 italic">
|
|
498
|
+
<span class="text-xs text-ink-200 ml-2 italic">{{ t('concept.designationOnly') }}</span>
|
|
491
499
|
<span v-if="lc.entryStatus" class="badge text-[10px] ml-auto" :class="entryStatusColor(lc.entryStatus)" :title="entryStatusDefinition(lc.entryStatus) ?? ''">{{ entryStatusLabel(lc.entryStatus) }}</span>
|
|
492
500
|
</div>
|
|
493
501
|
<!-- Collapsed preview -->
|
|
@@ -599,30 +607,30 @@ const nonVerbalReps = computed(() => {
|
|
|
599
607
|
|
|
600
608
|
<!-- Ontological metadata -->
|
|
601
609
|
<div v-if="lc.classification || lc.reviewType || lc.release || lc.lineageSourceSimilarity != null || lc.lcScript || lc.lcSystem" class="border-t border-ink-100/60 pt-2 mt-2">
|
|
602
|
-
<div class="text-[10px] uppercase tracking-wide text-ink-300 font-medium mb-1.5">
|
|
610
|
+
<div class="text-[10px] uppercase tracking-wide text-ink-300 font-medium mb-1.5">{{ t('concept.ontologicalMetadata') }}</div>
|
|
603
611
|
<dl class="grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-xs">
|
|
604
612
|
<template v-if="lc.classification">
|
|
605
|
-
<dt class="text-ink-300">
|
|
613
|
+
<dt class="text-ink-300">{{ t('concept.classificationLabel') }}</dt>
|
|
606
614
|
<dd class="text-ink-700">{{ lc.classification }}</dd>
|
|
607
615
|
</template>
|
|
608
616
|
<template v-if="lc.reviewType">
|
|
609
|
-
<dt class="text-ink-300">
|
|
617
|
+
<dt class="text-ink-300">{{ t('concept.reviewType') }}</dt>
|
|
610
618
|
<dd class="text-ink-700">{{ lc.reviewType }}</dd>
|
|
611
619
|
</template>
|
|
612
620
|
<template v-if="lc.release">
|
|
613
|
-
<dt class="text-ink-300">
|
|
621
|
+
<dt class="text-ink-300">{{ t('concept.release') }}</dt>
|
|
614
622
|
<dd class="text-ink-700">{{ lc.release }}</dd>
|
|
615
623
|
</template>
|
|
616
624
|
<template v-if="lc.lineageSourceSimilarity != null">
|
|
617
|
-
<dt class="text-ink-300">
|
|
625
|
+
<dt class="text-ink-300">{{ t('concept.lineageSimilarity') }}</dt>
|
|
618
626
|
<dd class="text-ink-700">{{ lc.lineageSourceSimilarity }}%</dd>
|
|
619
627
|
</template>
|
|
620
628
|
<template v-if="lc.lcScript">
|
|
621
|
-
<dt class="text-ink-300">
|
|
629
|
+
<dt class="text-ink-300">{{ t('concept.script') }}</dt>
|
|
622
630
|
<dd class="text-ink-700 font-mono">{{ lc.lcScript }}</dd>
|
|
623
631
|
</template>
|
|
624
632
|
<template v-if="lc.lcSystem">
|
|
625
|
-
<dt class="text-ink-300">
|
|
633
|
+
<dt class="text-ink-300">{{ t('concept.conversionSystem') }}</dt>
|
|
626
634
|
<dd class="text-ink-700 font-mono">{{ lc.lcSystem }}</dd>
|
|
627
635
|
</template>
|
|
628
636
|
</dl>
|
|
@@ -727,7 +735,7 @@ const nonVerbalReps = computed(() => {
|
|
|
727
735
|
|
|
728
736
|
<!-- Language quick-jump -->
|
|
729
737
|
<div class="card p-5">
|
|
730
|
-
<div class="section-label">
|
|
738
|
+
<div class="section-label">{{ t('concept.languagesSidebar', { count: languages.length }) }}</div>
|
|
731
739
|
<div class="space-y-1 mt-3 max-h-80 overflow-y-auto">
|
|
732
740
|
<button
|
|
733
741
|
v-for="lang in languages"
|
|
@@ -740,7 +748,7 @@ const nonVerbalReps = computed(() => {
|
|
|
740
748
|
<span
|
|
741
749
|
class="w-1.5 h-1.5 rounded-full flex-shrink-0"
|
|
742
750
|
:class="hasDefinition(lang) ? 'bg-emerald-400' : 'bg-ink-200'"
|
|
743
|
-
:title="hasDefinition(lang) ? '
|
|
751
|
+
:title="hasDefinition(lang) ? t('concept.hasDefinition') : t('concept.designationOnlyTitle')"
|
|
744
752
|
></span>
|
|
745
753
|
<span class="text-sm font-medium text-ink-800 group-hover:text-ink-900 transition-colors" v-html="renderMath(getTermForLang(lang))"></span>
|
|
746
754
|
</div>
|
|
@@ -763,24 +771,24 @@ const nonVerbalReps = computed(() => {
|
|
|
763
771
|
<div class="section-label">{{ t('concept.metadata') }}</div>
|
|
764
772
|
<dl class="space-y-2 text-xs mt-3">
|
|
765
773
|
<div v-if="managedStatus">
|
|
766
|
-
<dt class="text-ink-300">
|
|
774
|
+
<dt class="text-ink-300">{{ t('concept.status') }}</dt>
|
|
767
775
|
<dd class="mt-0.5">
|
|
768
776
|
<span class="badge text-[10px]" :class="conceptStatusColor(managedStatus)" :title="conceptStatusDefinition(managedStatus) ?? ''">{{ conceptStatusLabel(managedStatus) }}</span>
|
|
769
777
|
</dd>
|
|
770
778
|
</div>
|
|
771
779
|
<div v-if="engConcept?.reviewDate">
|
|
772
|
-
<dt class="text-ink-300">
|
|
780
|
+
<dt class="text-ink-300">{{ t('concept.reviewDate') }}</dt>
|
|
773
781
|
<dd class="text-ink-700 mt-0.5">{{ engConcept.reviewDate.slice(0, 10) }}</dd>
|
|
774
782
|
</div>
|
|
775
783
|
<div v-if="engConcept?.reviewDecisionEvent">
|
|
776
|
-
<dt class="text-ink-300">
|
|
784
|
+
<dt class="text-ink-300">{{ t('concept.decision') }}</dt>
|
|
777
785
|
<dd class="text-ink-700 mt-0.5">{{ engConcept.reviewDecisionEvent }}</dd>
|
|
778
786
|
</div>
|
|
779
787
|
<div>
|
|
780
|
-
<dt class="text-ink-300">
|
|
788
|
+
<dt class="text-ink-300">{{ t('concept.uri') }}</dt>
|
|
781
789
|
<dd class="font-mono text-ink-600 break-all mt-0.5 text-[11px] flex items-start gap-1.5">
|
|
782
790
|
<span class="break-all">{{ conceptUriValue }}</span>
|
|
783
|
-
<button @click="copyUri" class="flex-shrink-0 p-0.5 rounded text-ink-300 hover:text-ink-600 hover:bg-ink-50 transition-colors" :title="uriCopied ? '
|
|
791
|
+
<button @click="copyUri" class="flex-shrink-0 p-0.5 rounded text-ink-300 hover:text-ink-600 hover:bg-ink-50 transition-colors" :title="uriCopied ? t('concept.uriCopied') : t('concept.copyUri')" :aria-label="uriCopied ? t('concept.uriCopied') : t('concept.copyUri')">
|
|
784
792
|
<svg v-if="!uriCopied" class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10a2 2 0 01-2-2v-1m6 4v-3a2 2 0 00-2-2H8"/></svg>
|
|
785
793
|
<svg v-else class="w-3.5 h-3.5 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>
|
|
786
794
|
</button>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from 'vue';
|
|
3
3
|
import { FORMAT_REGISTRY } from '../utils/concept-formats';
|
|
4
|
+
import { useI18n } from '../i18n';
|
|
5
|
+
|
|
6
|
+
const { t } = useI18n();
|
|
4
7
|
|
|
5
8
|
const props = defineProps<{
|
|
6
9
|
registerId: string;
|
|
@@ -27,7 +30,7 @@ const links = computed<FormatLink[]>(() =>
|
|
|
27
30
|
|
|
28
31
|
<template>
|
|
29
32
|
<div v-if="links.length" class="space-y-2">
|
|
30
|
-
<div class="section-label">
|
|
33
|
+
<div class="section-label">{{ t('concept.downloads') }}</div>
|
|
31
34
|
<div class="flex flex-wrap gap-2">
|
|
32
35
|
<a
|
|
33
36
|
v-for="link in links"
|
package/src/config/types.ts
CHANGED
|
@@ -12,6 +12,10 @@ export interface LogoConfig {
|
|
|
12
12
|
alt: string;
|
|
13
13
|
url?: string;
|
|
14
14
|
remoteUrl?: string;
|
|
15
|
+
light?: string;
|
|
16
|
+
dark?: string;
|
|
17
|
+
localLight?: string;
|
|
18
|
+
localDark?: string;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
export interface SiteBranding {
|
|
@@ -90,6 +94,7 @@ export interface DatasetConfig {
|
|
|
90
94
|
color?: string;
|
|
91
95
|
tags?: string[];
|
|
92
96
|
languageOrder?: string[];
|
|
97
|
+
ref?: string;
|
|
93
98
|
downloads?: string[];
|
|
94
99
|
translations?: Record<string, { title?: string; description?: string }>;
|
|
95
100
|
}
|
|
@@ -21,8 +21,8 @@ export interface RuntimeSiteConfig {
|
|
|
21
21
|
header?: { family: string; source: string; weights?: number[]; url?: string };
|
|
22
22
|
body?: { family: string; source: string; weights?: number[]; url?: string };
|
|
23
23
|
};
|
|
24
|
-
logo?: { path: string; alt: string; url?: string };
|
|
25
|
-
footerLogo?: { path: string; alt: string; url?: string };
|
|
24
|
+
logo?: { path: string; alt: string; url?: string; light?: string; dark?: string };
|
|
25
|
+
footerLogo?: { path: string; alt: string; url?: string; light?: string; dark?: string };
|
|
26
26
|
ownerName?: string;
|
|
27
27
|
ownerUrl?: string;
|
|
28
28
|
};
|
package/src/i18n/locales/eng.yml
CHANGED
|
@@ -64,6 +64,37 @@ concept.lifecycleDates: Lifecycle dates
|
|
|
64
64
|
concept.conceptSources: Concept sources
|
|
65
65
|
concept.incoming: Incoming
|
|
66
66
|
concept.outgoing: Outgoing
|
|
67
|
+
concept.lang: lang
|
|
68
|
+
concept.rdf: RDF
|
|
69
|
+
concept.history: History
|
|
70
|
+
concept.languages: languages
|
|
71
|
+
concept.collapseAll: Collapse all
|
|
72
|
+
concept.expandAll: Expand all
|
|
73
|
+
concept.designationOnly: designation only
|
|
74
|
+
concept.previous: Previous concept
|
|
75
|
+
concept.next: Next concept
|
|
76
|
+
concept.of: of
|
|
77
|
+
concept.hasDefinition: Has definition
|
|
78
|
+
concept.designationOnlyTitle: Designation only
|
|
79
|
+
concept.languagesSidebar: "Languages ({count})"
|
|
80
|
+
concept.ontologicalMetadata: Ontological metadata
|
|
81
|
+
concept.classificationLabel: Classification
|
|
82
|
+
concept.reviewType: Review type
|
|
83
|
+
concept.release: Release
|
|
84
|
+
concept.lineageSimilarity: Lineage similarity
|
|
85
|
+
concept.script: Script
|
|
86
|
+
concept.conversionSystem: Conversion system
|
|
87
|
+
concept.status: Status
|
|
88
|
+
concept.reviewDate: Review Date
|
|
89
|
+
concept.decision: Decision
|
|
90
|
+
concept.uri: URI
|
|
91
|
+
concept.copyUri: Copy URI to clipboard
|
|
92
|
+
concept.uriCopied: URI copied
|
|
93
|
+
concept.downloads: Downloads
|
|
94
|
+
concept.failedToLoad: Failed to load concept
|
|
95
|
+
concept.notFound: Concept not found
|
|
96
|
+
concept.notFoundMsg: "The concept \"{id}\" does not exist in this dataset."
|
|
97
|
+
concept.backToDataset: Back to dataset
|
|
67
98
|
|
|
68
99
|
# Sidebar
|
|
69
100
|
sidebar.overview: Overview
|
|
@@ -126,3 +157,11 @@ graph.stubStatus: stub
|
|
|
126
157
|
graph.collapseControls: Collapse controls
|
|
127
158
|
graph.expandControls: Expand controls
|
|
128
159
|
graph.loading: Loading graph data...
|
|
160
|
+
|
|
161
|
+
# Sidebar provenance
|
|
162
|
+
sidebar.provenance: Provenance
|
|
163
|
+
sidebar.publishedBy: published by
|
|
164
|
+
sidebar.updated: Updated
|
|
165
|
+
sidebar.concepts: concepts
|
|
166
|
+
sidebar.languages: languages
|
|
167
|
+
sidebar.viewSource: View source
|
package/src/i18n/locales/fra.yml
CHANGED
|
@@ -64,6 +64,37 @@ concept.lifecycleDates: Dates du cycle de vie
|
|
|
64
64
|
concept.conceptSources: Sources du concept
|
|
65
65
|
concept.incoming: Entrantes
|
|
66
66
|
concept.outgoing: Sortantes
|
|
67
|
+
concept.lang: lang.
|
|
68
|
+
concept.rdf: RDF
|
|
69
|
+
concept.history: Historique
|
|
70
|
+
concept.languages: langues
|
|
71
|
+
concept.collapseAll: Tout réduire
|
|
72
|
+
concept.expandAll: Tout développer
|
|
73
|
+
concept.designationOnly: désignation uniquement
|
|
74
|
+
concept.previous: Concept précédent
|
|
75
|
+
concept.next: Concept suivant
|
|
76
|
+
concept.of: de
|
|
77
|
+
concept.hasDefinition: A une définition
|
|
78
|
+
concept.designationOnlyTitle: Désignation uniquement
|
|
79
|
+
concept.languagesSidebar: "Langues ({count})"
|
|
80
|
+
concept.ontologicalMetadata: Métadonnées ontologiques
|
|
81
|
+
concept.classificationLabel: Classification
|
|
82
|
+
concept.reviewType: Type de révision
|
|
83
|
+
concept.release: Version
|
|
84
|
+
concept.lineageSimilarity: Similarité de lignée
|
|
85
|
+
concept.script: Écriture
|
|
86
|
+
concept.conversionSystem: Système de conversion
|
|
87
|
+
concept.status: Statut
|
|
88
|
+
concept.reviewDate: Date de révision
|
|
89
|
+
concept.decision: Décision
|
|
90
|
+
concept.uri: URI
|
|
91
|
+
concept.copyUri: Copier l'URI dans le presse-papiers
|
|
92
|
+
concept.uriCopied: URI copiée
|
|
93
|
+
concept.downloads: Téléchargements
|
|
94
|
+
concept.failedToLoad: Échec du chargement du concept
|
|
95
|
+
concept.notFound: Concept non trouvé
|
|
96
|
+
concept.notFoundMsg: "Le concept « {id} » n'existe pas dans ce jeu de données."
|
|
97
|
+
concept.backToDataset: Retour au jeu de données
|
|
67
98
|
|
|
68
99
|
# Sidebar
|
|
69
100
|
sidebar.overview: Vue d'ensemble
|
|
@@ -126,3 +157,11 @@ graph.stubStatus: non chargé
|
|
|
126
157
|
graph.collapseControls: Réduire les contrôles
|
|
127
158
|
graph.expandControls: Développer les contrôles
|
|
128
159
|
graph.loading: Chargement des données du graphe...
|
|
160
|
+
|
|
161
|
+
# Sidebar provenance
|
|
162
|
+
sidebar.provenance: Provenance
|
|
163
|
+
sidebar.publishedBy: publié par
|
|
164
|
+
sidebar.updated: Mis à jour
|
|
165
|
+
sidebar.concepts: concepts
|
|
166
|
+
sidebar.languages: langues
|
|
167
|
+
sidebar.viewSource: Voir la source
|
package/src/style.css
CHANGED
|
@@ -398,6 +398,12 @@
|
|
|
398
398
|
.dark .placeholder\:text-ink-300::placeholder { color: #484a6e !important; }
|
|
399
399
|
|
|
400
400
|
/* ── Semantic colors (badges, accent text) ── */
|
|
401
|
+
.dark .concept-link {
|
|
402
|
+
filter: brightness(1.5);
|
|
403
|
+
}
|
|
404
|
+
.dark .concept-link:hover {
|
|
405
|
+
filter: brightness(1.3) underline;
|
|
406
|
+
}
|
|
401
407
|
.dark .bg-blue-50 { background-color: rgba(59, 130, 246, 0.15) !important; }
|
|
402
408
|
.dark .text-blue-600 { color: #93bbfd !important; }
|
|
403
409
|
.dark .text-blue-700 { color: #7aa8fb !important; }
|
package/src/utils/lang.ts
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
fra: '
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
import { locale } from '../i18n';
|
|
2
|
+
|
|
3
|
+
const LANG_NAMES: Record<string, Record<string, string>> = {
|
|
4
|
+
eng: { eng: 'English', fra: 'Anglais' },
|
|
5
|
+
ara: { eng: 'Arabic', fra: 'Arabe' },
|
|
6
|
+
deu: { eng: 'German', fra: 'Allemand' },
|
|
7
|
+
fra: { eng: 'French', fra: 'Français' },
|
|
8
|
+
spa: { eng: 'Spanish', fra: 'Espagnol' },
|
|
9
|
+
ita: { eng: 'Italian', fra: 'Italien' },
|
|
10
|
+
jpn: { eng: 'Japanese', fra: 'Japonais' },
|
|
11
|
+
kor: { eng: 'Korean', fra: 'Coréen' },
|
|
12
|
+
pol: { eng: 'Polish', fra: 'Polonais' },
|
|
13
|
+
por: { eng: 'Portuguese', fra: 'Portugais' },
|
|
14
|
+
srp: { eng: 'Serbian', fra: 'Serbe' },
|
|
15
|
+
swe: { eng: 'Swedish', fra: 'Suédois' },
|
|
16
|
+
zho: { eng: 'Chinese', fra: 'Chinois' },
|
|
17
|
+
rus: { eng: 'Russian', fra: 'Russe' },
|
|
18
|
+
fin: { eng: 'Finnish', fra: 'Finnois' },
|
|
19
|
+
dan: { eng: 'Danish', fra: 'Danois' },
|
|
20
|
+
nld: { eng: 'Dutch', fra: 'Néerlandais' },
|
|
21
|
+
msa: { eng: 'Malay', fra: 'Malais' },
|
|
22
|
+
nob: { eng: 'Norwegian Bokmål', fra: 'Norvégien Bokmål' },
|
|
23
|
+
nno: { eng: 'Norwegian Nynorsk', fra: 'Norvégien Nynorsk' },
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
export function langName(code: string): string {
|
|
25
|
-
return LANG_NAMES[code] ?? code;
|
|
27
|
+
return LANG_NAMES[code]?.[locale.value] ?? LANG_NAMES[code]?.eng ?? code;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export function langLabel(code: string): string {
|
|
@@ -3,6 +3,9 @@ import { computed, watch, ref, onMounted, onUnmounted } from 'vue';
|
|
|
3
3
|
import { useRouter } from 'vue-router';
|
|
4
4
|
import { useVocabularyStore } from '../stores/vocabulary';
|
|
5
5
|
import ConceptDetail from '../components/ConceptDetail.vue';
|
|
6
|
+
import { useI18n } from '../i18n';
|
|
7
|
+
|
|
8
|
+
const { t } = useI18n();
|
|
6
9
|
|
|
7
10
|
const props = defineProps<{
|
|
8
11
|
registerId: string;
|
|
@@ -108,12 +111,12 @@ onUnmounted(() => window.removeEventListener('keydown', onKeydown));
|
|
|
108
111
|
</div>
|
|
109
112
|
<div v-else-if="localError" class="max-w-xl mx-auto text-center py-20">
|
|
110
113
|
<div class="card p-8 border-red-200 bg-red-50/50">
|
|
111
|
-
<p class="text-red-700 font-medium mb-1">
|
|
114
|
+
<p class="text-red-700 font-medium mb-1">{{ t('concept.failedToLoad') }}</p>
|
|
112
115
|
<p class="text-sm text-red-600/80 mb-4">{{ localError }}</p>
|
|
113
116
|
<div class="flex gap-2 justify-center">
|
|
114
|
-
<button @click="loadConcept(registerId, conceptId)" class="btn-primary">
|
|
117
|
+
<button @click="loadConcept(registerId, conceptId)" class="btn-primary">{{ t('dataset.retry') }}</button>
|
|
115
118
|
<router-link :to="{ name: 'dataset', params: { registerId } }" class="btn-secondary">
|
|
116
|
-
|
|
119
|
+
{{ t('concept.backToDataset') }}
|
|
117
120
|
</router-link>
|
|
118
121
|
</div>
|
|
119
122
|
</div>
|
|
@@ -121,10 +124,10 @@ onUnmounted(() => window.removeEventListener('keydown', onKeydown));
|
|
|
121
124
|
<div v-else-if="!concept" class="max-w-xl mx-auto text-center py-20">
|
|
122
125
|
<div class="card p-8">
|
|
123
126
|
<div class="text-ink-200 text-5xl mb-3 font-serif">?</div>
|
|
124
|
-
<h3 class="text-lg font-medium text-ink-700 mb-2">
|
|
125
|
-
<p class="text-sm text-ink-400 mb-4">
|
|
127
|
+
<h3 class="text-lg font-medium text-ink-700 mb-2">{{ t('concept.notFound') }}</h3>
|
|
128
|
+
<p class="text-sm text-ink-400 mb-4">{{ t('concept.notFoundMsg', { id: conceptId }) }}</p>
|
|
126
129
|
<router-link :to="{ name: 'dataset', params: { registerId } }" class="btn-primary">
|
|
127
|
-
|
|
130
|
+
{{ t('concept.backToDataset') }}
|
|
128
131
|
</router-link>
|
|
129
132
|
</div>
|
|
130
133
|
</div>
|
|
@@ -7,15 +7,19 @@ import { FORMAT_LABELS } from '../config/types';
|
|
|
7
7
|
import { langName, langLabel, sortLanguages } from '../utils/lang';
|
|
8
8
|
import ConceptCard from '../components/ConceptCard.vue';
|
|
9
9
|
import { useI18n } from '../i18n';
|
|
10
|
+
import { useSiteConfig } from '../config/use-site-config';
|
|
10
11
|
|
|
11
12
|
const props = defineProps<{ registerId: string }>();
|
|
12
13
|
|
|
13
14
|
const store = useVocabularyStore();
|
|
14
15
|
const { getStyle } = useDsStyle();
|
|
15
|
-
const { loading, localError
|
|
16
|
+
const { ensureLoaded, loading, localError } = useDatasetLoader(() => props.registerId);
|
|
16
17
|
const { t } = useI18n();
|
|
18
|
+
const { localizedDatasetField } = useSiteConfig();
|
|
17
19
|
|
|
18
20
|
const manifest = computed(() => store.manifests.get(props.registerId));
|
|
21
|
+
const localizedTitle = computed(() => localizedDatasetField(props.registerId, 'title', manifest.value?.title));
|
|
22
|
+
const localizedDescription = computed(() => localizedDatasetField(props.registerId, 'description', manifest.value?.description));
|
|
19
23
|
const adapter = computed(() => store.datasets.get(props.registerId));
|
|
20
24
|
const chunkLoading = ref(false);
|
|
21
25
|
|
|
@@ -184,13 +188,13 @@ function goToPage(p: number) {
|
|
|
184
188
|
<nav aria-label="Breadcrumb" class="flex items-center gap-1.5 text-sm text-ink-400 mb-6">
|
|
185
189
|
<router-link :to="{ name: 'home' }" class="hover:text-ink-700 transition-colors">{{ t('nav.home') }}</router-link>
|
|
186
190
|
<span class="text-ink-200">/</span>
|
|
187
|
-
<span class="text-ink-700">{{
|
|
191
|
+
<span class="text-ink-700">{{ localizedTitle }}</span>
|
|
188
192
|
</nav>
|
|
189
193
|
|
|
190
194
|
<!-- Header -->
|
|
191
195
|
<div v-if="manifest" class="mb-8">
|
|
192
|
-
<h1 class="font-serif text-3xl text-ink-800 mb-2">{{
|
|
193
|
-
<p class="text-ink-400 leading-relaxed max-w-2xl">{{
|
|
196
|
+
<h1 class="font-serif text-3xl text-ink-800 mb-2">{{ localizedTitle }}</h1>
|
|
197
|
+
<p class="text-ink-400 leading-relaxed max-w-2xl">{{ localizedDescription }}</p>
|
|
194
198
|
<div class="flex flex-wrap gap-2 mt-4">
|
|
195
199
|
<span class="badge" :style="{ backgroundColor: getStyle(registerId).light, color: getStyle(registerId).dark }">{{ manifest.conceptCount.toLocaleString() }} {{ t('dataset.concepts') }}</span>
|
|
196
200
|
<span class="badge badge-gray">{{ manifest.languages.length }} {{ t('dataset.languages') }}</span>
|