@devslab/site-kit 0.3.0 → 0.5.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/src/core/seo.mjs CHANGED
@@ -1,4 +1,10 @@
1
- import { LOCALES, canonicalLocale, localeAttributes } from "./locales.mjs";
1
+ import { FAMILY_LOCALES } from "./locales.mjs";
2
+
3
+ // Every function here that names a locale takes an optional `registry`.
4
+ // Omitted it is the family's fourteen, which is what a family site wants.
5
+ // A product that added its own languages must pass its registry, or the
6
+ // hreflang alternates and the sitemap would quietly omit them — the page
7
+ // would render in Tamil while telling search engines Tamil does not exist.
2
8
 
3
9
  const cleanBase = (baseUrl) => String(baseUrl).replace(/\/+$/, "");
4
10
  const cleanPath = (path) => {
@@ -6,27 +12,27 @@ const cleanPath = (path) => {
6
12
  return value === "/" ? "/" : value;
7
13
  };
8
14
 
9
- export function localizedPath(path, locale, defaultLocale) {
10
- const canonical = canonicalLocale(locale);
11
- const fallback = canonicalLocale(defaultLocale);
15
+ export function localizedPath(path, locale, defaultLocale, registry = FAMILY_LOCALES) {
16
+ const canonical = registry.canonicalLocale(locale);
17
+ const fallback = registry.canonicalLocale(defaultLocale);
12
18
  if (!canonical || !fallback) throw new RangeError("localizedPath received an unsupported locale");
13
19
  const normalized = cleanPath(path);
14
20
  return canonical === fallback ? normalized : `/${canonical}${normalized === "/" ? "" : normalized}`;
15
21
  }
16
22
 
17
- export function localizedUrl(baseUrl, path, locale, defaultLocale) {
18
- return `${cleanBase(baseUrl)}${localizedPath(path, locale, defaultLocale)}`;
23
+ export function localizedUrl(baseUrl, path, locale, defaultLocale, registry = FAMILY_LOCALES) {
24
+ return `${cleanBase(baseUrl)}${localizedPath(path, locale, defaultLocale, registry)}`;
19
25
  }
20
26
 
21
- export function buildMetadata({ baseUrl, path, locale, defaultLocale, title, description, siteName, image }) {
22
- const canonicalLocaleCode = canonicalLocale(locale);
27
+ export function buildMetadata({ baseUrl, path, locale, defaultLocale, title, description, siteName, image, registry = FAMILY_LOCALES }) {
28
+ const canonicalLocaleCode = registry.canonicalLocale(locale);
23
29
  if (!canonicalLocaleCode) throw new RangeError(`Unsupported locale: ${locale}`);
24
- const canonical = localizedUrl(baseUrl, path, canonicalLocaleCode, defaultLocale);
25
- const alternates = LOCALES.map(({ code }) => ({ hreflang: code, href: localizedUrl(baseUrl, path, code, defaultLocale) }));
26
- alternates.push({ hreflang: "x-default", href: localizedUrl(baseUrl, path, defaultLocale, defaultLocale) });
30
+ const canonical = localizedUrl(baseUrl, path, canonicalLocaleCode, defaultLocale, registry);
31
+ const alternates = registry.LOCALES.map(({ code }) => ({ hreflang: code, href: localizedUrl(baseUrl, path, code, defaultLocale, registry) }));
32
+ alternates.push({ hreflang: "x-default", href: localizedUrl(baseUrl, path, defaultLocale, defaultLocale, registry) });
27
33
  const imageUrl = new URL(image, `${cleanBase(baseUrl)}/`).href;
28
34
  return {
29
- html: localeAttributes(canonicalLocaleCode),
35
+ html: registry.localeAttributes(canonicalLocaleCode),
30
36
  title,
31
37
  description,
32
38
  canonical,
@@ -44,13 +50,13 @@ export function buildMetadata({ baseUrl, path, locale, defaultLocale, title, des
44
50
  };
45
51
  }
46
52
 
47
- export function buildSitemap({ baseUrl, routes, defaultLocale, lastModified }) {
48
- return routes.flatMap((path) => LOCALES.map(({ code }) => ({
49
- loc: localizedUrl(baseUrl, path, code, defaultLocale),
53
+ export function buildSitemap({ baseUrl, routes, defaultLocale, lastModified, registry = FAMILY_LOCALES }) {
54
+ return routes.flatMap((path) => registry.LOCALES.map(({ code }) => ({
55
+ loc: localizedUrl(baseUrl, path, code, defaultLocale, registry),
50
56
  locale: code,
51
57
  alternates: [
52
- ...LOCALES.map(({ code: alternate }) => ({ hreflang: alternate, href: localizedUrl(baseUrl, path, alternate, defaultLocale) })),
53
- { hreflang: "x-default", href: localizedUrl(baseUrl, path, defaultLocale, defaultLocale) },
58
+ ...registry.LOCALES.map(({ code: alternate }) => ({ hreflang: alternate, href: localizedUrl(baseUrl, path, alternate, defaultLocale, registry) })),
59
+ { hreflang: "x-default", href: localizedUrl(baseUrl, path, defaultLocale, defaultLocale, registry) },
54
60
  ],
55
61
  ...(lastModified ? { lastmod: lastModified } : {}),
56
62
  })));
package/styles.css CHANGED
@@ -42,3 +42,17 @@
42
42
 
43
43
  @media (prefers-reduced-motion: reduce) { .site-header { scroll-behavior: auto; } }
44
44
  @media (forced-colors: active) { .site-header, .site-footer, .site-status { forced-color-adjust: auto; } }
45
+
46
+ .site-locale-flag { position: relative; }
47
+ .site-locale-flag__trigger { list-style: none; display: inline-grid; place-items: center; min-inline-size: 44px; min-block-size: 44px; border-radius: var(--dds-radius-sm); cursor: pointer; }
48
+ .site-locale-flag__trigger::-webkit-details-marker { display: none; }
49
+ .site-locale-flag__trigger:hover { background: var(--dds-color-bg-subtle); }
50
+ .site-locale-flag__trigger:focus-visible { outline: 2px solid var(--dds-color-border-focus); outline-offset: 2px; }
51
+ .site-locale-flag__svg { inline-size: 24px; block-size: 18px; border-radius: 2px; box-shadow: 0 0 0 1px var(--dds-color-border-default); }
52
+ /* z-index is local to .site-header's own stacking context (position: sticky, z-index: 100), not the document root. */
53
+ .site-locale-flag__list { position: absolute; inset-block-start: calc(100% + var(--dds-space-4)); inset-inline-end: 0; z-index: 200; margin: 0; padding: var(--dds-space-4); list-style: none; min-inline-size: 14rem; max-block-size: min(60dvh, 28rem); overflow-y: auto; background: var(--dds-color-bg-default); border: 1px solid var(--dds-color-border-default); border-radius: var(--dds-radius-md); box-shadow: var(--dds-elevation-2); }
54
+ .site-locale-flag__option { display: flex; align-items: center; gap: var(--dds-space-12); min-block-size: 44px; padding-inline: var(--dds-space-12); border-radius: var(--dds-radius-sm); color: var(--dds-color-text-primary); text-decoration: none; }
55
+ .site-locale-flag__option:hover { background: var(--dds-color-bg-subtle); }
56
+ .site-locale-flag__option:focus-visible { outline: 2px solid var(--dds-color-border-focus); outline-offset: -2px; }
57
+ .site-locale-flag__option[aria-current="true"] { font-weight: 700; }
58
+ @media (max-width: 720px) { .site-locale-flag__list { position: static; inline-size: 100%; max-block-size: none; box-shadow: none; } }