lightnet 3.5.0 → 3.6.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 (45) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/__e2e__/fixtures/basics/node_modules/.bin/astro +2 -2
  3. package/__e2e__/fixtures/basics/package.json +2 -2
  4. package/exports/components.ts +1 -0
  5. package/exports/content.ts +13 -3
  6. package/package.json +3 -3
  7. package/src/components/CategoriesSection.astro +69 -19
  8. package/src/components/HeroSection.astro +4 -24
  9. package/src/components/HighlightSection.astro +1 -1
  10. package/src/components/MediaGallery.astro +2 -2
  11. package/src/components/MediaList.astro +2 -2
  12. package/src/components/SearchInput.astro +30 -0
  13. package/src/components/Section.astro +1 -1
  14. package/src/content/astro-image.ts +10 -0
  15. package/src/content/compare-media-collection-items.ts +1 -1
  16. package/src/content/content-schema.ts +38 -6
  17. package/src/content/get-categories.ts +64 -6
  18. package/src/content/get-media-items.ts +1 -1
  19. package/src/content/get-media-types.ts +1 -1
  20. package/src/content/query-media-items.ts +1 -1
  21. package/src/i18n/translations/TRANSLATION-STATUS.md +32 -0
  22. package/src/i18n/translations/ar.yml +22 -0
  23. package/src/i18n/translations/bn.yml +22 -0
  24. package/src/i18n/translations/de.yml +2 -2
  25. package/src/i18n/translations/en.yml +2 -2
  26. package/src/i18n/translations/es.yml +22 -0
  27. package/src/i18n/translations/fi.yml +22 -0
  28. package/src/i18n/translations/fr.yml +22 -0
  29. package/src/i18n/translations/hi.yml +22 -0
  30. package/src/i18n/translations/pt.yml +22 -0
  31. package/src/i18n/translations/ru.yml +2 -2
  32. package/src/i18n/translations/uk.yml +2 -2
  33. package/src/i18n/translations/zh.yml +22 -0
  34. package/src/i18n/translations.ts +10 -2
  35. package/src/layouts/MarkdownPage.astro +7 -1
  36. package/src/pages/api/search.ts +1 -1
  37. package/src/pages/details-page/components/main-details/Title.astro +1 -1
  38. package/src/pages/details-page/components/more-details/Categories.astro +11 -4
  39. package/src/pages/details-page/utils/get-collection-items.ts +1 -1
  40. package/src/pages/search-page/SearchPageRoute.astro +1 -1
  41. package/src/pages/search-page/components/SearchFilter.astro +2 -2
  42. package/src/pages/search-page/components/SearchList.astro +2 -2
  43. package/src/content/content-schema-internal.ts +0 -52
  44. package/src/content/external-api.ts +0 -7
  45. package/src/content/resolve-category-label.ts +0 -20
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # lightnet
2
2
 
