lightnet 3.12.2 → 4.0.1

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 (111) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/exports/content.ts +7 -2
  3. package/exports/i18n.ts +0 -1
  4. package/exports/index.ts +1 -5
  5. package/exports/utils.ts +0 -1
  6. package/package.json +26 -12
  7. package/src/astro-integration/config.ts +60 -49
  8. package/src/astro-integration/integration.ts +13 -24
  9. package/src/astro-integration/tailwind.ts +86 -0
  10. package/src/astro-integration/validators/validate-inline-translations.ts +51 -0
  11. package/src/astro-integration/validators/validate-languages.ts +39 -0
  12. package/src/astro-integration/virtual.d.ts +8 -6
  13. package/src/astro-integration/vite-plugin-lightnet-config.ts +29 -9
  14. package/src/components/CarouselSection.astro +7 -11
  15. package/src/components/CategoriesSection.astro +2 -2
  16. package/src/components/HighlightSection.astro +4 -7
  17. package/src/components/Icon.tsx +2 -2
  18. package/src/components/MediaGallerySection.astro +88 -68
  19. package/src/components/MediaList.astro +9 -7
  20. package/src/components/SearchInput.astro +7 -4
  21. package/src/components/Section.astro +7 -5
  22. package/src/components/VideoPlayer.astro +2 -3
  23. package/src/content/content-schema.ts +129 -142
  24. package/src/content/get-categories.ts +52 -28
  25. package/src/content/get-languages.ts +29 -8
  26. package/src/content/get-media-collections.ts +43 -0
  27. package/src/content/get-media-types.ts +41 -7
  28. package/src/content/query-media-items.ts +23 -13
  29. package/src/i18n/bcp-47.ts +8 -0
  30. package/src/i18n/get-locale-paths.ts +1 -3
  31. package/src/i18n/locals.d.ts +21 -3
  32. package/src/i18n/locals.ts +18 -11
  33. package/src/i18n/resolve-current-locale.ts +18 -0
  34. package/src/i18n/resolve-language.ts +10 -5
  35. package/src/i18n/translate-map.ts +70 -0
  36. package/src/i18n/translate.ts +68 -47
  37. package/src/layouts/Page.astro +5 -3
  38. package/src/layouts/components/LanguagePicker.astro +22 -17
  39. package/src/layouts/components/Menu.astro +2 -5
  40. package/src/layouts/components/MenuItem.astro +1 -1
  41. package/src/layouts/components/PageNavigation.astro +29 -29
  42. package/src/layouts/components/PageTitle.astro +23 -7
  43. package/src/pages/404Route.astro +2 -1
  44. package/src/pages/RootRoute.astro +6 -1
  45. package/src/pages/details-page/DefaultDetailsPage.astro +9 -2
  46. package/src/pages/details-page/DetailsPageRoute.astro +1 -2
  47. package/src/pages/details-page/components/AudioPanel.astro +7 -3
  48. package/src/pages/details-page/components/AudioPlayer.astro +2 -2
  49. package/src/pages/details-page/components/ContentSection.astro +67 -44
  50. package/src/pages/details-page/components/MediaCollection.astro +8 -4
  51. package/src/pages/details-page/components/MediaCollectionsSection.astro +3 -6
  52. package/src/pages/details-page/components/main-details/EditButton.astro +22 -10
  53. package/src/pages/details-page/components/main-details/OpenButton.astro +17 -12
  54. package/src/pages/details-page/components/main-details/ShareButton.astro +3 -2
  55. package/src/pages/details-page/components/more-details/Categories.astro +5 -3
  56. package/src/pages/details-page/components/more-details/Languages.astro +12 -7
  57. package/src/pages/details-page/utils/create-content-metadata.ts +24 -9
  58. package/src/pages/details-page/utils/get-translations.ts +6 -0
  59. package/src/pages/search-page/components/LoadingSkeleton.tsx +6 -5
  60. package/src/pages/search-page/components/SearchFilter.astro +10 -21
  61. package/src/pages/search-page/components/SearchFilter.tsx +2 -2
  62. package/src/pages/search-page/components/SearchList.astro +10 -7
  63. package/src/pages/search-page/components/SearchListItem.tsx +5 -4
  64. package/src/pages/search-page/hooks/use-search.ts +5 -2
  65. package/src/utils/lazy.ts +20 -0
  66. package/src/utils/paths.ts +40 -3
  67. package/src/utils/urls.ts +1 -2
  68. package/src/utils/verify-schema.ts +12 -10
  69. package/tailwind.config.ts +1 -25
  70. package/__e2e__/admin.spec.ts +0 -20
  71. package/__e2e__/basics-fixture.ts +0 -77
  72. package/__e2e__/fixtures/basics/astro.config.mjs +0 -40
  73. package/__e2e__/fixtures/basics/node_modules/.bin/astro +0 -21
  74. package/__e2e__/fixtures/basics/node_modules/.bin/tailwind +0 -21
  75. package/__e2e__/fixtures/basics/node_modules/.bin/tailwindcss +0 -21
  76. package/__e2e__/fixtures/basics/node_modules/.bin/tsc +0 -21
  77. package/__e2e__/fixtures/basics/node_modules/.bin/tsserver +0 -21
  78. package/__e2e__/fixtures/basics/package.json +0 -21
  79. package/__e2e__/fixtures/basics/public/favicon.svg +0 -1
  80. package/__e2e__/fixtures/basics/public/files/example.pdf +0 -0
  81. package/__e2e__/fixtures/basics/src/assets/logo.png +0 -0
  82. package/__e2e__/fixtures/basics/src/content/categories/christian-living.json +0 -3
  83. package/__e2e__/fixtures/basics/src/content/categories/teens.json +0 -3
  84. package/__e2e__/fixtures/basics/src/content/categories/theology.json +0 -3
  85. package/__e2e__/fixtures/basics/src/content/media/faithful-freestyle--en.json +0 -13
  86. package/__e2e__/fixtures/basics/src/content/media/how-to-kickflip--de.json +0 -12
  87. package/__e2e__/fixtures/basics/src/content/media/images/cover.jpg +0 -0
  88. package/__e2e__/fixtures/basics/src/content/media/images/how-to-kickflip--en.webp +0 -0
  89. package/__e2e__/fixtures/basics/src/content/media/skate-sounds--en.json +0 -15
  90. package/__e2e__/fixtures/basics/src/content/media-collections/how-to-articles.json +0 -3
  91. package/__e2e__/fixtures/basics/src/content/media-types/audio.json +0 -7
  92. package/__e2e__/fixtures/basics/src/content/media-types/book.json +0 -9
  93. package/__e2e__/fixtures/basics/src/content/media-types/video.json +0 -7
  94. package/__e2e__/fixtures/basics/src/content.config.ts +0 -3
  95. package/__e2e__/fixtures/basics/src/pages/[locale]/index.astro +0 -16
  96. package/__e2e__/fixtures/basics/src/translations/de.yml +0 -9
  97. package/__e2e__/fixtures/basics/src/translations/en.yml +0 -9
  98. package/__e2e__/fixtures/basics/tailwind.config.mjs +0 -8
  99. package/__e2e__/global.teardown.ts +0 -5
  100. package/__e2e__/homepage.spec.ts +0 -123
  101. package/__e2e__/search.spec.ts +0 -14
  102. package/__tests__/pages/details-page/create-content-metadata.spec.ts +0 -135
  103. package/__tests__/utils/markdown.spec.ts +0 -74
  104. package/__tests__/utils/urls.spec.ts +0 -27
  105. package/playwright.config.ts +0 -31
  106. package/src/astro-integration/project-context.ts +0 -5
  107. package/src/content/compare-media-collection-items.ts +0 -24
  108. package/src/i18n/resolve-default-locale.ts +0 -19
  109. package/src/i18n/resolve-locales.ts +0 -5
  110. package/src/pages/details-page/utils/get-collection-items.ts +0 -29
  111. package/vitest.config.js +0 -20
