@apleasantview/eleventy-plugin-baseline 0.1.0-next.41 → 0.1.0-next.42

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.
Files changed (39) hide show
  1. package/README.md +19 -19
  2. package/core/content-graph/extractors.js +63 -18
  3. package/core/content-graph/graph.js +5 -2
  4. package/core/dates/git-date.js +71 -0
  5. package/core/dates/index.js +55 -0
  6. package/core/locale/derive-lang.js +19 -0
  7. package/core/locale/index.js +6 -0
  8. package/core/locale/normalize-lang.js +13 -0
  9. package/core/locale/normalize-locale.js +20 -0
  10. package/core/locale/open-graph-locale.js +14 -0
  11. package/core/locale/resolve-default.js +27 -0
  12. package/core/locale/resolve-locale.js +16 -0
  13. package/core/markdown/wikilinks.js +1 -1
  14. package/core/page-context/build.js +120 -23
  15. package/core/schema.js +3 -1
  16. package/core/seo-graph/adapter.js +246 -0
  17. package/core/seo-graph/build.js +87 -0
  18. package/core/seo-graph/index.js +1 -0
  19. package/core/seo-graph/open-graph.js +130 -0
  20. package/core/seo-graph/register.js +42 -0
  21. package/core/seo-graph/schema.js +18 -0
  22. package/core/state.js +3 -1
  23. package/core/surface/index.js +1 -1
  24. package/core/types.js +3 -0
  25. package/core/utils/{normalize-languages.js → normalize-language-map.js} +14 -5
  26. package/core/utils/title-case-slug.js +15 -0
  27. package/index.js +15 -9
  28. package/modules/head/drivers/posthtml-head-elements.js +92 -10
  29. package/modules/head/index.js +16 -9
  30. package/modules/head/schema.js +7 -3
  31. package/modules/multilang/filters/i18n-default-translation.js +2 -4
  32. package/modules/multilang/filters/i18n-translation-in.js +2 -2
  33. package/modules/multilang/filters/i18n-translations-for.js +2 -2
  34. package/modules/multilang/index.js +78 -39
  35. package/modules/navigator/index.js +6 -5
  36. package/modules/sitemap/index.js +4 -4
  37. package/modules/sitemap/templates/sitemap-core.html +1 -1
  38. package/package.json +2 -1
  39. /package/core/{surface/global-date-function.js → dates/date-global.js} +0 -0
@@ -0,0 +1,42 @@
1
+ import { createLogger } from '../logging/index.js';
2
+ import { getScope, memoize } from '../registry.js';
3
+ import { createSeoNamespace } from './build.js';
4
+
5
+ const SCOPE_NAME = 'core:seo-graph';
6
+ const LOG_NAME = 'seo-graph';
7
+ const COMPUTED_KEY = 'eleventyComputed._seoGraph';
8
+
9
+ /**
10
+ * @param {import("@11ty/eleventy").UserConfig} eleventyConfig
11
+ * @param {Object} coreContext
12
+ */
13
+ export function registerSeoGraph(eleventyConfig, coreContext) {
14
+ const { state, runtime } = coreContext;
15
+ const { settings, options } = state;
16
+
17
+ const log = createLogger(LOG_NAME, { verbose: options.verbose });
18
+ const scope = getScope(eleventyConfig, SCOPE_NAME);
19
+
20
+ const buildSeoNamespace = createSeoNamespace({ scope, settings, runtime, options, log });
21
+
22
+ function shouldSkip(data) {
23
+ if (data._internal) return true;
24
+ if (data.page?.outputFileExtension !== 'html') return true;
25
+ return false;
26
+ }
27
+
28
+ eleventyConfig.addGlobalData(COMPUTED_KEY, () => {
29
+ return (data) => {
30
+ if (shouldSkip(data)) return data._seoGraph ?? null;
31
+ return memoize(scope, data, buildSeoNamespace);
32
+ };
33
+ });
34
+
35
+ log.info('SEO graph registered');
36
+
37
+ return {
38
+ get: (data) => scope.cache.get(data),
39
+ getByKey: (key) => scope.values.get(key),
40
+ snapshot: () => Object.fromEntries(scope.values)
41
+ };
42
+ }
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+
3
+ // Structural shape of the resolved `seo` namespace. Permissive on values;
4
+ // strict only where a missing/wrong shape would break a downstream consumer.
5
+ export const seoSchema = z
6
+ .object({
7
+ title: z.string().optional(),
8
+ description: z.string().optional(),
9
+ url: z.string().optional(),
10
+ ogImage: z.unknown().optional(),
11
+ locale: z.string().optional(),
12
+ openGraph: z.record(z.unknown()).optional(),
13
+ twitter: z.record(z.unknown()).optional(),
14
+ // The assembled JSON-LD @graph. Authored identity lives at
15
+ // `data.schema`; the resolved graph lives here. No `seo.schema` path.
16
+ graph: z.array(z.unknown()).optional()
17
+ })
18
+ .passthrough();
package/core/state.js CHANGED
@@ -39,8 +39,10 @@ export function deriveBaselineState(settings, options, { mode } = {}) {
39
39
  url: settings.url,
40
40
  noindex: settings.noindex ?? false,
41
41
  defaultLanguage: settings.defaultLanguage,
42
+ defaultLocale: settings.defaultLocale,
42
43
  languages: settings.languages,
43
- head: settings.head
44
+ head: settings.head,
45
+ seo: settings.seo
44
46
  };