3
+ ## 3.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#286](https://github.com/LightNetDev/LightNet/pull/286) [`4c3b137`](https://github.com/LightNetDev/LightNet/commit/4c3b13762357d63f486261ba8eb8202e666dfb55) Thanks [@NorthernMoodler](https://github.com/NorthernMoodler)! - Refine de, ru and uk language strings
8
+
9
+ ## 3.6.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#280](https://github.com/LightNetDev/LightNet/pull/280) [`b7a9fea`](https://github.com/LightNetDev/LightNet/commit/b7a9fea1b599458838ae96866ee0910f93b6f56e) Thanks [@smn-cds](https://github.com/smn-cds)! - Feature: Category Image Grid Enhancement
14
+ - Introduced an optional `image` property for categories, allowing for enhanced visual representation.
15
+ - Updated the `CategorySection` component to support the display of category images.
16
+ - To utilize this feature, set the `layout` attribute to `image-grid`, enabling a visually appealing grid layout for category images.
17
+
18
+ - [#275](https://github.com/LightNetDev/LightNet/pull/275) [`d4bc7d4`](https://github.com/LightNetDev/LightNet/commit/d4bc7d4888b1282d9809e2451f7d867961d2dc07) Thanks [@sayedtenkanen](https://github.com/sayedtenkanen)! - Add Finnish translation
19
+
20
+ - [#281](https://github.com/LightNetDev/LightNet/pull/281) [`ed115b6`](https://github.com/LightNetDev/LightNet/commit/ed115b6a515cb22dfbf644103025c5ceb06cef1d) Thanks [@ajjn](https://github.com/ajjn)! - Add translation files for Arabic, Chinese, French, Spanish, Hindi, Bengali, and Portuguese
21
+
22
+ - [#280](https://github.com/LightNetDev/LightNet/pull/280) [`b7a9fea`](https://github.com/LightNetDev/LightNet/commit/b7a9fea1b599458838ae96866ee0910f93b6f56e) Thanks [@smn-cds](https://github.com/smn-cds)! - Improve MarkdownPage
23
+ - add rounded corners for images for more consistency with other images on LightNet
24
+ - add `className` property to apply your own classes to the component
25
+
26
+ - [#280](https://github.com/LightNetDev/LightNet/pull/280) [`b7a9fea`](https://github.com/LightNetDev/LightNet/commit/b7a9fea1b599458838ae96866ee0910f93b6f56e) Thanks [@smn-cds](https://github.com/smn-cds)! - Add reusable SearchInput component
27
+
28
+ ### Patch Changes
29
+
30
+ - [#280](https://github.com/LightNetDev/LightNet/pull/280) [`b7a9fea`](https://github.com/LightNetDev/LightNet/commit/b7a9fea1b599458838ae96866ee0910f93b6f56e) Thanks [@smn-cds](https://github.com/smn-cds)! - Improve text wrap behavior for titles by setting `text-wrap:balance`
31
+
32
+ - [#280](https://github.com/LightNetDev/LightNet/pull/280) [`b7a9fea`](https://github.com/LightNetDev/LightNet/commit/b7a9fea1b599458838ae96866ee0910f93b6f56e) Thanks [@smn-cds](https://github.com/smn-cds)! - Update translations: Remove ellipsis from search placeholder
33
+
3
34
  ## 3.5.0
4
35
 
5
36
  ### Minor Changes
@@ -6,9 +6,9 @@ case `uname` in
6
6
  esac
7
7
 
8
8
  if [ -z "$NODE_PATH" ]; then
9
- export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.10.1_@types+node@24.0.8_jiti@2.4.2_lightningcss@1.29.1_rollup@4.44.1_terser@5.39.0_typescript@5.8.3_yaml@2.8.0/node_modules/astro/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.10.1_@types+node@24.0.8_jiti@2.4.2_lightningcss@1.29.1_rollup@4.44.1_terser@5.39.0_typescript@5.8.3_yaml@2.8.0/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/node_modules"
9
+ export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.11.0_@types+node@24.0.12_jiti@2.4.2_lightningcss@1.29.1_rollup@4.44.2_terser@5.39.0_typescript@5.8.3_yaml@2.8.0/node_modules/astro/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.11.0_@types+node@24.0.12_jiti@2.4.2_lightningcss@1.29.1_rollup@4.44.2_terser@5.39.0_typescript@5.8.3_yaml@2.8.0/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/node_modules"
10
10
  else
11
- export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.10.1_@types+node@24.0.8_jiti@2.4.2_lightningcss@1.29.1_rollup@4.44.1_terser@5.39.0_typescript@5.8.3_yaml@2.8.0/node_modules/astro/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.10.1_@types+node@24.0.8_jiti@2.4.2_lightningcss@1.29.1_rollup@4.44.1_terser@5.39.0_typescript@5.8.3_yaml@2.8.0/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/node_modules:$NODE_PATH"
11
+ export NODE_PATH="/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.11.0_@types+node@24.0.12_jiti@2.4.2_lightningcss@1.29.1_rollup@4.44.2_terser@5.39.0_typescript@5.8.3_yaml@2.8.0/node_modules/astro/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/astro@5.11.0_@types+node@24.0.12_jiti@2.4.2_lightningcss@1.29.1_rollup@4.44.2_terser@5.39.0_typescript@5.8.3_yaml@2.8.0/node_modules:/home/runner/work/LightNet/LightNet/node_modules/.pnpm/node_modules:$NODE_PATH"
12
12
  fi
13
13
  if [ -x "$basedir/node" ]; then
14
14
  exec "$basedir/node" "$basedir/../astro/astro.js" "$@"
@@ -7,8 +7,8 @@
7
7
  "@astrojs/react": "^4.3.0",
8
8
  "@astrojs/tailwind": "^6.0.2",
9
9
  "@lightnet/decap-admin": "^3.1.1",
10
- "astro": "^5.10.1",
11
- "lightnet": "^3.5.0",
10
+ "astro": "^5.11.0",
11
+ "lightnet": "^3.6.0",
12
12
  "react": "^19.1.0",
13
13
  "react-dom": "^19.1.0",
14
14
  "sharp": "^0.33.5",
@@ -4,6 +4,7 @@ export { default as HighlightSection } from "../src/components/HighlightSection.
4
4
  export { default as Icon } from "../src/components/Icon"
5
5
  export { default as MediaGallerySection } from "../src/components/MediaGallerySection.astro"
6
6
  export { default as MediaList } from "../src/components/MediaList.astro"
7
+ export { default as SearchInput } from "../src/components/SearchInput.astro"
7
8
  export { default as SearchSection } from "../src/components/SearchSection.astro"
8
9
  export { default as Section } from "../src/components/Section.astro"
9
10
  export { default as VideoPlayer } from "../src/components/VideoPlayer.astro"
@@ -1,8 +1,18 @@
1
1
  export {
2
- categorySchema,
2
+ createCategorySchema as categorySchema,
3
3
  LIGHTNET_COLLECTIONS,
4
4
  mediaCollectionSchema,
5
- mediaSchema,
5
+ createMediaItemSchema as mediaItemSchema,
6
6
  mediaTypeSchema,
7
7
  } from "../src/content/content-schema"
8
- export { getMediaItems } from "../src/content/external-api"
8
+
9
+ import { type CollectionEntry, getCollection } from "astro:content"
10
+
11
+ import {
12
+ type MediaItemQuery,
13
+ queryMediaItems,
14
+ } from "../src/content/query-media-items"
15
+
16
+ export const getMediaItems = (
17
+ query?: MediaItemQuery<CollectionEntry<"media">>,
18
+ ) => queryMediaItems(getCollection("media"), query ?? {})
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "lightnet",
3
3
  "type": "module",
4
4
  "license": "MIT",
5
- "version": "3.5.0",
5
+ "version": "3.6.1",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/LightNetDev/lightnet",
@@ -49,13 +49,13 @@
49
49
  "@types/react": "^19.1.8",
50
50
  "daisyui": "^4.12.24",
51
51
  "fuse.js": "^7.1.0",
52
- "i18next": "^25.3.0",
52
+ "i18next": "^25.3.2",
53
53
  "marked": "^16.0.0",
54
54
  "yaml": "^2.8.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@playwright/test": "^1.53.2",
58
- "@types/node": "^22.15.34",
58
+ "@types/node": "^22.16.2",
59
59
  "vitest": "^3.2.4"
60
60
  },
61
61
  "scripts": {
@@ -1,37 +1,87 @@
1
1
  ---
2
- import { getCategories } from "../content/get-categories"
2
+ import { AstroError } from "astro/errors"
3
+ import { Image } from "astro:assets"
4
+
5
+ import { getUsedCategories } from "../content/get-categories"
3
6
  import { searchPagePath } from "../utils/paths"
4
7
  import Section from "./Section.astro"
5
8
 
6
9
  interface Props {
7
10
  title?: string
11
+ layout?: "button-grid" | "image-grid"
8
12
  }
9
13
 
10
- const { title } = Astro.props
14
+ const { title, layout = "button-grid" } = Astro.props
11
15
  const { t, currentLocale } = Astro.locals.i18n
12
16
 
13
- const categories = await getCategories(currentLocale, t)
17
+ const categories = await getUsedCategories(currentLocale, t)
18
+ type Category = (typeof categories)[number]
19
+
20
+ function getImage({ image, id }: Category) {
21
+ if (!image) {
22
+ throw new AstroError(
23
+ `The CategorySection with layout="image-grid" requires an image for category "${id}".`,
24
+ `To resolve this issue, either change the layout to "button-grid" or provide an image path in /src/content/categories/${id}.json.`,
25
+ )
26
+ }
27
+ return image
28
+ }
14
29
  ---
15
30
 
16
31
  {
17
32
  categories.length && (
18
33
  <Section title={title ?? t("ln.categories")}>
19
- <ul class="flex w-full flex-wrap gap-2 sm:gap-3">
20
- {categories.map((category) => (
21
- <li class="flex max-w-56 grow">
22
- <a
23
- class="flex h-12 w-full items-center justify-center rounded-xl bg-gray-200 p-2 px-8 shadow-sm hover:bg-gray-300 sm:h-14"
24
- href={searchPagePath(currentLocale, {
25
- category: category.id,
26
- })}
27
- >
28
- <span class="line-clamp-2 block text-xs font-bold uppercase text-gray-600">
29
- {category.name}
30
- </span>
31
- </a>
32
- </li>
33
- ))}
34
- </ul>
34
+ {layout === "button-grid" && (
35
+ <ul class="grid w-full grid-cols-2 flex-wrap gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
36
+ {categories.map((category) => (
37
+ <li class="grow">
38
+ <a
39
+ class="flex h-20 w-full items-center justify-center rounded-2xl bg-gray-200 p-3 text-gray-600 shadow-sm transition-colors ease-in-out hover:bg-gray-300"
40
+ href={searchPagePath(currentLocale, {
41
+ category: category.id,
42
+ })}
43
+ >
44
+ <div class="line-clamp-3 overflow-hidden text-balance text-center text-xs font-bold">
45
+ {category.name}
46
+ </div>
47
+ </a>
48
+ </li>
49
+ ))}
50
+ </ul>
51
+ )}
52
+ {layout === "image-grid" && (
53
+ <ol class="grid grid-cols-2 items-end justify-between gap-4 sm:grid-cols-3 md:grid-cols-4 md:gap-8 lg:grid-cols-5">
54
+ {categories.map((category) => (
55
+ <li>
56
+ <a
57
+ href={searchPagePath(currentLocale, { category: category.id })}
58
+ class="group flex flex-col gap-3"
59
+ >
60
+ <div class="relative overflow-hidden rounded-md shadow-md outline-2 outline-gray-400 transition-all duration-75 ease-in-out sm:group-hover:outline">
61
+ <Image
62
+ class="h-full w-full object-contain"
63
+ src={getImage(category)}
64
+ alt=""
65
+ widths={[256, 512, 768, 1024]}
66
+ sizes={
67
+ "(max-width: 640px) calc(calc(100vw - 3.5rem ) / 2), " +
68
+ "(max-width: 768px) calc(calc(100vw - 5rem ) / 3), " +
69
+ "(max-width: 1024px) calc(calc(100vw - 10rem ) / 4), " +
70
+ "(max-width: 1280px) calc(calc(100vw - 12rem ) / 5), " +
71
+ "217px"
72
+ }
73
+ />
74
+ <div class="absolute start-0 top-0 flex h-full w-full flex-col justify-end bg-gradient-to-t from-black/80 via-black/35 via-30% to-transparent to-65% p-4 text-gray-50">
75
+ <span class="line-clamp-3 text-balance font-bold">
76
+ {category.name}
77
+ </span>
78
+ </div>
79
+ </div>
80
+ </a>
81
+ </li>
82
+ ))}
83
+ </ol>
84
+ )}
35
85
  </Section>
36
86
  )
37
87
  }
@@ -2,7 +2,7 @@
2
2
  import type { ImageMetadata } from "astro"
3
3
  import { Image } from "astro:assets"
4
4
 
5
- import Icon from "./Icon"
5
+ import SearchInput from "./SearchInput.astro"
6
6
 
7
7
  interface Props {
8
8
  image: ImageMetadata
@@ -40,8 +40,6 @@ const subtitleSizes = {
40
40
  lg: "text-lg sm:text-xl md:text-3xl",
41
41
  xl: "text-xl sm:text-2xl md:text-4xl",
42
42
  } as const
43
-
44
- const t = Astro.locals.i18n.t
45
43
  ---
46
44
 
47
45
  <div class="w-full">
@@ -60,7 +58,7 @@ const t = Astro.locals.i18n.t
60
58
  {
61
59
  title && (
62
60
  <h1
63
- class="max-w-screen-md font-bold tracking-tight transition-transform duration-1000 group-hover:scale-[102%]"
61
+ class="max-w-screen-md text-balance font-bold tracking-tight transition-transform duration-1000 group-hover:scale-[102%]"
64
62
  class:list={[titleSizes[titleSize], titleClass]}
65
63
  >
66
64
  {title}
@@ -70,7 +68,7 @@ const t = Astro.locals.i18n.t
70
68
  {
71
69
  subtitle && (
72
70
  <p
73
- class="mt-1 max-w-screen-sm font-bold md:mt-2"
71
+ class="mt-1 max-w-screen-sm text-balance font-bold md:mt-2"
74
72
  class:list={[
75
73
  "sm:text-lg md:text-2xl",
76
74
  subtitleSizes[subtitleSize],
@@ -83,25 +81,7 @@ const t = Astro.locals.i18n.t
83
81
  }
84
82
  {
85
83
  showSearch && (
86
- <form
87
- action={`/${Astro.currentLocale}/media`}
88
- method="get"
89
- class="dy-join group mt-6 w-full max-w-sm rounded-2xl shadow-md outline-2 outline-offset-2 outline-gray-400 group-focus-within:outline md:mt-10 md:max-w-md"
90
- >
91
- <input
92
- class="dy-input dy-join-item grow rounded-2xl bg-gray-100/95 text-gray-900 placeholder-gray-500 shadow-inner focus:outline-none"
93
- enterkeyhint="search"
94
- type="search"
95
- name="search"
96
- placeholder={t("ln.search.placeholder")}
97
- />
98
- <button
99
- type="submit"
100
- class="dy-btn dy-join-item rounded-2xl border-gray-100/95 bg-gray-800 text-gray-50 hover:bg-gray-950 hover:text-gray-300"
101
- >
102
- <Icon className="mdi--magnify" ariaLabel={t("ln.search.title")} />
103
- </button>
104
- </form>
84
+ <SearchInput className="mt-6 max-w-sm md:mt-10 md:max-w-md" />
105
85
  )
106
86
  }
107
87
  <slot />
@@ -36,7 +36,7 @@ const { image, id, title, text, link, className, titleClass, textClass } =
36
36
  {
37
37
  title && (
38
38
  <h2
39
- class="mb-4 text-2xl font-bold sm:mb-8 sm:text-3xl"
39
+ class="mb-4 text-balance text-2xl font-bold sm:mb-8 sm:text-3xl"
40
40
  class:list={titleClass}
41
41
  >
42
42
  {title}
@@ -65,7 +65,7 @@ const items = itemsInput.filter((item) => !!item)
65
65
  <span class="absolute start-[3px] top-0 h-full w-[4px] bg-gradient-to-r from-gray-500/20 to-transparent" />
66
66
  )}
67
67
  </div>
68
- <span class="line-clamp-2 h-12 text-sm font-bold text-gray-700">
68
+ <span class="line-clamp-2 h-12 text-balance text-sm font-bold text-gray-700">
69
69
  <Icon
70
70
  className={`${types[item.data.type.id].icon} me-2 align-bottom`}
71
71
  ariaLabel={types[item.data.type.id].name}
@@ -109,7 +109,7 @@ const items = itemsInput.filter((item) => !!item)
109
109
  }
110
110
  />
111
111
  </div>
112
- <span class="line-clamp-2 h-12 text-sm font-bold text-gray-700">
112
+ <span class="line-clamp-2 h-12 text-balance text-sm font-bold text-gray-700">
113
113
  <Icon
114
114
  className={`${types[item.data.type.id].icon} me-2 align-bottom`}
115
115
  ariaLabel={types[item.data.type.id].name}
@@ -71,14 +71,14 @@ const mediaTypes = Object.fromEntries(
71
71
  class="ms-5 flex grow flex-col justify-center sm:ms-8"
72
72
  lang={item.data.language}
73
73
  >
74
- <p class="mb-1 line-clamp-3 font-bold text-gray-700 md:mb-3">
74
+ <p class="mb-1 line-clamp-3 text-balance font-bold text-gray-700 md:mb-3">
75
75
  <Icon
76
76
  className={`${mediaTypes[item.data.type.id].icon} me-2 align-bottom text-2xl text-gray-700`}
77
77
  ariaLabel={mediaTypes[item.data.type.id].name}
78
78
  />
79
79
  <span>{item.data.title}</span>
80
80
  </p>
81
- <div class="mb-3 flex flex-col flex-wrap items-start gap-2 md:flex-row md:items-center md:gap-3">
81
+ <div class="mb-3 flex flex-col flex-wrap items-start gap-2 text-balance md:flex-row md:items-center md:gap-3">
82
82
  {!!item.data.authors?.length && (
83
83
  <p class="mb-1 md:mb-0 md:text-base">
84
84
  {item.data.authors.join(", ")}
@@ -0,0 +1,30 @@
1
+ ---
2
+ import Icon from "./Icon"
3
+
4
+ type Props = {
5
+ className?: string
6
+ }
7
+
8
+ const { t } = Astro.locals.i18n
9
+ ---
10
+
11
+ <form
12
+ action={`/${Astro.currentLocale}/media`}
13
+ method="get"
14
+ class="dy-join group w-full rounded-2xl shadow-md outline-2 outline-offset-2 outline-gray-400 group-focus-within:outline"
15
+ class:list={[Astro.props.className]}
16
+ >
17
+ <input
18
+ class="dy-input dy-join-item grow rounded-2xl bg-gray-100/95 text-gray-900 placeholder-gray-500 shadow-inner focus:outline-none"
19
+ enterkeyhint="search"
20
+ type="search"
21
+ name="search"
22
+ placeholder={t("ln.search.placeholder")}
23
+ />
24
+ <button
25
+ type="submit"
26
+ class="dy-btn dy-join-item rounded-2xl border-gray-100/95 bg-gray-800 text-gray-50 hover:bg-gray-950 hover:text-gray-300"
27
+ >
28
+ <Icon className="mdi--magnify" ariaLabel={t("ln.search.title")} />
29
+ </button>
30
+ </form>
@@ -72,7 +72,7 @@ const marginTopValues = {
72
72
  {
73
73
  title && (
74
74
  <h2
75
- class="mb-10 text-2xl font-bold text-gray-700 sm:mb-12 sm:text-3xl"
75
+ class="mb-10 text-balance text-2xl font-bold text-gray-700 sm:mb-12 sm:text-3xl"
76
76
  class:list={[disableHorizontalPadding && "px-4 md:px-8"]}
77
77
  >
78
78
  {title}
@@ -12,3 +12,13 @@ export const astroImage = (image: ImageFunction) =>
12
12
  .string()
13
13
  .transform((path) => (path.startsWith("./") ? path : `./${path}`))
14
14
  .pipe(image())
15
+
16
+ /**
17
+ * The Astro image function resolves to this schema.
18
+ */
19
+ export const imageSchema = z.object({
20
+ src: z.string(),
21
+ width: z.number(),
22
+ height: z.number(),
23
+ format: z.enum(["png", "jpg", "jpeg", "tiff", "webp", "gif", "svg", "avif"]),
24
+ })
@@ -1,4 +1,4 @@
1
- import type { MediaItemEntry } from "./content-schema-internal"
1
+ import type { MediaItemEntry } from "./content-schema"
2
2
 
3
3
  export function compareMediaCollectionItems(
4
4
  item1: MediaItemEntry,
@@ -3,7 +3,7 @@ import { z } from "astro/zod"
3
3
  import type { SchemaContext } from "astro:content"
4
4
  import { defineCollection, reference } from "astro:content"
5
5
 
6
- import { astroImage } from "./astro-image"
6
+ import { astroImage, imageSchema } from "./astro-image"
7
7
 
8
8
  /**
9
9
  * Category Schema
@@ -18,6 +18,16 @@ export const categorySchema = z.object({
18
18
  * @example "category.biography"
19
19
  */
20
20
  label: z.string(),
21
+
22
+ /* Relative path to the thumbnail image of this category.
23
+ *
24
+ * The image is expected to be inside the `images` folder next to category definition json.
25
+ * It can have one of these file types: png, jpg, tiff, webp, gif, svg, avif.
26
+ * We suggest to give it a size of at least 1000px for it's longer side.
27
+ *
28
+ * @example "./images/devotionals.jpg"
29
+ */
30
+ image: imageSchema.optional(),
21
31
  })
22
32
 
23
33
  /**
@@ -117,12 +127,12 @@ export const mediaItemSchema = z.object({
117
127
  *
118
128
  * The image is expected to be inside the `images` folder next to the media item definition json.
119
129
  * This image will be used for previews and on the media item detail page.
120
- * It can have on of this file types: png, jpg, tiff, webp, gif, svg, avif.
130
+ * It can have one of these file types: png, jpg, tiff, webp, gif, svg, avif.
121
131
  * We suggest to give it a size of at least 1000px for it's longer side.
122
132
  *
123
133
  * @example "./images/a-book-about-love--en.jpg"
124
134
  */
125
- image: z.string(),
135
+ image: imageSchema,
126
136
  /**
127
137
  * List of objects defining the content of this media item.
128
138
  */
@@ -160,11 +170,16 @@ export const mediaItemSchema = z.object({
160
170
  * @param schemaContext that is passed by astro's defineCollection schema.
161
171
  * @returns schema with image mixed in.
162
172
  */
163
- export const mediaSchema = ({ image }: SchemaContext) =>
173
+ export const createMediaItemSchema = ({ image }: SchemaContext) =>
164
174
  mediaItemSchema.extend({
165
175
  image: astroImage(image),
166
176
  })
167
177
 
178
+ export const createCategorySchema = ({ image }: SchemaContext) =>
179
+ categorySchema.extend({
180
+ image: astroImage(image).optional(),
181
+ })
182
+
168
183
  /**
169
184
  * Media Type Schema
170
185
  */
@@ -246,7 +261,7 @@ export const mediaTypeSchema = z.object({
246
261
  export const LIGHTNET_COLLECTIONS = {
247
262
  categories: defineCollection({
248
263
  loader: glob({ pattern: "*.json", base: "./src/content/categories" }),
249
- schema: categorySchema,
264
+ schema: createCategorySchema,
250
265
  }),
251
266
  "media-collections": defineCollection({
252
267
  loader: glob({
@@ -260,7 +275,7 @@ export const LIGHTNET_COLLECTIONS = {
260
275
  pattern: "*.json",
261
276
  base: "./src/content/media",
262
277
  }),
263
- schema: mediaSchema,
278
+ schema: createMediaItemSchema,
264
279
  }),
265
280
  "media-types": defineCollection({
266
281
  loader: glob({
@@ -270,3 +285,20 @@ export const LIGHTNET_COLLECTIONS = {
270
285
  schema: mediaTypeSchema,
271
286
  }),
272
287
  }
288
+
289
+ export const mediaItemEntrySchema = z.object({
290
+ id: z.string(),
291
+ data: mediaItemSchema,
292
+ })
293
+
294
+ export type MediaItemEntry = z.infer<typeof mediaItemEntrySchema>
295
+
296
+ export const mediaTypeEntrySchema = z.object({
297
+ id: z.string(),
298
+ data: mediaTypeSchema,
299
+ })
300
+
301
+ export const categoryEntrySchema = z.object({
302
+ id: z.string(),
303
+ data: categorySchema,
304
+ })
@@ -1,15 +1,73 @@
1
+ import { AstroError } from "astro/errors"
2
+ import { getCollection } from "astro:content"
3
+
1
4
  import { type TranslateFn } from "../i18n/translate"
5
+ import { verifySchemaAsync } from "../utils/verify-schema"
6
+ import { categoryEntrySchema } from "./content-schema"
2
7
  import { getMediaItems } from "./get-media-items"
3
- import { resolveCategoryLabel } from "./resolve-category-label"
4
8
 
5
- const contentCategories = new Set<string>(
9
+ const categoriesById = Object.fromEntries(
10
+ (await loadCategories()).map(({ id, data }) => [id, data]),
11
+ )
12
+
13
+ const contentCategories = Object.fromEntries(
6
14
  (await getMediaItems())
7
15
  .flatMap((item) => item.data.categories ?? [])
8
- .map((c) => c.id),
16
+ .map((c) => {
17
+ const category = categoriesById[c.id]
18
+ if (!category) {
19
+ throw new AstroError(
20
+ "Missing Category",
21
+ `A media item references a non-existent category: "${c.id}".\n` +
22
+ `To fix this, create a category file at:\n` +
23
+ `src/content/categories/${c.id}.json`,
24
+ )
25
+ }
26
+ return [c.id, category]
27
+ }),
9
28
  )
10
29
 
11
- export async function getCategories(currentLocale: string, t: TranslateFn) {
12
- return [...contentCategories.values()]
13
- .map((id) => ({ id, name: resolveCategoryLabel(t, id) }))
30
+ /**
31
+ * Get categories that are referenced from media items.
32
+ * Adds the translated name of the category and sorts by this name.
33
+ *
34
+ * @param currentLocale current locale
35
+ * @param t translate function
36
+ * @returns categories sorted by name
37
+ */
38
+ export async function getUsedCategories(currentLocale: string, t: TranslateFn) {
39
+ return [...Object.entries(contentCategories)]
40
+ .map(([id, data]) => ({ id, ...data, name: t(data.label) }))
14
41
  .sort((a, b) => a.name.localeCompare(b.name, currentLocale))
15
42
  }
43
+
44
+ export async function getCategory(id: string) {
45
+ const category = categoriesById[id]
46
+ if (!category) {
47
+ throw new AstroError(
48
+ `Missing category "${id}"`,
49
+ `To fix the issue, add a category at "src/content/categories/${id}.json".`,
50
+ )
51
+ }
52
+ return {
53
+ id,
54
+ ...category,
55
+ }
56
+ }
57
+
58
+ async function loadCategories() {
59
+ return Promise.all(
60
+ (await getCollection("categories")).map((category: unknown) =>
61
+ parseCategory(category),
62
+ ),
63
+ )
64
+ }
65
+
66
+ function parseCategory(item: unknown) {
67
+ return verifySchemaAsync(
68
+ categoryEntrySchema,
69
+ item,
70
+ (id) => `Invalid category: ${id}`,
71
+ (id) => `Fix these issues inside "src/content/category/${id}.json":`,
72
+ )
73
+ }
@@ -1,7 +1,7 @@
1
1
  import { getCollection, getEntry } from "astro:content"
2
2
 
3
3
  import { verifySchemaAsync } from "../utils/verify-schema"
4
- import { mediaItemEntrySchema } from "./content-schema-internal"
4
+ import { mediaItemEntrySchema } from "./content-schema"
5
5
 
6
6
  /**
7
7
  * Internal API to get media items. Since this package is a Astro integration
@@ -1,7 +1,7 @@
1
1
  import { getCollection, getEntry } from "astro:content"
2
2
 
3
3
  import { verifySchema } from "../utils/verify-schema"
4
- import { mediaTypeEntrySchema } from "./content-schema-internal"
4
+ import { mediaTypeEntrySchema } from "./content-schema"
5
5
 
6
6
  export const getMediaType = async (id: string) => {
7
7
  return verifySchema(
@@ -1,5 +1,5 @@
1
1
  import { compareMediaCollectionItems } from "./compare-media-collection-items"
2
- import type { MediaItemEntry } from "./content-schema-internal"
2
+ import type { MediaItemEntry } from "./content-schema"
3
3
 
4
4
  export type MediaItemQuery<TMediaItem extends MediaItemEntry> = {
5
5
  /**
@@ -2,6 +2,14 @@
2
2
 
3
3
  This autogenerated report shows all built-in languages and the current status of their translations.
4
4
 
5
+ ## **AR** ([ar.yml](./ar.yml))
6
+
7
+ All keys have been translated. ✅
8
+
9
+ ## **BN** ([bn.yml](./bn.yml))
10
+
11
+ All keys have been translated. ✅
12
+
5
13
  ## **DE** ([de.yml](./de.yml))
6
14
 
7
15
  All keys have been translated. ✅
@@ -10,6 +18,26 @@ All keys have been translated. ✅
10
18
 
11
19
  All keys have been translated. ✅
12
20
 
21
+ ## **ES** ([es.yml](./es.yml))
22
+
23
+ All keys have been translated. ✅
24
+
25
+ ## **FI** ([fi.yml](./fi.yml))
26
+
27
+ All keys have been translated. ✅
28
+
29
+ ## **FR** ([fr.yml](./fr.yml))
30
+
31
+ All keys have been translated. ✅
32
+
33
+ ## **HI** ([hi.yml](./hi.yml))
34
+
35
+ All keys have been translated. ✅
36
+
37
+ ## **PT** ([pt.yml](./pt.yml))
38
+
39
+ All keys have been translated. ✅
40
+
13
41
  ## **RU** ([ru.yml](./ru.yml))
14
42
 
15
43
  All keys have been translated. ✅
@@ -17,3 +45,7 @@ All keys have been translated. ✅
17
45
  ## **UK** ([uk.yml](./uk.yml))
18
46
 
19
47
  All keys have been translated. ✅
48
+
49
+ ## **ZH** ([zh.yml](./zh.yml))
50
+
51
+ All keys have been translated. ✅
@@ -0,0 +1,22 @@
1
+ ln.header.open-main-menu: افتح القائمة الرئيسية
2
+ ln.header.select-language: اختر اللغة
3
+ ln.home.title: الصفحة الرئيسية
4
+ ln.category: الفئة
5
+ ln.categories: الفئات
6
+ ln.language: اللغة
7
+ ln.languages: اللغات
8
+ ln.type: النوع
9
+ ln.external-link: رابط خارجي
10
+ ln.search.title: بحث
11
+ ln.search.placeholder: ابحث في الوسائط
12
+ ln.search.all-languages: جميع اللغات
13
+ ln.search.all-types: جميع الأنواع
14
+ ln.search.all-categories: جميع الفئات
15
+ ln.search.no-results: لا توجد نتائج
16
+ ln.details.open: فتح
17
+ ln.details.share: مشاركة
18
+ ln.details.part-of-collection: جزء من مجموعة
19
+ ln.details.download: تنزيل
20
+ ln.share.url-copied-to-clipboard: تم نسخ الرابط إلى الحافظة
21
+ ln.404.page-not-found: الصفحة غير موجودة
22
+ ln.404.go-to-the-home-page: العودة إلى الصفحة الرئيسية
@@ -0,0 +1,22 @@
1
+ ln.header.open-main-menu: প্রধান মেনু খুলুন
2
+ ln.header.select-language: ভাষা নির্বাচন করুন
3
+ ln.home.title: হোম
4
+ ln.category: বিভাগ
5
+ ln.categories: বিভাগসমূহ
6
+ ln.language: ভাষা
7
+ ln.languages: ভাষাসমূহ
8
+ ln.type: ধরন
9
+ ln.external-link: বহিরাগত লিঙ্ক
10
+ ln.search.title: অনুসন্ধান
11
+ ln.search.placeholder: মিডিয়া অনুসন্ধান করুন
12
+ ln.search.all-languages: সকল ভাষা
13
+ ln.search.all-types: সকল ধরন
14
+ ln.search.all-categories: সকল বিভাগ
15
+ ln.search.no-results: কোনো ফলাফল নেই
16
+ ln.details.open: খুলুন
17
+ ln.details.share: শেয়ার করুন
18
+ ln.details.part-of-collection: সংগ্রহের অংশ
19
+ ln.details.download: ডাউনলোড করুন
20
+ ln.share.url-copied-to-clipboard: লিঙ্ক ক্লিপবোর্ডে কপি হয়েছে
21
+ ln.404.page-not-found: পৃষ্ঠা পাওয়া যায়নি
22
+ ln.404.go-to-the-home-page: হোম পৃষ্ঠায় যান
@@ -5,7 +5,7 @@ ln.category: Kategorie
5
5
  ln.language: Sprache
6
6
  ln.languages: Sprachen
7
7
  ln.type: Typ
8
- ln.external-link: Externer link
8
+ ln.external-link: Externer Link
9
9
  ln.details.open: Öffnen
10
10
  ln.details.part-of-collection: Teil der Sammlung
11
11
  ln.details.download: Download
@@ -17,6 +17,6 @@ ln.search.all-categories: Alle Kategorien
17
17
  ln.search.all-languages: Alle Sprachen
18
18
  ln.search.all-types: Alle Typen
19
19
  ln.search.no-results: Keine Ergebnisse
20
- ln.search.placeholder: Medien durchsuchen...
20
+ ln.search.placeholder: Suche Medien
21
21
  ln.search.title: Suche
22
22
  ln.share.url-copied-to-clipboard: Link in die Zwischenablage kopiert
@@ -62,10 +62,10 @@ ln.external-link: External link
62
62
  ln.search.title: Search
63
63
 
64
64
  # Placeholder text for the search input to search for media items.
65
- # English: Search media...
65
+ # English: Search media
66
66
  #
67
67
  # Used on: https://sk8-ministries.pages.dev/en/media/
68
- ln.search.placeholder: Search media...
68
+ ln.search.placeholder: Search media
69
69
 
70
70
  # Filter option to display content in all languages.
71
71
  #
@@ -0,0 +1,22 @@
1
+ ln.header.open-main-menu: Abrir menú principal
2
+ ln.header.select-language: Seleccionar idioma
3
+ ln.home.title: Inicio
4
+ ln.category: Categoría
5
+ ln.categories: Categorías
6
+ ln.language: Idioma
7
+ ln.languages: Idiomas
8
+ ln.type: Tipo
9
+ ln.external-link: Enlace externo
10
+ ln.search.title: Buscar
11
+ ln.search.placeholder: Buscar medios
12
+ ln.search.all-languages: Todos los idiomas
13
+ ln.search.all-types: Todos los tipos
14
+ ln.search.all-categories: Todas las categorías
15
+ ln.search.no-results: Sin resultados
16
+ ln.details.open: Abrir
17
+ ln.details.share: Compartir
18
+ ln.details.part-of-collection: Parte de la colección
19
+ ln.details.download: Descargar
20
+ ln.share.url-copied-to-clipboard: Enlace copiado al portapapeles
21
+ ln.404.page-not-found: Página no encontrada
22
+ ln.404.go-to-the-home-page: Ir a la página de inicio
@@ -0,0 +1,22 @@
1
+ ln.header.open-main-menu: Avaa päävalikko
2
+ ln.header.select-language: Valitse kieli
3
+ ln.home.title: Etusivu
4
+ ln.category: Kategoria
5
+ ln.categories: Kategoriat
6
+ ln.language: Kieli
7
+ ln.languages: Kielet
8
+ ln.type: Mediatyyppi
9
+ ln.external-link: Ulkoinen linkki
10
+ ln.search.title: Haku
11
+ ln.search.placeholder: Hae mediaa
12
+ ln.search.all-languages: Kaikki kielet
13
+ ln.search.all-types: Kaikki mediatyypit
14
+ ln.search.all-categories: Kaikki kategoriat
15
+ ln.search.no-results: Ei tuloksia
16
+ ln.details.open: Avaa
17
+ ln.details.share: Jaa
18
+ ln.details.part-of-collection: Osa kokoelmaa
19
+ ln.details.download: Lataa
20
+ ln.share.url-copied-to-clipboard: Linkki kopioitu leikepöydälle
21
+ ln.404.page-not-found: Sivua ei löytynyt
22
+ ln.404.go-to-the-home-page: Palaa etusivulle
@@ -0,0 +1,22 @@
1
+ ln.header.open-main-menu: Ouvrir le menu principal
2
+ ln.header.select-language: Sélectionner la langue
3
+ ln.home.title: Accueil
4
+ ln.category: Catégorie
5
+ ln.categories: Catégories
6
+ ln.language: Langue
7
+ ln.languages: Langues
8
+ ln.type: Type
9
+ ln.external-link: Lien externe
10
+ ln.search.title: Recherche
11
+ ln.search.placeholder: Rechercher des médias
12
+ ln.search.all-languages: Toutes les langues
13
+ ln.search.all-types: Tous les types
14
+ ln.search.all-categories: Toutes les catégories
15
+ ln.search.no-results: Aucun résultat
16
+ ln.details.open: Ouvrir
17
+ ln.details.share: Partager
18
+ ln.details.part-of-collection: Fait partie de la collection
19
+ ln.details.download: Télécharger
20
+ ln.share.url-copied-to-clipboard: Lien copié dans le presse-papiers
21
+ ln.404.page-not-found: Page non trouvée
22
+ ln.404.go-to-the-home-page: Aller à la page d’accueil
@@ -0,0 +1,22 @@
1
+ ln.header.open-main-menu: मुख्य मेनू खोलें
2
+ ln.header.select-language: भाषा चुनें
3
+ ln.home.title: मुखपृष्ठ
4
+ ln.category: श्रेणी
5
+ ln.categories: श्रेणियाँ
6
+ ln.language: भाषा
7
+ ln.languages: भाषाएँ
8
+ ln.type: प्रकार
9
+ ln.external-link: बाहरी लिंक
10
+ ln.search.title: खोज
11
+ ln.search.placeholder: मीडिया खोजें
12
+ ln.search.all-languages: सभी भाषाएँ
13
+ ln.search.all-types: सभी प्रकार
14
+ ln.search.all-categories: सभी श्रेणियाँ
15
+ ln.search.no-results: कोई परिणाम नहीं मिला
16
+ ln.details.open: खोलें
17
+ ln.details.share: साझा करें
18
+ ln.details.part-of-collection: संग्रह का भाग
19
+ ln.details.download: डाउनलोड करें
20
+ ln.share.url-copied-to-clipboard: लिंक क्लिपबोर्ड पर कॉपी हो गया है
21
+ ln.404.page-not-found: पृष्ठ नहीं मिला
22
+ ln.404.go-to-the-home-page: मुखपृष्ठ पर जाएँ
@@ -0,0 +1,22 @@
1
+ ln.header.open-main-menu: Abrir menu principal
2
+ ln.header.select-language: Escolher idioma
3
+ ln.home.title: Início
4
+ ln.category: Categoria
5
+ ln.categories: Categorias
6
+ ln.language: Idioma
7
+ ln.languages: Idiomas
8
+ ln.type: Tipo
9
+ ln.external-link: Ligação externa
10
+ ln.search.title: Pesquisa
11
+ ln.search.placeholder: Pesquisar conteúdos
12
+ ln.search.all-languages: Todos os idiomas
13
+ ln.search.all-types: Todos os tipos
14
+ ln.search.all-categories: Todas as categorias
15
+ ln.search.no-results: Nenhum resultado
16
+ ln.details.open: Abrir
17
+ ln.details.share: Partilhar
18
+ ln.details.part-of-collection: Parte da coleção
19
+ ln.details.download: Transferir
20
+ ln.share.url-copied-to-clipboard: Ligação copiada para a área de transferência
21
+ ln.404.page-not-found: Página não encontrada
22
+ ln.404.go-to-the-home-page: Ir para a página inicial
@@ -8,7 +8,7 @@ ln.languages: Языки
8
8
  ln.type: Тип
9
9
  ln.external-link: Внешняя ссылка
10
10
  ln.search.title: Поиск
11
- ln.search.placeholder: Поиск медиа...
11
+ ln.search.placeholder: Поиск медиа
12
12
  ln.search.all-languages: Все языки
13
13
  ln.search.all-types: Все типы
14
14
  ln.search.all-categories: Все категории
@@ -17,6 +17,6 @@ ln.details.open: Открыть
17
17
  ln.details.share: Поделиться
18
18
  ln.details.part-of-collection: Часть коллекции
19
19
  ln.details.download: Скачать
20
- ln.share.url-copied-to-clipboard: ссылка скопированa в буфер обмена
20
+ ln.share.url-copied-to-clipboard: Ссылка скопированa в буфер
21
21
  ln.404.page-not-found: Страница не найдена
22
22
  ln.404.go-to-the-home-page: Перейти на главную страницу
@@ -8,7 +8,7 @@ ln.languages: Мови
8
8
  ln.type: Тип
9
9
  ln.external-link: Зовнішнє посилання
10
10
  ln.search.title: Пошук
11
- ln.search.placeholder: Пошук медіа...
11
+ ln.search.placeholder: Пошук медіа
12
12
  ln.search.all-languages: Усі мови
13
13
  ln.search.all-types: Усі типи
14
14
  ln.search.all-categories: Усі категорії
@@ -17,6 +17,6 @@ ln.details.open: Відкрити
17
17
  ln.details.share: Поділитися
18
18
  ln.details.part-of-collection: Частина колекції
19
19
  ln.details.download: Завантажити
20
- ln.share.url-copied-to-clipboard: посилання скопійовано в буфер обміну
20
+ ln.share.url-copied-to-clipboard: Посилання скопійовано до буферу обміну
21
21
  ln.404.page-not-found: Сторінку не знайдено
22
22
  ln.404.go-to-the-home-page: Перейти на Головну сторінку
@@ -0,0 +1,22 @@
1
+ ln.header.open-main-menu: 打开主菜单
2
+ ln.header.select-language: 选择语言
3
+ ln.home.title: 首页
4
+ ln.category: 分类
5
+ ln.categories: 分类
6
+ ln.language: 语言
7
+ ln.languages: 语言
8
+ ln.type: 类型
9
+ ln.external-link: 外部链接
10
+ ln.search.title: 搜索
11
+ ln.search.placeholder: 搜索媒体…
12
+ ln.search.all-languages: 所有语言
13
+ ln.search.all-types: 所有类型
14
+ ln.search.all-categories: 所有分类
15
+ ln.search.no-results: 没有结果
16
+ ln.details.open: 打开
17
+ ln.details.share: 分享
18
+ ln.details.part-of-collection: 属于一个合集
19
+ ln.details.download: 下载
20
+ ln.share.url-copied-to-clipboard: 链接已复制到剪贴板
21
+ ln.404.page-not-found: 页面未找到
22
+ ln.404.go-to-the-home-page: 返回首页
@@ -1,10 +1,18 @@
1
1
  import YAML from "yaml"
2
2
 
3
3
  const builtInTranslations = {
4
- en: () => import("./translations/en.yml?raw"),
4
+ ar: () => import("./translations/ar.yml?raw"),
5
+ bn: () => import("./translations/bn.yml?raw"),
5
6
  de: () => import("./translations/de.yml?raw"),
6
- uk: () => import("./translations/uk.yml?raw"),
7
+ en: () => import("./translations/en.yml?raw"),
8
+ es: () => import("./translations/es.yml?raw"),
9
+ fi: () => import("./translations/fi.yml?raw"),
10
+ fr: () => import("./translations/fr.yml?raw"),
11
+ hi: () => import("./translations/hi.yml?raw"),
12
+ pt: () => import("./translations/pt.yml?raw"),
7
13
  ru: () => import("./translations/ru.yml?raw"),
14
+ uk: () => import("./translations/uk.yml?raw"),
15
+ zh: () => import("./translations/zh.yml?raw"),
8
16
  } as const
9
17
 
10
18
  type BuiltInLanguage = keyof typeof builtInTranslations
@@ -1,10 +1,16 @@
1
1
  ---
2
2
  import Page from "./Page.astro"
3
+
4
+ // prettier-ignore
5
+ type Props = {
6
+ className?: string
7
+ };
3
8
  ---
4
9
 
5
10
  <Page>
6
11
  <article
7
- class="prose prose-gray mx-auto mt-8 max-w-screen-md px-4 sm:mt-16 md:px-8"
12
+ class="prose prose-img:rounded-md mx-auto mt-8 max-w-screen-md px-4 sm:mt-16 md:px-8"
13
+ class:list={[Astro.props.className]}
8
14
  >
9
15
  <slot />
10
16
  </article>
@@ -1,7 +1,7 @@
1
1
  import type { APIRoute } from "astro"
2
2
  import { getImage } from "astro:assets"
3
3
 
4
- import type { MediaItemEntry } from "../../content/content-schema-internal"
4
+ import type { MediaItemEntry } from "../../content/content-schema"
5
5
  import { getMediaItems } from "../../content/get-media-items"
6
6
  import { markdownToText } from "../../utils/markdown"
7
7
  import type { SearchItem } from "./search-response"
@@ -17,7 +17,7 @@ const titleSize = {
17
17
  ---
18
18
 
19
19
  <h1
20
- class="font-bold"
20
+ class="text-balance font-bold"
21
21
  lang={language}
22
22
  class:list={[
23
23
  titleSize[title.length > 20 ? "md" : "xl"],
@@ -1,6 +1,6 @@
1
1
  ---
2
+ import { getCategory } from "../../../../content/get-categories"
2
3
  import { getMediaItem } from "../../../../content/get-media-items"
3
- import { resolveCategoryLabel } from "../../../../content/resolve-category-label"
4
4
  import { searchPagePath } from "../../../../utils/paths"
5
5
  import Label from "./Label.astro"
6
6
 
@@ -10,8 +10,11 @@ interface Props {
10
10
 
11
11
  const item = await getMediaItem(Astro.props.mediaId)
12
12
 
13
- const categories = item.data.categories?.map(({ id }) => id)
14
13
  const { t } = Astro.locals.i18n
14
+
15
+ const categories = await Promise.all(
16
+ item.data.categories?.map(({ id }) => getCategory(id)) ?? [],
17
+ )
15
18
  ---
16
19
 
17
20
  {
@@ -21,8 +24,12 @@ const { t } = Astro.locals.i18n
21
24
  <ul class="flex flex-wrap gap-2">
22
25
  {categories.map(async (category) => (
23
26
  <li class="flex rounded-lg bg-gray-200 px-4 py-1 text-gray-500 hover:bg-gray-300">
24
- <a href={searchPagePath(Astro.currentLocale, { category })}>
25
- {resolveCategoryLabel(Astro.locals.i18n.t, category)}
27
+ <a
28
+ href={searchPagePath(Astro.currentLocale, {
29
+ category: category.id,
30
+ })}
31
+ >
32
+ {t(category.label)}
26
33
  </a>
27
34
  </li>
28
35
  ))}
@@ -1,5 +1,5 @@
1
1
  import { compareMediaCollectionItems } from "../../../content/compare-media-collection-items"
2
- import type { MediaItemEntry } from "../../../content/content-schema-internal"
2
+ import type { MediaItemEntry } from "../../../content/content-schema"
3
3
  import { getMediaItems } from "../../../content/get-media-items"
4
4
 
5
5
  const groupItemsByCollections = async () => {
@@ -11,7 +11,7 @@ const { t } = Astro.locals.i18n
11
11
  <Page>
12
12
  <div class="mx-auto max-w-screen-md">
13
13
  <div class="px-4 md:px-8">
14
- <h1 class="mb-4 mt-8 text-4xl md:mb-8 md:mt-12 md:text-5xl">
14
+ <h1 class="mb-4 mt-8 text-balance text-4xl md:mb-8 md:mt-12 md:text-5xl">
15
15
  {t("ln.search.title")}
16
16
  </h1>
17
17
  <SearchFilter />
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import config from "virtual:lightnet/config"
3
3
 
4
- import { getCategories } from "../../../content/get-categories"
4
+ import { getUsedCategories } from "../../../content/get-categories"
5
5
  import { contentLanguages } from "../../../content/get-languages"
6
6
  import { getMediaTypes } from "../../../content/get-media-types"
7
7
  import { provideTranslations } from "../utils/search-filter-translations"
@@ -12,7 +12,7 @@ const { t, currentLocale } = Astro.locals.i18n
12
12
  const sortByName = (array: { id: string; name: string }[]) =>
13
13
  array.sort((a, b) => a.name.localeCompare(b.name, currentLocale))
14
14
 
15
- const categories = (await getCategories(currentLocale, t)).map(
15
+ const categories = (await getUsedCategories(currentLocale, t)).map(
16
16
  ({ id, name }) => ({ id, name }),
17
17
  )
18
18
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  import { getCollection } from "astro:content"
3
3
 
4
- import { getCategories } from "../../../content/get-categories"
4
+ import { getUsedCategories } from "../../../content/get-categories"
5
5
  import { contentLanguages } from "../../../content/get-languages"
6
6
  import { getMediaTypes } from "../../../content/get-media-types"
7
7
  import { provideTranslations } from "../utils/search-translations"
@@ -10,7 +10,7 @@ import SearchListReact from "./SearchList.tsx"
10
10
  const { t, currentLocale, direction } = Astro.locals.i18n
11
11
 
12
12
  const categories: Record<string, string> = {}
13
- for (const { id, name } of await getCategories(currentLocale, t)) {
13
+ for (const { id, name } of await getUsedCategories(currentLocale, t)) {
14
14
  categories[id] = name
15
15
  }
16
16
 
@@ -1,52 +0,0 @@
1
- /**
2
- * We use this internal content schema to make content schema easier to
3
- * comprehend for external users.
4
- */
5
- import { z } from "astro/zod"
6
-
7
- import {
8
- mediaItemSchema as externalMediaItemSchema,
9
- mediaTypeSchema,
10
- } from "./content-schema"
11
-
12
- export type MediaItem = z.infer<typeof mediaItemSchema>
13
- export type MediaItemEntry = {
14
- id: string
15
- data: MediaItem
16
- }
17
-
18
- /**
19
- * Media Item
20
- */
21
- const mediaItemSchema = externalMediaItemSchema.extend({
22
- image: z.object({
23
- src: z.string(),
24
- width: z.number(),
25
- height: z.number(),
26
- format: z.enum([
27
- "png",
28
- "jpg",
29
- "jpeg",
30
- "tiff",
31
- "webp",
32
- "gif",
33
- "svg",
34
- "avif",
35
- ]),
36
- }),
37
- })
38
-
39
- export const mediaItemEntrySchema = z.object({
40
- id: z.string(),
41
- data: mediaItemSchema,
42
- })
43
-
44
- /**
45
- * Media Type
46
- */
47
- export const mediaTypeEntrySchema = z.object({
48
- id: z.string(),
49
- data: mediaTypeSchema,
50
- })
51
-
52
- export type MediaType = z.infer<typeof mediaTypeSchema>
@@ -1,7 +0,0 @@
1
- import { type CollectionEntry, getCollection } from "astro:content"
2
-
3
- import { type MediaItemQuery, queryMediaItems } from "./query-media-items"
4
-
5
- export const getMediaItems = (
6
- query?: MediaItemQuery<CollectionEntry<"media">>,
7
- ) => queryMediaItems(getCollection("media"), query ?? {})
@@ -1,20 +0,0 @@
1
- import { AstroError } from "astro/errors"
2
- import { getCollection } from "astro:content"
3
-
4
- import type { TranslateFn } from "../i18n/translate"
5
-
6
- const categories = await getCollection("categories")
7
-
8
- export const resolveCategoryLabel = (
9
- translate: TranslateFn,
10
- categoryId: string,
11
- ) => {
12
- const category = categories.find((c) => c.id === categoryId)
13
- if (!category) {
14
- throw new AstroError(
15
- `Missing category "${categoryId}"`,
16
- `To fix the issue, add a category at "src/content/categories/${categoryId}.json".`,
17
- )
18
- }
19
- return translate(category.data.label)
20
- }