package/CHANGELOG.md CHANGED
@@ -1,5 +1,60 @@
1
1
  # lightnet
2
2
 
3
+ ## 4.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#368](https://github.com/LightNetDev/LightNet/pull/368) [`e47f57b`](https://github.com/LightNetDev/LightNet/commit/e47f57b620ef269f49341e976d71b50e42c23f8a) Thanks [@smn-cds](https://github.com/smn-cds)! - Limit published package contents to runtime files only.
8
+
9
+ ## 4.0.0
10
+
11
+ ### Major Changes
12
+
13
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - LightNet now targets Astro v6, so sites using LightNet should upgrade their Astro app and follow the Astro v6 migration guide.
14
+
15
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - LightNet no longer injects Astro `i18n` routing config during setup, and locale-aware code should now use `Astro.locals.i18n.currentLocale`.
16
+
17
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Label fields now use explicit inline locale maps across config and content, and `t(...)` treats string input strictly as a translation key.
18
+
19
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - `MediaGallerySection` props were renamed for clarity, with `layout` becoming `itemWidth`, `viewLayout` becoming `layout`, and the default layout changing to `"carousel"`.
20
+
21
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Media item `content` entries now require explicit typed objects with `type: "upload"` or `type: "link"`.
22
+
23
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - DaisyUI was removed from LightNet's built-in Tailwind configuration, so projects using `dy-` classes must install and configure DaisyUI themselves.
24
+
25
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Deprecated `media-types.detailsPage.coverStyle` support was removed, and cover styling now belongs at top-level `coverImageStyle`.
26
+
27
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - The `verifySchema` and `verifySchemaAsync` utility exports were removed from `lightnet/utils`.
28
+ Replace them with Zod's native parse functions.
29
+
30
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Collection ownership was reversed from `media[].collections` to `media-collections[].mediaItems`, so collections now define membership and order directly.
31
+
32
+ ### Minor Changes
33
+
34
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - User translation keys no longer need the `x.` prefix, so custom keys can use direct names like `site.title` while built-in LightNet keys remain under `ln.*`.
35
+
36
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - LightNet now uses Lucide icons, and projects should update custom icon names from the deprecated `mdi--` prefix to `lucide--`.
37
+
38
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - LightNet no longer depends on `@astrojs/tailwind`, so sites should remove that package and use LightNet's built-in Tailwind implementation instead.
39
+
40
+ ### Patch Changes
41
+
42
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - `commonId` is now optional for media items in both `lightnet` schema validation and `@lightnet/sveltia-admin`.
43
+
44
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Avoid a dev-time Vite dependency re-optimization caused by mutating Astro integrations during startup.
45
+
46
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Export `pathWithBase` from `lightnet/utils` and use that public entrypoint inside `@lightnet/sveltia-admin` so published installs resolve the media edit button controller correctly.
47
+
48
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Strip the Astro `base` path before resolving the current locale from the URL pathname so localized pages render with the correct site language under deployments like `/docs/de/...`.
49
+
50
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Preserve inline Vite PostCSS options when LightNet injects Tailwind and Autoprefixer so existing user plugins and PostCSS settings are not dropped.
51
+
52
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Fix i18n missing-key detection so translations are validated by key presence instead of comparing the translated value to the key.
53
+
54
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Respect Astro `base` paths when LightNet builds internal page and API URLs so localized links, redirects, search form actions, and search API requests work correctly under subpath deployments such as `/docs`.
55
+
56
+ - [#361](https://github.com/LightNetDev/LightNet/pull/361) [`65b3eec`](https://github.com/LightNetDev/LightNet/commit/65b3eec5b68565237b46c6423d21257ad4747dce) Thanks [@smn-cds](https://github.com/smn-cds)! - Deduplicate media-collection memberships so repeated media items in one collection do not render the same collection multiple times.
57
+
3
58
  ## 3.12.2
4
59
 
5
60
  ### Patch Changes
@@ -13,6 +13,11 @@ import {
13
13
  queryMediaItems,
14
14
  } from "../src/content/query-media-items"
15
15
 
16
- export const getMediaItems = (
16
+ export const getMediaItems = async (
17
17
  query?: MediaItemQuery<CollectionEntry<"media">>,
18
- ) => queryMediaItems(getCollection("media"), query ?? {})
18
+ ) =>
19
+ queryMediaItems(
20
+ getCollection("media"),
21
+ getCollection("media-collections"),
22
+ query ?? {},
23
+ )
package/exports/i18n.ts CHANGED
@@ -1,2 +1 @@
1
1
  export { getLocalePaths } from "../src/i18n/get-locale-paths"
2
- export { resolveDefaultLocale } from "../src/i18n/resolve-default-locale"
package/exports/index.ts CHANGED
@@ -1,6 +1,2 @@
1
- export {
2
- type Language,
3
- type LightnetConfig,
4
- type Link,
5
- } from "../src/astro-integration/config"
1
+ export { type LightnetConfig, type Link } from "../src/astro-integration/config"
6
2
  export { lightnet as default } from "../src/astro-integration/integration"
package/exports/utils.ts CHANGED
@@ -1,2 +1 @@
1
1
  export { detailsPagePath, searchPagePath } from "../src/utils/paths"
2
- export { verifySchema, verifySchemaAsync } from "../src/utils/verify-schema"
package/package.json CHANGED
@@ -3,12 +3,15 @@
3
3
  "description": "LightNet makes it easy to run your own digital media library.",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
- "version": "3.12.2",
6
+ "version": "4.0.1",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/LightNetDev/lightnet",
10
10
  "directory": "packages/lightnet"
11
11
  },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