45
47
 
46
48
  const resolvedOptions = {
@@ -5,7 +5,7 @@
5
5
  * user templates can reach: filters, global functions, shortcodes.
6
6
  */
7
7
 
8
- import { registerDateGlobal } from './global-date-function.js';
8
+ import { registerDateGlobal } from '../dates/index.js';
9
9
 
10
10
  // --- Filters ---
11
11
  export { markdownFilter } from '../markdown/markdownify.js';
package/core/types.js CHANGED
@@ -14,6 +14,9 @@
14
14
  * @property {string} [url]
15
15
  * @property {boolean} [noindex]
16
16
  * @property {string} [defaultLanguage]
17
+ * Short language code; a writer-side alias for defaultLocale.
18
+ * @property {string} [defaultLocale]
19
+ * BCP 47 default locale; preferred when both are set.
17
20
  * @property {Record<string, unknown>} [languages]
18
21
  * @property {Object} [head]
19
22
  */
@@ -1,13 +1,20 @@
1
1
  /**
2
- * Normalize language input to an object map.
3
- * Accepts an array of language codes or an object keyed by language code.
4
- * Returns undefined if input is invalid or empty.
2
+ * Normalize the `settings.languages` config into an object map with
3
+ * lowercased, trimmed keys.
4
+ *
5
+ * Accepts an array of language codes or an object keyed by language code;
6
+ * either form ends up with `[normalizedKey]: entry`. Returns undefined if
7
+ * the input is invalid or empty.
8
+ *
9
+ * Lives in `core/utils/` (not `core/locale/`) because the array-vs-object
10
+ * shape coercion is config-shape adapting, not locale handling. The
11
+ * lowercasing of keys is the only locale-shaped part.
5
12
  *
6
13
  * @param {Object} settings - Options object containing languages.
7
14
  * @param {import('../logging/index.js').BaselineLogger} [logger] - Logger for dropped-entry notice.
8
15
  * @returns {Record<string, Object>|undefined} Normalized language map, or undefined.
9
16
  */
10
- export function normalizeLanguages(settings, logger) {
17
+ export function normalizeLanguageMap(settings, logger) {
11
18
  const normalizedLanguages = Array.isArray(settings.languages)
12
19
  ? Object.fromEntries(
13
20
  settings.languages
@@ -15,7 +22,9 @@ export function normalizeLanguages(settings, logger) {
15
22
  .map((lang) => [lang.toLowerCase().trim(), {}])
16
23
  )
17
24
  : settings.languages && typeof settings.languages === 'object'
18
- ? settings.languages
25
+ ? Object.fromEntries(
26
+ Object.entries(settings.languages).map(([k, v]) => [k.toLowerCase().trim(), v])
27
+ )
19
28
  : undefined;
20
29
 
21
30
  if (logger && Array.isArray(settings.languages)) {
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Title-case a URL slug for display: "core-reference" → "Core Reference".
3
+ * The rough inverse of {@link slugify}: splits on hyphens/underscores and
4
+ * capitalises each word.
5
+ *
6
+ * @param {string} slug
7
+ * @returns {string}
8
+ */
9
+ export function titleCaseSlug(slug) {
10
+ return String(slug)
11
+ .split(/[-_]/)
12
+ .filter(Boolean)
13
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
14
+ .join(' ');
15
+ }
package/index.js CHANGED
@@ -15,6 +15,7 @@ import { createContentMapStore } from './core/content-map-store.js';
15
15
  import { createTranslationMapStore } from './core/translation-map-store.js';
16
16
  import { createSlugIndex } from './core/slug-index.js';
17
17
  import { registerPageContext } from './core/page-context/index.js';
18
+ import { registerSeoGraph } from './core/seo-graph/index.js';
18
19
  import { autoHeadingIds, safeUse, wikilinks } from './core/markdown/index.js';
19
20
  import { slugify } from './core/utils/slugify.js';
20
21
  import { assetsCore, headCore, multilangCore, navigatorCore, sitemapCore } from './modules.js';
@@ -50,9 +51,10 @@ const INTERNAL_KEYS = [
50
51
  '_snapshot',
51
52
  'eleventyComputed._pageContext',
52
53
  'eleventyComputed._node',
53
- 'eleventyComputed._edges',
54
+ 'eleventyComputed._seoGraph',
54
55
  'eleventyComputed._backlinks',
55
- 'eleventyComputed._outgoing'
56
+ 'eleventyComputed._outgoing',
57
+ 'eleventyComputed._edges'
56
58
  ];
57
59
 
58
60
  // Base logger outputs regardless of options.
@@ -174,13 +176,14 @@ export default function baseline(settings = {}, options = {}) {
174
176
  }
175
177
 
176
178
  INTERNAL_KEYS.forEach((key) => {
177
- // We leave eleventyComputed callback kEys alone, the rest are reserved-empty.
179
+ // We leave eleventyComputed callback keys alone, the rest are reserved-empty.
178
180
  if (
179
181
  key === 'eleventyComputed._pageContext' ||
180
182
  key === 'eleventyComputed._node' ||
181
- key === 'eleventyComputed._edges' ||
183
+ key === 'eleventyComputed._seoGraph' ||
182
184
  key === 'eleventyComputed._backlinks' ||
183
- key === 'eleventyComputed._outgoing'
185
+ key === 'eleventyComputed._outgoing' ||
186
+ key === 'eleventyComputed._edges'
184
187
  )
185
188
  return;
186
189
  eleventyConfig.addGlobalData(key, {});
@@ -286,8 +289,9 @@ export default function baseline(settings = {}, options = {}) {
286
289
  helpers
287
290
  };
288
291
 
289
- // Page context registry
292
+ // Page context and SEO graph registries
290
293
  const pageContextRegistry = registerPageContext(eleventyConfig, coreContext);
294
+ const seoGraphRegistry = registerSeoGraph(eleventyConfig, coreContext);
291
295
 
292
296
  // --- Content graph ---
293
297
  // Cascade hookup for the content graph. Reads via the runtime getter so
@@ -338,7 +342,8 @@ export default function baseline(settings = {}, options = {}) {
338
342
  // --- Snapshots ---
339
343
  coreContext.snapshots = {
340
344
  contentMap: () => contentMapStore.snapshot(),
341
- pageContext: () => pageContextRegistry.snapshot()
345
+ pageContext: () => pageContextRegistry.snapshot(),
346
+ seoGraph: () => seoGraphRegistry.snapshot()
342
347
  };
343
348
 
344
349
  // --- Module registry ---
@@ -346,7 +351,7 @@ export default function baseline(settings = {}, options = {}) {
346
351
  { when: state.features.multilang, name: 'multilang', plugin: multilangCore },
347
352
  { when: state.features.sitemap, name: 'sitemap', plugin: sitemapCore },
348
353
  { name: 'navigator', plugin: navigatorCore },
349
- { when: state.features.head, name: 'head', plugin: headCore, consumes: { pageContext: true } },
354
+ { when: state.features.head, name: 'head', plugin: headCore, consumes: { pageContext: true, seoGraph: true } },
350
355
  { when: state.features.assets, name: 'assets', plugin: assetsCore }
351
356
  ];
352
357
 
@@ -357,7 +362,8 @@ export default function baseline(settings = {}, options = {}) {
357
362
  const moduleContext = {
358
363
  ...coreContext,
359
364
  log: scopedLog(name),
360
- resolvePageContext: consumes.pageContext ? pageContextRegistry : null
365
+ resolvePageContext: consumes.pageContext ? pageContextRegistry : null,
366
+ resolveSeoGraph: consumes.seoGraph ? seoGraphRegistry : null
361
367
  };
362
368
 
363
369
  eleventyConfig.addPlugin(plugin, moduleContext);
@@ -27,28 +27,41 @@ import { dedupeMeta, dedupeLink } from '../utils/dedupe.js';
27
27
  *
28
28
  * Scope:
29
29
  * Owns node emission, dedupe orchestration, capo sort, and placeholder
30
- * replacement.
31
- * Does not own seed shape (page context), hreflang building
32
- * (head/utils/alternates.js), or capo's element weights (capo.js).
30
+ * replacement. Owns the og:/twitter: vocabulary: the seo substrate hands
31
+ * over short structured keys, this driver maps them to <meta>/<script>.
32
+ * Does not own seed shape (page context), the seo projection
33
+ * (core/seo-graph), hreflang building (head/utils/alternates.js), or capo's
34
+ * element weights (capo.js).
33
35
  *
34
36
  * Data flow:
35
- * seeds + alternates + options → emit → dedupe → capo-sort → PostHTML
37
+ * seeds + seo + alternates + options → emit → dedupe → capo-sort → PostHTML
36
38
  * tree mutation
37
39
  *
38
40
  * @param {Object} args
39
41
  * @param {Object} args.seeds - Page context for the current page.
42
+ * @param {Object} [args.seo] - Resolved _seoGraph namespace (url, schema, openGraph, twitter).
40
43
  * @param {Array<Object>} args.alternates - hreflang link descriptors.
41
44
  * @param {Object} args.options - Head options (titleSeparator, showGenerator).
42
45
  * @param {string} args.placeholderTag - Placeholder element to replace.
43
46
  * @param {string} args.eol - End-of-line separator interleaved between nodes.
44
47
  * @returns {(tree: Object) => Object} PostHTML plugin function.
45
48
  */
46
- export function renderHead({ seeds, alternates, options, placeholderTag, eol }) {
47
- const defaults = emitMeta(seeds.meta, seeds.render, options);
49
+ export function renderHead({ seeds, seo, alternates, options, placeholderTag, eol }) {
50
+ // seo.url is the canonical source, resolved at cascade-time. It is undefined
51
+ // on noindex / no-settings.url pages, where the seo layer deliberately drops
52
+ // the canonical — emitMeta's guard then emits nothing, which is correct. The
53
+ // seo handle is present whenever a head renders (same skip set as page
54
+ // context), so there is no absent-handle case to fall back for.
55
+ const canonical = seo?.url;
56
+ const defaults = emitMeta(seeds.meta, seeds.render, options, canonical);
48
57
  const extras = emitExtras(seeds.head, alternates);
58
+ const { meta: seoMeta, multi: seoMulti, scripts: seoScripts } = emitSeo(seo);
49
59
 
50
- const deduped = dedupeAll([...defaults, ...extras]);
51
- const sorted = capoSort(deduped);
60
+ // seo emits last so the substrate wins a property collision with settings.head.
61
+ // Multi-valued tags and JSON-LD bypass dedupe (property-keyed last-wins would
62
+ // collapse repeated og:locale:alternate / article:author to one).
63
+ const deduped = dedupeAll([...defaults, ...extras, ...seoMeta]);
64
+ const sorted = capoSort([...deduped, ...seoMulti, ...seoScripts]);
52
65
 
53
66
  return function rendererPlugin(tree) {
54
67
  tree.match({ tag: placeholderTag }, () => ({
@@ -59,14 +72,14 @@ export function renderHead({ seeds, alternates, options, placeholderTag, eol })
59
72
  };
60
73
  }
61
74
 
62
- function emitMeta(meta, render, options) {
75
+ function emitMeta(meta, render, options, canonical) {
63
76
  const nodes = [];
64
77
  nodes.push(mkMeta({ charset: 'UTF-8' }));
65
78
  nodes.push(mkMeta({ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }));
66
79
  if (meta.title) nodes.push({ tag: 'title', content: [meta.title] });
67
80
  if (meta.description) nodes.push(mkMeta({ name: 'description', content: meta.description }));
68
81
  nodes.push(mkMeta({ name: 'robots', content: meta.robots }));
69
- if (meta.canonical) nodes.push(mkLink({ rel: 'canonical', href: meta.canonical }));
82
+ if (canonical) nodes.push(mkLink({ rel: 'canonical', href: canonical }));
70
83
  if (options.showGenerator && render.generator) {
71
84
  nodes.push(mkMeta({ name: 'generator', content: render.generator }));
72
85
  }
@@ -74,6 +87,75 @@ function emitMeta(meta, render, options) {
74
87
  return nodes;
75
88
  }
76
89
 
90
+ /**
91
+ * Map the resolved seo projection to head nodes. The substrate hands over short
92
+ * structured keys; this driver owns the og:/twitter: vocabulary. Single-valued
93
+ * tags ride the deduped meta flow; multi-valued ones (og:locale:alternate,
94
+ * repeated article:author / article:tag) go in `multi` to skip the property-keyed dedupe.
95
+ * The JSON-LD graph rides `scripts` (the adapter returns a bare @graph array,
96
+ * wrapped in its @context envelope here).
97
+ *
98
+ * @param {Object} [seo] - Resolved seo namespace (graph, openGraph, twitter).
99
+ * @returns {{ meta: Array<Object>, multi: Array<Object>, scripts: Array<Object> }}
100
+ */
101
+ function emitSeo(seo) {
102
+ const meta = [];
103
+ const multi = [];
104
+ const scripts = [];
105
+ if (!seo) return { meta, multi, scripts };
106
+
107
+ const og = seo.openGraph ?? {};
108
+ const tw = seo.twitter ?? {};
109
+
110
+ const prop = (property, content) => {
111
+ if (content !== undefined && content !== null && content !== '') meta.push(mkMeta({ property, content }));
112
+ };
113
+ const name = (key, content) => {
114
+ if (content !== undefined && content !== null && content !== '') meta.push(mkMeta({ name: key, content }));
115
+ };
116
+
117
+ prop('og:title', og.title);
118
+ prop('og:type', og.type);
119
+ prop('og:description', og.description);
120
+ prop('og:url', og.url);
121
+ prop('og:site_name', og.siteName);
122
+ prop('og:locale', og.locale);
123
+ prop('og:image', og.image);
124
+ prop('og:image:alt', og.imageAlt);
125
+ if (og.imageWidth !== undefined) prop('og:image:width', String(og.imageWidth));
126
+ if (og.imageHeight !== undefined) prop('og:image:height', String(og.imageHeight));
127
+
128
+ for (const loc of asArray(og.localeAlternate)) {
129
+ if (loc) multi.push(mkMeta({ property: 'og:locale:alternate', content: loc }));
130
+ }
131
+
132
+ if (og.article) {
133
+ prop('article:published_time', og.article.publishedTime);
134
+ prop('article:modified_time', og.article.modifiedTime);
135
+ prop('article:section', og.article.section);
136
+ for (const author of asArray(og.article.authors)) {
137
+ if (author) multi.push(mkMeta({ property: 'article:author', content: author }));
138
+ }
139
+ for (const tag of asArray(og.article.tags)) {
140
+ if (tag) multi.push(mkMeta({ property: 'article:tag', content: tag }));
141
+ }
142
+ }
143
+
144
+ name('twitter:card', tw.card);
145
+ name('twitter:site', tw.site);
146
+ name('twitter:creator', tw.creator);
147
+ name('twitter:title', tw.title);
148
+ name('twitter:description', tw.description);
149
+ name('twitter:image', tw.image);
150
+
151
+ if (seo.schema?.length) {
152
+ const content = JSON.stringify({ '@context': 'https://schema.org', '@graph': seo.schema });
153
+ scripts.push(mkScript({ type: 'application/ld+json', content }));
154
+ }
155
+
156
+ return { meta, multi, scripts };
157
+ }
158
+
77
159
  function emitExtras(head, alternates = []) {
78
160
  const nodes = [];
79
161
  for (const m of asArray(head?.meta)) nodes.push(mkMeta(m));
@@ -33,15 +33,16 @@ const EOL = '\n';
33
33
  * every field the composer needs from cascade-time into transform-time.
34
34
  *
35
35
  * Scope:
36
- * Owns transform-time composition and placeholder replacement.
37
- * Pass 1 covers bucket 1 only: charset, viewport, title, description,
38
- * robots, canonical, optional generator, plus user extras from
39
- * settings.head and hreflang alternates. SEO and JSON-LD are later passes.
40
- * Does not own seed shape (page context) or driver internals.
36
+ * Owns transform-time composition and placeholder replacement. Emits
37
+ * charset, viewport, title, description, robots, canonical, optional
38
+ * generator, user extras from settings.head, hreflang alternates, plus the
39
+ * seo substrate's OG/Twitter projections and JSON-LD graph.
40
+ * Does not own seed shape (page context), the seo projection
41
+ * (core/seo-graph), or driver internals.
41
42
  *
42
43
  * Data flow:
43
- * page context + translation-map store + settings.head → driver →
44
- * PostHTML tree mutation (replaces <baseline-head>)
44
+ * page context + seo handle + translation-map store + settings.head →
45
+ * driver → PostHTML tree mutation (replaces <baseline-head>)
45
46
  *
46
47
  * @param {import("@11ty/eleventy").UserConfig} eleventyConfig
47
48
  * @param {Object} moduleContext
@@ -59,6 +60,7 @@ export function headCore(eleventyConfig, moduleContext) {
59
60
  }
60
61
 
61
62
  const pageContextRegistry = moduleContext.resolvePageContext;
63
+ const seoGraphRegistry = moduleContext.resolveSeoGraph;
62
64
 
63
65
  // Resolved plugin options with defaults.
64
66
  const headOptions = {
@@ -86,14 +88,19 @@ export function headCore(eleventyConfig, moduleContext) {
86
88
  return (tree) => tree;
87
89
  }
88
90
 
89
- const translationKey = seeds.page?.locale?.translationKey;
91
+ // Peer substrate, read by the same key as the page context above. Carries
92
+ // the resolved canonical, OG/Twitter projections, and the JSON-LD graph.
93
+ const seo = seoGraphRegistry?.getByKey(key);
94
+
95
+ const translationKey = seeds.page?.translationKey;
90
96
 
91
97
  const alternates = translationKey
92
- ? buildAlternates(seeds.page?.locale?.translationKey, runtime.translationMap.get(), seeds.site?.url)
98
+ ? buildAlternates(translationKey, runtime.translationMap.get(), seeds.site?.url)
93
99
  : [];
94
100
 
95
101
  return renderHead({
96
102
  seeds,
103
+ seo,
97
104
  alternates,
98
105
  options: headOptions,
99
106
  placeholderTag: PLACEHOLDER_TAG,
@@ -6,6 +6,7 @@ import * as z from 'zod';
6
6
  // `options.head` slice: render-behaviour knobs.
7
7
  export const optionsSchema = z.looseObject({
8
8
  titleSeparator: z.string().optional(),
9
+ titleTemplate: z.string().optional(),
9
10
  showGenerator: z.boolean().optional()
10
11
  });
11
12
 
@@ -17,10 +18,13 @@ export const settingsHeadSchema = z.looseObject({
17
18
  style: z.array(z.looseObject({})).optional()
18
19
  });
19
20
 
20
- // `settings.seo` site-default SEO scalars, page-overridable.
21
+ // `settings.seo` site-default SEO config: canonical policy, default share
22
+ // image, and OG/Twitter defaults. Structural only; values stay permissive.
21
23
  export const settingsSeoSchema = z.looseObject({
22
- ogImage: z.string().optional(),
23
- twitterSite: z.string().optional()
24
+ preserveQueryParams: z.boolean().optional(),
25
+ ogImage: z.unknown().optional(),
26
+ openGraph: z.looseObject({}).optional(),
27
+ twitter: z.looseObject({}).optional()
24
28
  });
25
29
 
26
30
  // Page-level `seo:` block. Same scalar set as bare front matter, namespaced.
@@ -5,10 +5,8 @@
5
5
  * @returns {object|null}
6
6
  */
7
7
  export default function i18nDefaultTranslation(page, collection) {
8
- if (!page?.locale?.translationKey) return null;
8
+ if (!page?.translationKey) return null;
9
9
  return (
10
- collection.find(
11
- (p) => p.locale && p.locale.translationKey === page.locale.translationKey && p.locale.isDefaultLang
12
- ) || null
10
+ collection.find((p) => p.translationKey === page.translationKey && p.isDefaultLang) || null
13
11
  );
14
12
  }
@@ -6,11 +6,11 @@
6
6
  * @returns {object|null}
7
7
  */
8
8
  export default function i18nTranslationIn(page, collection, lang) {
9
- if (!page?.locale?.translationKey) return null;
9
+ if (!page?.translationKey) return null;
10
10
 
11
11
  return (
12
12
  collection.find(
13
- (p) => p.locale && p.locale.translationKey === page.locale.translationKey && p.locale.lang === lang
13
+ (p) => p.translationKey === page.translationKey && p.lang === lang
14
14
  ) || null
15
15
  );
16
16
  }
@@ -5,6 +5,6 @@
5
5
  * @returns {Array<object>}
6
6
  */
7
7
  export default function i18nTranslationsFor(page, collection) {
8
- if (!page?.locale?.translationKey) return [];
9
- return collection.filter((p) => p.locale && p.locale.translationKey === page.locale.translationKey);
8
+ if (!page?.translationKey) return [];
9
+ return collection.filter((p) => p.translationKey === page.translationKey);
10
10
  }