@canopy-iiif/app 1.9.13 → 1.9.15

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/lib/build/iiif.js CHANGED
@@ -23,7 +23,11 @@ const {
23
23
  getLocaleRouteConfig,
24
24
  getDefaultLocaleCode,
25
25
  } = require("../common");
26
- const {readCanopyLocalesWithMessages} = require("../locales");
26
+ const {
27
+ readCanopyLocalesWithMessages,
28
+ readLocaleMessages,
29
+ buildLocaleRuntimeScript,
30
+ } = require("../locales");
27
31
  const {resolveCanopyConfigPath} = require("../config-path");
28
32
  const mdx = require("./mdx");
29
33
  const {log, logLine, logResponse} = require("./log");
@@ -2141,6 +2145,10 @@ async function buildIiifCollectionPages(CONFIG) {
2141
2145
  if (siteLanguageToggle) {
2142
2146
  siteContext.languageToggle = siteLanguageToggle;
2143
2147
  }
2148
+ try {
2149
+ const localeMessages = readLocaleMessages(pageLocale);
2150
+ if (localeMessages) siteContext.localeMessages = localeMessages;
2151
+ } catch (_) {}
2144
2152
  try {
2145
2153
  const localeRoutes = getLocaleRouteConfig(pageLocale);
2146
2154
  if (localeRoutes) siteContext.routes = localeRoutes;
@@ -2313,6 +2321,10 @@ async function buildIiifCollectionPages(CONFIG) {
2313
2321
  );
2314
2322
 
2315
2323
  const headSegments = [head];
2324
+ try {
2325
+ const localeScript = buildLocaleRuntimeScript(pageLocale);
2326
+ if (localeScript) headSegments.push(localeScript);
2327
+ } catch (_) {}
2316
2328
  const needsReact = !!(
2317
2329
  needsHydrateViewer ||
2318
2330
  needsRelated ||
package/lib/build/mdx.js CHANGED
@@ -20,7 +20,7 @@ const globalRoot = typeof globalThis !== "undefined" ? globalThis : global;
20
20
  if (globalRoot && typeof globalRoot.__canopyRequire !== "function") {
21
21
  globalRoot.__canopyRequire = typeof require === "function" ? require : null;
22
22
  }
23
- const {readCanopyLocalesWithMessages} = require("../locales");
23
+ const {readCanopyLocalesWithMessages, readLocaleMessages} = require("../locales");
24
24
 
25
25
  function getSiteLanguageToggle() {
26
26
  try {
@@ -1083,6 +1083,10 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
1083
1083
  if (siteLanguageToggle) {
1084
1084
  siteContext.languageToggle = siteLanguageToggle;
1085
1085
  }
1086
+ try {
1087
+ const localeMessages = readLocaleMessages(pageLocale);
1088
+ if (localeMessages) siteContext.localeMessages = localeMessages;
1089
+ } catch (_) {}
1086
1090
  try {
1087
1091
  const localeRoutes = getLocaleRouteConfig(pageLocale);
1088
1092
  if (localeRoutes) siteContext.routes = localeRoutes;
@@ -14,6 +14,7 @@ const {
14
14
  } = require('../common');
15
15
  const { log } = require('./log');
16
16
  const mdx = require('./mdx');
17
+ const {buildLocaleRuntimeScript} = require('../locales');
17
18
  const navigation = require('../components/navigation');
18
19
  const referenced = require('../components/referenced');
19
20
 
@@ -325,6 +326,10 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
325
326
  if (BASE_PATH) vendorTag = `<script>window.CANOPY_BASE_PATH=${JSON.stringify(BASE_PATH)}</script>` + vendorTag;
326
327
  } catch (_) {}
327
328
  const headSegments = [head];
329
+ try {
330
+ const localeScript = buildLocaleRuntimeScript(pageLocale);
331
+ if (localeScript) headSegments.push(localeScript);
332
+ } catch (_) {}
328
333
  const extraScripts = [];
329
334
  const pushClassicScript = (src) => {
330
335
  if (!src || src === jsRel) return;
@@ -23,6 +23,8 @@ module.exports = {
23
23
  navigation: 'navigation',
24
24
  section_navigation: 'section navigation',
25
25
  content_navigation: 'content navigation',
26
+ primary_navigation: 'Primary navigation',
27
+ metadata: 'metadata',
26
28
  gallery: 'gallery',
27
29
  gallery_thumbnails: 'gallery thumbnails',
28
30
  map: 'map',
@@ -62,6 +64,8 @@ module.exports = {
62
64
  filter_values: 'Filter {content} values',
63
65
  clear_content_search: 'Clear {content} search',
64
66
  none_applied: 'No {content} applied',
67
+ applied_count: '{count} {content} applied',
68
+ toggle_content: 'Toggle {content}',
65
69
  scroll_direction_content: 'Scroll {direction} through {content}',
66
70
  step_content: '{direction} {content}',
67
71
  item_numbered: '{content} {index}',
@@ -74,7 +78,8 @@ module.exports = {
74
78
  next: 'Next'
75
79
  },
76
80
  misc: {
77
- referenced_by: 'Referenced by'
81
+ referenced_by: 'Referenced by',
82
+ on_this_page: 'On this page'
78
83
  }
79
84
  }
80
85
  };
@@ -0,0 +1,69 @@
1
+ const DEFAULT_LOCALE_MESSAGES = require('./default-locale');
2
+
3
+ function getGlobalScope() {
4
+ if (typeof globalThis !== 'undefined') return globalThis;
5
+ if (typeof window !== 'undefined') return window;
6
+ if (typeof global !== 'undefined') return global;
7
+ return null;
8
+ }
9
+
10
+ function accessPath(obj, path) {
11
+ if (!obj || typeof obj !== 'object' || !path) return undefined;
12
+ const parts = Array.isArray(path) ? path : String(path).split('.');
13
+ let current = obj;
14
+ for (const part of parts) {
15
+ if (!current || typeof current !== 'object') return undefined;
16
+ const key = String(part).trim();
17
+ if (!key || !(key in current)) return undefined;
18
+ current = current[key];
19
+ }
20
+ return current;
21
+ }
22
+
23
+ function formatTemplate(template, replacements = {}) {
24
+ if (template == null) return template;
25
+ const str = String(template);
26
+ if (!replacements || typeof replacements !== 'object') return str;
27
+ return str.replace(/\{([^}]+)\}/g, (match, key) => {
28
+ const replacement = Object.prototype.hasOwnProperty.call(replacements, key)
29
+ ? replacements[key]
30
+ : undefined;
31
+ if (replacement == null) return match;
32
+ return String(replacement);
33
+ });
34
+ }
35
+
36
+ function getRuntimeLocaleMessages() {
37
+ const scope = getGlobalScope();
38
+ const candidate = scope && scope.CANOPY_LOCALE_MESSAGES;
39
+ if (candidate && typeof candidate === 'object') return candidate;
40
+ return DEFAULT_LOCALE_MESSAGES || {};
41
+ }
42
+
43
+ function getRuntimeLocaleCode() {
44
+ const scope = getGlobalScope();
45
+ const value = scope && scope.CANOPY_LOCALE_CODE;
46
+ return typeof value === 'string' ? value : '';
47
+ }
48
+
49
+ function getRuntimeMessage(path, fallback) {
50
+ const messages = getRuntimeLocaleMessages();
51
+ const value = accessPath(messages, path);
52
+ if (value == null) return fallback;
53
+ return value;
54
+ }
55
+
56
+ function formatRuntimeMessage(path, fallback, replacements) {
57
+ const template = getRuntimeMessage(path, fallback);
58
+ if (template == null) return template;
59
+ return formatTemplate(template, replacements);
60
+ }
61
+
62
+ module.exports = {
63
+ getRuntimeLocaleMessages,
64
+ getRuntimeLocaleCode,
65
+ getRuntimeMessage,
66
+ formatRuntimeMessage,
67
+ formatTemplate,
68
+ accessPath,
69
+ };
package/lib/locales.js CHANGED
@@ -111,6 +111,34 @@ function readLocaleMessages(lang) {
111
111
  return deepMerge(defaultMessages, override);
112
112
  }
113
113
 
114
+ function serializeForScript(value) {
115
+ try {
116
+ return JSON.stringify(value || {}).replace(/</g, '\\u003c');
117
+ } catch (_) {
118
+ return '{}';
119
+ }
120
+ }
121
+
122
+ function buildLocaleRuntimeScript(localeCode) {
123
+ let messages = null;
124
+ try {
125
+ messages = readLocaleMessages(localeCode);
126
+ } catch (_) {
127
+ messages = null;
128
+ }
129
+ if (!messages) {
130
+ try {
131
+ messages = readLocaleMessages(null);
132
+ } catch (_) {
133
+ messages = DEFAULT_LOCALE_MESSAGES;
134
+ }
135
+ }
136
+ const langValue = typeof localeCode === 'string' ? localeCode : '';
137
+ const serializedMessages = serializeForScript(messages || {});
138
+ const serializedCode = JSON.stringify(langValue || '').replace(/</g, '\\u003c');
139
+ return `<script>window.CANOPY_LOCALE_CODE=${serializedCode};window.CANOPY_LOCALE_MESSAGES=${serializedMessages};</script>`;
140
+ }
141
+
114
142
  function normalizeRouteValue(value) {
115
143
  if (typeof value !== 'string') return '';
116
144
  const trimmed = value.trim();
@@ -170,5 +198,6 @@ module.exports = {
170
198
  readCanopyLocalesWithMessages,
171
199
  readLocaleMessages,
172
200
  readLocaleRoutes,
201
+ buildLocaleRuntimeScript,
173
202
  DEFAULT_LOCALE_ROUTES,
174
203
  };
@@ -6,7 +6,9 @@ import {
6
6
  SearchFiltersDialog,
7
7
  } from "@canopy-iiif/app/ui";
8
8
  import resultTemplates from "__CANOPY_SEARCH_RESULT_TEMPLATES__";
9
+ import localeRuntime from "../locale-runtime.js";
9
10
  const SITE_TITLE_FALLBACK = "Site title";
11
+ const {getRuntimeMessage, formatRuntimeMessage} = localeRuntime;
10
12
 
11
13
  function readSiteTitleFromGlobals() {
12
14
  try {
@@ -139,6 +141,16 @@ function resolveRecordHrefForLocale(record, fallbackHref) {
139
141
  return first || fallbackHref;
140
142
  }
141
143
 
144
+ function resolveTypeLabel(type) {
145
+ const key = String(type || "").toLowerCase();
146
+ if (key === "work") return getRuntimeMessage("common.types.work", "Works");
147
+ if (key === "page") return getRuntimeMessage("common.types.page", "Pages");
148
+ if (key === "docs") return getRuntimeMessage("common.types.docs", "Docs");
149
+ if (key === "all") return getRuntimeMessage("common.nouns.types", "types");
150
+ if (key === "annotation") return getRuntimeMessage("common.nouns.items", "items");
151
+ return key ? key.charAt(0).toUpperCase() + key.slice(1) : "";
152
+ }
153
+
142
154
  // Lightweight IndexedDB utilities (no deps) with defensive guards
143
155
  function hasIDB() {
144
156
  try {
@@ -899,7 +911,12 @@ function useStore() {
899
911
 
900
912
  function ResultsMount(props = {}) {
901
913
  const {results, type, loading, query, resultSettings} = useStore();
902
- if (loading) return <div className="text-slate-600">Loading…</div>;
914
+ if (loading)
915
+ return (
916
+ <div className="text-slate-600">
917
+ {formatRuntimeMessage("common.statuses.loading", "Loading…")}
918
+ </div>
919
+ );
903
920
  const normalizedType = String(type || "").trim().toLowerCase();
904
921
  const hasOverrides =
905
922
  resultSettings && Object.keys(resultSettings || {}).length > 0;
@@ -977,10 +994,20 @@ function TabsMount() {
977
994
  function SummaryMount() {
978
995
  const {query, type, shown, total} = useStore();
979
996
  const text = useMemo(() => {
980
- if (!query) return `Showing ${shown} of ${total} items`;
981
- return `Found ${shown} of ${total} in ${
982
- type === "all" ? "all types" : type
983
- } for "${query}"`;
997
+ const itemsLabel = getRuntimeMessage("common.nouns.items", "items");
998
+ if (!query) {
999
+ return formatRuntimeMessage(
1000
+ "common.statuses.summary_content",
1001
+ "Showing {shown} of {total} {content}",
1002
+ {shown, total, content: itemsLabel},
1003
+ );
1004
+ }
1005
+ const typeLabel = resolveTypeLabel(type);
1006
+ return formatRuntimeMessage(
1007
+ "common.statuses.search_summary",
1008
+ 'Found {shown} of {total} in {type} for "{query}"',
1009
+ {shown, total, type: typeLabel, query},
1010
+ );
984
1011
  }, [query, type, shown, total]);
985
1012
  return <div className="text-sm text-slate-600">{text}</div>;
986
1013
  }
@@ -18,6 +18,7 @@ const {
18
18
  getDefaultLocaleCode,
19
19
  getSiteTitle,
20
20
  } = require('../common');
21
+ const {buildLocaleRuntimeScript} = require('../locales');
21
22
  const { resolveCanopyConfigPath } = require('../config-path');
22
23
 
23
24
  const SEARCH_TEMPLATES_ALIAS = '__CANOPY_SEARCH_RESULT_TEMPLATES__';
@@ -368,6 +369,10 @@ async function buildSearchPageForEntry(routeEntry) {
368
369
  }
369
370
  }
370
371
  let headExtra = vendorTags + head + importMap + customRuntimeTag;
372
+ try {
373
+ const localeScript = buildLocaleRuntimeScript(pageLocale);
374
+ if (localeScript) headExtra = localeScript + headExtra;
375
+ } catch (_) {}
371
376
  if (siteTitle && typeof siteTitle === 'string') {
372
377
  const siteTitleScript = `<script>window.CANOPY_SITE_TITLE=${JSON.stringify(siteTitle)}</script>`;
373
378
  headExtra = siteTitleScript + headExtra;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "1.9.13",
3
+ "version": "1.9.15",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",