12
15
  "bugs": {
13
16
  "url": "https://github.com/LightNetDev/lightnet/issues"
14
17
  },
@@ -16,6 +19,13 @@
16
19
  "keywords": [
17
20
  "astro-integration"
18
21
  ],
22
+ "files": [
23
+ "exports",
24
+ "src",
25
+ "tailwind.config.ts",
26
+ "README.md",
27
+ "CHANGELOG.md"
28
+ ],
19
29
  "exports": {
20
30
  ".": "./exports/index.ts",
21
31
  "./content": "./exports/content.ts",
@@ -35,38 +45,42 @@
35
45
  "./api/versions.ts": "./src/api/versions.ts"
36
46
  },
37
47
  "peerDependencies": {
38
- "astro": "^5.1.0",
48
+ "astro": "^6.0.0",
39
49
  "react": "^19.0.0",
40
50
  "react-dom": "^19.0.0",
41
51
  "tailwindcss": ">=3.4.0 <4.0.0"
42
52
  },
43
53
  "dependencies": {
44
- "@astrojs/react": "^4.4.2",
45
- "@astrojs/tailwind": "^6.0.2",
54
+ "@astrojs/react": "^5.0.2",
55
+ "@iconify-json/lucide": "^1.2.100",
46
56
  "@iconify-json/mdi": "^1.2.3",
47
57
  "@iconify/tailwind": "^1.2.0",
48
58
  "@tailwindcss/typography": "^0.5.19",
49
- "@tanstack/react-virtual": "^3.13.18",
50
- "daisyui": "^4.12.24",
59
+ "@tanstack/react-virtual": "^3.13.23",
60
+ "autoprefixer": "^10.4.27",
51
61
  "embla-carousel": "^8.6.0",
52
62
  "embla-carousel-wheel-gestures": "^8.1.0",
53
63
  "fuse.js": "^7.1.0",
54
- "i18next": "^25.8.5",
55
- "marked": "^16.4.2",
56
- "yaml": "^2.8.2"
64
+ "i18next": "^25.10.10",
65
+ "lucide-react": "^1.7.0",
66
+ "marked": "^17.0.5",
67
+ "postcss": "^8.5.8",
68
+ "postcss-load-config": "^6.0.1",
69
+ "yaml": "^2.8.3"
57
70
  },
58
71
  "devDependencies": {
72
+ "@internal/e2e-test-utils": "^0.0.1",
59
73
  "@playwright/test": "^1.58.2",
60
- "@types/node": "^22.19.11",
61
74
  "@types/react": "^19.2.14",
75
+ "astro": "^6.1.1",
62
76
  "typescript": "^5.9.3",
63
- "vitest": "^4.0.18"
77
+ "vitest": "^4.1.2"
64
78
  },
65
79
  "engines": {
66
80
  "node": ">=22"
67
81
  },
68
82
  "scripts": {
69
83
  "test": "vitest",
70
- "e2e": "playwright install --with-deps chromium && playwright test"
84
+ "e2e": "playwright test"
71
85
  }
72
86
  }
@@ -1,5 +1,25 @@
1
1
  import { z } from "astro/zod"
2
2
 
3
+ import { isBcp47 } from "../i18n/bcp-47"
4
+ import { validateInlineTranslations } from "./validators/validate-inline-translations"
5
+ import { validateLanguages } from "./validators/validate-languages"
6
+
7
+ /**
8
+ * Translations by BCP-47 tags.
9
+ * We can only do basic validation here because we cannot access locales
10
+ * from the same config.
11
+ *
12
+ * @example
13
+ * {
14
+ * de: "Hallo",
15
+ * en: "Hello"
16
+ * }
17
+ */
18
+ export const inlineTranslationSchema = z.record(
19
+ z.string(),
20
+ z.string().nonempty(),
21
+ )
22
+
3
23
  /**
4
24
  * Link Schema.
5
25
  */
@@ -12,9 +32,10 @@ const linkSchema = z.object({
12
32
  href: z.string(),
13
33
  /**
14
34
  * Label to be used for the link.
15
- * Can either be a translation key or a fixed string.
35
+ * Must define a value for the default site locale.
36
+ * Other configured site locales are optional.
16
37
  */
17
- label: z.string(),
38
+ label: inlineTranslationSchema,
18
39
  /**
19
40
  * If this is set to true the currentLocale will be appended to
20
41
  * the href path. Eg. for href="/about"
@@ -27,63 +48,35 @@ const linkSchema = z.object({
27
48
  requiresLocale: z.boolean().default(true),
28
49
  })
29
50
 
30
- /**
31
- * Language Schema.
32
- */
51
+ const bcp47Schema = z.string().refine(isBcp47, {
52
+ message: "Invalid BCP-47 language code",
53
+ })
54
+
33
55
  const languageSchema = z
34
56
  .object({
35
57
  /**
36
- * IETF BCP-47 language tag for this language.
37
- *
38
- * This will be the identifier of this language and will
39
- * also appear on the URL paths of the website.
58
+ * BCP-47 code for this language.
40
59
  */
41
- code: z.string(),
60
+ code: bcp47Schema,
42
61
  /**
43
- * The name of the language that will be shown on the Website.
44
- *
45
- * Can either be a fixed string or a translation key.
62
+ * Display name for this language.
46
63
  */
47
- label: z.string(),
64
+ label: inlineTranslationSchema,
48
65
  /**
49
- * Should this language be used as a site language?
50
- *
51
- * Make sure to provide translations inside the `src/translations/` folder.
52
- *
53
- * Default is `false`
66
+ * Whether this language should be exposed as a site UI language.
54
67
  */
55
68
  isSiteLanguage: z.boolean().default(false),
56
69
  /**
57
- * Should this language be used as the default site language?
58
- *
59
- * The default language will be used as a fallback when translations are missing
60
- * also this will be the language selected when a user visits the site on the `/` path.
61
- *
62
- * Setting this to `true` will also set `isSiteLanguage` to `true`.
63
- *
64
- * Default is `false`
70
+ * Whether this language is the default site language.
65
71
  */
66
72
  isDefaultSiteLanguage: z.boolean().default(false),
67
73
  /**
68
- * An array of fallback language codes.
69
- *
70
- * This is used when no translation key is defined for this language.
71
- * The system will iterate over this array in order and use the first language for which a
72
- * matching translation key is found.
73
- *
74
- * If no match is found from the fallback languages, the system will
75
- * attempt the translation using the default site language.
76
- *
77
- * If the translation still cannot be resolved, it will then fall back to the English
78
- * translation as a final resort.
79
- *
80
- * @example ["fr", "it"]
74
+ * Ordered fallback language codes used when a translation key is missing.
81
75
  */
82
- fallbackLanguages: z.string().array().default([]),
76
+ fallbackLanguages: z.array(bcp47Schema).default([]),
83
77
  })
84
78
  .transform((language) => ({
85
79
  ...language,
86
- // if language is default site language also set is site language to true.
87
80
  isSiteLanguage: language.isDefaultSiteLanguage || language.isSiteLanguage,
88
81
  }))
89
82
 
@@ -123,11 +116,11 @@ export const configSchema = z.object({
123
116
  /**
124
117
  * Title of the web site.
125
118
  */
126
- title: z.string(),
119
+ title: inlineTranslationSchema,
127
120
  /**
128
- * All languages: content languages and site languages.
121
+ * Languages supported by this site.
129
122
  */
130
- languages: languageSchema.array(),
123
+ languages: z.array(languageSchema).min(1).superRefine(validateLanguages),
131
124
  /**
132
125
  * Favicons for your site.
133
126
  */
@@ -155,9 +148,10 @@ export const configSchema = z.object({
155
148
  src: z.string(),
156
149
  /**
157
150
  * Alt attribute to add for screen reader etc.
158
- * This can be a fixed string or a translation key.
151
+ * Must define a value for the default site locale.
152
+ * Other configured site locales are optional.
159
153
  */
160
- alt: z.string().optional(),
154
+ alt: inlineTranslationSchema.optional(),
161
155
  /**
162
156
  * Size in px to use for the logo on the header bar.
163
157
  * The size will be applied to the shorter side of your logo image.
@@ -223,8 +217,25 @@ export const configSchema = z.object({
223
217
  experimental: z.object({}).optional(),
224
218
  })
225
219
 
226
- export type Language = z.input<typeof languageSchema>
220
+ export const extendedConfigSchema = configSchema.transform((config, ctx) => {
221
+ const locales = config.languages
222
+ .filter((language) => language.isSiteLanguage)
223
+ .map((language) => language.code)
224
+ const defaultLocale =
225
+ config.languages.find((language) => language.isDefaultSiteLanguage)?.code ??
226
+ ""
227
+
228
+ validateInlineTranslations(config, locales, defaultLocale, ctx)
229
+
230
+ return {
231
+ ...config,
232
+ locales,
233
+ defaultLocale,
234
+ }
235
+ })
236
+
227
237
  export type Link = z.input<typeof linkSchema>
238
+ export type Language = z.output<typeof languageSchema>
228
239
 
229
240
  export type LightnetConfig = z.input<typeof configSchema>
230
- export type PreparedLightnetConfig = z.output<typeof configSchema>
241
+ export type ExtendedLightnetConfig = z.output<typeof extendedConfigSchema>
@@ -1,12 +1,11 @@
1
1
  /// <reference path="../i18n/locals.d.ts" />
2
2
  import react from "@astrojs/react"
3
- import tailwind from "@astrojs/tailwind"
4
3
  import type { AstroIntegration } from "astro"
4
+ import { AstroError } from "astro/errors"
5
5
 
6
- import { resolveDefaultLocale } from "../i18n/resolve-default-locale"
7
- import { resolveLocales } from "../i18n/resolve-locales"
8
6
  import { verifySchema } from "../utils/verify-schema"
9
- import { configSchema, type LightnetConfig } from "./config"
7
+ import { extendedConfigSchema, type LightnetConfig } from "./config"
8
+ import tailwind from "./tailwind"
10
9
  import { vitePluginLightnetConfig } from "./vite-plugin-lightnet-config"
11
10
 
12
11
  export function lightnet(lightnetConfig: LightnetConfig): AstroIntegration {
@@ -20,11 +19,18 @@ export function lightnet(lightnetConfig: LightnetConfig): AstroIntegration {
20
19
  logger,
21
20
  addMiddleware,
22
21
  }) => {
22
+ if (!astroConfig.site) {
23
+ throw new AstroError(
24
+ "Invalid LightNet configuration",
25
+ "Set `site` in your Astro config. LightNet requires Astro `site` to be configured.",
26
+ )
27
+ }
28
+
23
29
  const config = verifySchema(
24
- configSchema,
30
+ extendedConfigSchema,
25
31
  lightnetConfig,
26
32
  "Invalid LightNet configuration",
27
- "Fix these errors on the LightNet configuration inside astro.config.mjs:",
33
+ "Fix these errors on the LightNet configuration:",
28
34
  )
29
35
 
30
36
  injectRoute({
@@ -65,28 +71,11 @@ export function lightnet(lightnetConfig: LightnetConfig): AstroIntegration {
65
71
 
66
72
  addMiddleware({ entrypoint: "lightnet/locals", order: "pre" })
67
73
 
68
- astroConfig.integrations.push(
69
- tailwind({ applyBaseStyles: false }),
70
- react(),
71
- )
72
-
73
74
  updateConfig({
75
+ integrations: [tailwind(), react()],
74
76
  vite: {
75
77
  plugins: [vitePluginLightnetConfig(config, astroConfig, logger)],
76
78
  },
77
- i18n: {
78
- defaultLocale: resolveDefaultLocale(config),
79
- locales: resolveLocales(config),
80
- routing: {
81
- redirectToDefaultLocale: false,
82
- // We need to set this to false to allow for
83
- // admin paths without locale. But actually
84
- // the default locale will be prefixed for regular
85
- // LightNet pages.
86
- prefixDefaultLocale: false,
87
- fallbackType: "rewrite",
88
- },
89
- },
90
79
  })
91
80
  },
92
81
  },
@@ -0,0 +1,86 @@
1
+ import { fileURLToPath } from "node:url"
2
+
3
+ import type { AstroIntegration } from "astro"
4
+ import autoprefixerPlugin from "autoprefixer"
5
+ import type { AcceptedPlugin, ProcessOptions } from "postcss"
6
+ import postcssrc from "postcss-load-config"
7
+ import tailwindPlugin from "tailwindcss"
8
+
9
+ type PostcssInlineOptions =
10
+ | undefined
11
+ | string
12
+ | (ProcessOptions & {
13
+ plugins?: AcceptedPlugin[]
14
+ })
15
+
16
+ function isInlinePostCssOptions(
17
+ postcssInlineOptions: PostcssInlineOptions,
18
+ ): postcssInlineOptions is Exclude<PostcssInlineOptions, undefined | string> {
19
+ return (
20
+ typeof postcssInlineOptions === "object" && postcssInlineOptions !== null
21
+ )
22
+ }
23
+
24
+ async function getPostCssConfig(
25
+ root: string,
26
+ postcssInlineOptions: PostcssInlineOptions,
27
+ ) {
28
+ let postcssConfigResult
29
+ if (!isInlinePostCssOptions(postcssInlineOptions)) {
30
+ const searchPath =
31
+ typeof postcssInlineOptions === "string" ? postcssInlineOptions : root
32
+ try {
33
+ postcssConfigResult = await postcssrc({}, searchPath)
34
+ } catch {
35
+ postcssConfigResult = null
36
+ }
37
+ }
38
+ return postcssConfigResult
39
+ }
40
+ async function getViteConfiguration(
41
+ root: string,
42
+ postcssInlineOptions: PostcssInlineOptions,
43
+ ) {
44
+ const postcssConfigResult = await getPostCssConfig(root, postcssInlineOptions)
45
+ const inlinePostcssOptions = isInlinePostCssOptions(postcssInlineOptions)
46
+ ? postcssInlineOptions
47
+ : null
48
+ const postcssOptions = inlinePostcssOptions
49
+ ? { ...inlinePostcssOptions }
50
+ : (postcssConfigResult?.options ?? {})
51
+ const postcssPlugins =
52
+ inlinePostcssOptions?.plugins?.slice() ??
53
+ postcssConfigResult?.plugins?.slice() ??
54
+ []
55
+ postcssPlugins.push(tailwindPlugin())
56
+ postcssPlugins.push(autoprefixerPlugin())
57
+ return {
58
+ css: {
59
+ postcss: {
60
+ ...postcssOptions,
61
+ plugins: postcssPlugins,
62
+ },
63
+ },
64
+ }
65
+ }
66
+
67
+ // Astro v6 no longer supports Tailwind CSS v3 through `@astrojs/tailwind`,
68
+ // so LightNet provides its own minimal integration for now.
69
+ // Remove this in the next major release once Tailwind v4 is the baseline,
70
+ // and then also remove `autoprefixer`, `postcss`, and `postcss-load-config`.
71
+ function tailwindIntegration(): AstroIntegration {
72
+ return {
73
+ name: "@lightnet/tailwind",
74
+ hooks: {
75
+ "astro:config:setup": async ({ config, updateConfig }) => {
76
+ updateConfig({
77
+ vite: await getViteConfiguration(
78
+ fileURLToPath(config.root),
79
+ config.vite.css?.postcss,
80
+ ),
81
+ })
82
+ },
83
+ },
84
+ }
85
+ }
86
+ export { tailwindIntegration as default }
@@ -0,0 +1,51 @@
1
+ import { z } from "astro/zod"
2
+
3
+ export const validateInlineTranslations = (
4
+ config: {
5
+ title: Record<string, string>
6
+ languages: { label: Record<string, string> }[]
7
+ mainMenu?: { label: Record<string, string> }[]
8
+ logo?: { alt?: Record<string, string> }
9
+ },
10
+ locales: string[],
11
+ defaultLocale: string,
12
+ ctx: z.RefinementCtx,
13
+ ) => {
14
+ const validateInlineTranslation = (
15
+ inlineTranslation: Record<string, string> | undefined,
16
+ path: (string | number)[],
17
+ ) => {
18
+ if (!inlineTranslation) {
19
+ return
20
+ }
21
+
22
+ if (!(defaultLocale in inlineTranslation)) {
23
+ ctx.addIssue({
24
+ code: "custom",
25
+ message: `Missing translation for default locale "${defaultLocale}"`,
26
+ path: [...path, defaultLocale],
27
+ })
28
+ }
29
+
30
+ for (const locale of Object.keys(inlineTranslation)) {
31
+ if (locales.includes(locale)) {
32
+ continue
33
+ }
34
+
35
+ ctx.addIssue({
36
+ code: "custom",
37
+ message: `Invalid locale "${locale}". Inline translations only support configured site locales: ${locales.join(", ")}`,
38
+ path: [...path, locale],
39
+ })
40
+ }
41
+ }
42
+
43
+ validateInlineTranslation(config.title, ["title"])
44
+ for (const [index, language] of config.languages.entries()) {
45
+ validateInlineTranslation(language.label, ["languages", index, "label"])
46
+ }
47
+ for (const [index, link] of (config.mainMenu ?? []).entries()) {
48
+ validateInlineTranslation(link.label, ["mainMenu", index, "label"])
49
+ }
50
+ validateInlineTranslation(config.logo?.alt, ["logo", "alt"])
51
+ }
@@ -0,0 +1,39 @@
1
+ import { z } from "astro/zod"
2
+
3
+ type Language = {
4
+ code: string
5
+ isDefaultSiteLanguage?: boolean
6
+ }
7
+
8
+ export const validateLanguages = (
9
+ languages: Language[],
10
+ ctx: z.RefinementCtx,
11
+ ) => {
12
+ const seen = new Set<string>()
13
+ let defaultCount = 0
14
+
15
+ for (const [index, language] of languages.entries()) {
16
+ const { code, isDefaultSiteLanguage } = language
17
+
18
+ if (!seen.has(code)) {
19
+ seen.add(code)
20
+ } else {
21
+ ctx.addIssue({
22
+ code: "custom",
23
+ message: `Duplicate language code "${code}"`,
24
+ path: [index, "code"],
25
+ })
26
+ }
27
+
28
+ if (isDefaultSiteLanguage) {
29
+ defaultCount += 1
30
+ }
31
+ }
32
+
33
+ if (defaultCount !== 1) {
34
+ ctx.addIssue({
35
+ code: "custom",
36
+ message: "Exactly one language must define isDefaultSiteLanguage: true",
37
+ })
38
+ }
39
+ }
@@ -1,5 +1,5 @@
1
1
  declare module "virtual:lightnet/config" {
2
- const config: import("./config").PreparedLightnetConfig
2
+ const config: import("./config").ExtendedLightnetConfig
3
3
  export default config
4
4
  }
5
5
 
@@ -8,11 +8,6 @@ declare module "virtual:lightnet/logo" {
8
8
  export default logo
9
9
  }
10
10
 
11
- declare module "virtual:lightnet/project-context" {
12
- const context: import("./project-context").ProjectContext
13
- export default context
14
- }
15
-
16
11
  declare module "virtual:lightnet/components/CustomHead" {
17
12
  const CustomHead: ((props: Record<string, any>) => any) | undefined
18
13
  export default CustomHead
@@ -22,3 +17,10 @@ declare module "virtual:lightnet/components/CustomFooter" {
22
17
  const CustomFooter: ((props: Record<string, any>) => any) | undefined
23
18
  export default CustomFooter
24
19
  }
20
+
21
+ declare module "virtual:lightnet/components/media-item-edit-button-controller" {
22
+ const mediaItemEditButtonController:
23
+ | { shouldShow: () => boolean; createHref: (mediaId: string) => string }
24
+ | undefined
25
+ export default mediaItemEditButtonController
26
+ }