lightnet 4.0.8 → 4.1.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/exports/content.ts +5 -7
  3. package/package.json +11 -11
  4. package/src/astro-integration/config.ts +15 -25
  5. package/src/components/CategoriesSection.astro +2 -2
  6. package/src/components/MediaGallerySection.astro +4 -9
  7. package/src/components/MediaList.astro +13 -13
  8. package/src/content/content-schema.ts +5 -292
  9. package/src/content/get-categories.ts +26 -29
  10. package/src/content/get-languages.ts +13 -26
  11. package/src/content/get-media-collections.ts +1 -1
  12. package/src/content/get-media-items.ts +1 -1
  13. package/src/content/get-media-types.ts +21 -14
  14. package/src/content/query-media-items.ts +2 -1
  15. package/src/content/schema/category.ts +40 -0
  16. package/src/content/schema/media-collection.ts +31 -0
  17. package/src/content/schema/media-item.ts +137 -0
  18. package/src/content/schema/media-type.ts +90 -0
  19. package/src/i18n/locals.d.ts +22 -0
  20. package/src/i18n/locals.ts +3 -1
  21. package/src/i18n/record-translation.ts +74 -0
  22. package/src/i18n/resolve-language.ts +12 -5
  23. package/src/i18n/translate-map.ts +129 -19
  24. package/src/i18n/translate.ts +38 -0
  25. package/src/i18n/translation-map-schema.ts +17 -0
  26. package/src/i18n/translations/TRANSLATION-STATUS.md +13 -41
  27. package/src/i18n/translations/ar.yml +1 -0
  28. package/src/i18n/translations/bn.yml +1 -0
  29. package/src/i18n/translations/de.yml +1 -0
  30. package/src/i18n/translations/es.yml +1 -0
  31. package/src/i18n/translations/fi.yml +1 -0
  32. package/src/i18n/translations/fr.yml +1 -0
  33. package/src/i18n/translations/hi.yml +1 -0
  34. package/src/i18n/translations/kk.yml +3 -0
  35. package/src/i18n/translations/pt.yml +1 -0
  36. package/src/i18n/translations/ru.yml +1 -0
  37. package/src/i18n/translations/uk.yml +1 -0
  38. package/src/i18n/translations/ur.yml +1 -0
  39. package/src/i18n/translations/zh.yml +1 -0
  40. package/src/i18n/translations.ts +5 -2
  41. package/src/layouts/Page.astro +3 -4
  42. package/src/layouts/components/Footer.astro +72 -10
  43. package/src/layouts/components/LanguagePicker.astro +2 -2
  44. package/src/layouts/components/PageNavigation.astro +17 -21
  45. package/src/layouts/components/PageTitle.astro +4 -13
  46. package/src/pages/details-page/DefaultDetailsPage.astro +2 -15
  47. package/src/pages/details-page/components/AudioPanel.astro +5 -4
  48. package/src/pages/details-page/components/ContentSection.astro +5 -4
  49. package/src/pages/details-page/components/MediaCollection.astro +2 -6
  50. package/src/pages/details-page/components/main-details/OpenButton.astro +20 -18
  51. package/src/pages/details-page/components/more-details/Categories.astro +3 -5
  52. package/src/pages/details-page/components/more-details/Languages.astro +7 -3
  53. package/src/pages/details-page/utils/create-content-metadata.ts +8 -22
  54. package/src/pages/search-page/api/search.ts +1 -1
  55. package/src/pages/search-page/components/SearchFilter.astro +5 -5
  56. package/src/pages/search-page/components/SearchList.astro +22 -19
  57. package/src/astro-integration/validators/validate-inline-translations.ts +0 -51
@@ -9,28 +9,24 @@ import Menu from "./Menu.astro"
9
9
  import MenuItem from "./MenuItem.astro"
10
10
 
11
11
  const currentPath = Astro.url.pathname
12
- const { t, tMap, currentLocale } = Astro.locals.i18n
12
+ const { t, tConfigField, currentLocale } = Astro.locals.i18n
13
13
 
14
- const items = (config.mainMenu ?? []).map(
15
- ({ href, label, requiresLocale }, index) => {
16
- const isExternal = isExternalUrl(href)
17
- const path =
18
- isExternal || !requiresLocale ? href : localizePath(currentLocale, href)
19
- const isActive =
20
- !isExternal &&
21
- (currentPath === localizePath(currentLocale, href) ||
22
- currentPath === localizePath(currentLocale, `${href}/`) ||
23
- currentPath === href)
24
- return {
25
- path,
26
- isExternal,
27
- labelText: tMap(label, {
28
- path: ["config", "mainMenu", index, "label"],
29
- }),
30
- isActive,
31
- }
32
- },
33
- )
14
+ const items = (config.mainMenu ?? []).map(({ href, label, requiresLocale }) => {
15
+ const isExternal = isExternalUrl(href)
16
+ const path =
17
+ isExternal || !requiresLocale ? href : localizePath(currentLocale, href)
18
+ const isActive =
19
+ !isExternal &&
20
+ (currentPath === localizePath(currentLocale, href) ||
21
+ currentPath === localizePath(currentLocale, `${href}/`) ||
22
+ currentPath === href)
23
+ return {
24
+ path,
25
+ isExternal,
26
+ labelText: tConfigField(label, config),
27
+ isActive,
28
+ }
29
+ })
34
30
  ---
35
31
 
36
32
  <nav class="-me-3 flex items-center">
@@ -14,18 +14,14 @@ function getWidth(logo: ImageMetadata) {
14
14
  return Math.floor(size * Math.max(1, logo.width / logo.height))
15
15
  }
16
16
 
17
- const { currentLocale, tMap } = Astro.locals.i18n
17
+ const { currentLocale, tConfigField } = Astro.locals.i18n
18
18
 
19
19
  const logoAlt = (() => {
20
20
  if (config.logo?.alt) {
21
- return tMap(config.logo.alt, {
22
- path: ["config", "logo", "alt"],
23
- })
21
+ return tConfigField(config.logo.alt, config)
24
22
  }
25
23
  if (config.logo?.replacesTitle) {
26
- return tMap(config.title, {
27
- path: ["config", "title"],
28
- })
24
+ return tConfigField(config.title, config)
29
25
  }
30
26
  return ""
31
27
  })()
@@ -53,10 +49,5 @@ const logoAlt = (() => {
53
49
  loading="eager"
54
50
  />
55
51
  ))
56
- }{
57
- !config.logo?.replacesTitle &&
58
- tMap(config.title, {
59
- path: ["config", "title"],
60
- })
61
- }</a
52
+ }{!config.logo?.replacesTitle && tConfigField(config.title, config)}</a
62
53
  >
@@ -1,5 +1,4 @@
1
1
  ---
2
- import type { TranslationMap } from "../../i18n/translate-map"
3
2
  import ContentSection from "./components/ContentSection.astro"
4
3
  import DescriptionSection from "./components/DescriptionSection.astro"
5
4
  import DetailsPage from "./components/DetailsPage.astro"
@@ -10,25 +9,13 @@ import MoreDetailsSection from "./components/MoreDetailsSection.astro"
10
9
 
11
10
  export type Props = {
12
11
  mediaId: string
13
- openActionLabel?: TranslationMap
14
12
  }
15
-
16
- const defaultOpenActionLabel = Object.fromEntries(
17
- Astro.locals.i18n.locales.map((locale) => [
18
- locale,
19
- Astro.locals.i18n.t("ln.details.open", { lng: locale }),
20
- ]),
21
- )
22
- const { mediaId, openActionLabel = defaultOpenActionLabel } = Astro.props
13
+ const { mediaId } = Astro.props
23
14
  ---
24
15
 
25
16
  <DetailsPage mediaId={mediaId}>
26
17
  <MainDetailsSection mediaId={mediaId}>
27
- <OpenButton
28
- className="mt-8"
29
- mediaId={mediaId}
30
- openActionLabel={openActionLabel}
31
- />
18
+ <OpenButton className="mt-8" mediaId={mediaId} />
32
19
  </MainDetailsSection>
33
20
  <DescriptionSection mediaId={mediaId} />
34
21
  <ContentSection mediaId={mediaId} />
@@ -9,13 +9,14 @@ interface Props {
9
9
  }
10
10
 
11
11
  const { mediaId, className } = Astro.props
12
- const { tMap } = Astro.locals.i18n
12
+ const { tContentField } = Astro.locals.i18n
13
13
 
14
14
  const item = await getMediaItem(mediaId)
15
15
 
16
- const content = item.data.content.map((contentItem, index) =>
17
- createContentMetadata(contentItem, tMap, {
18
- path: ["media-items", mediaId, "content", index],
16
+ const content = item.data.content.map(({ url, label }) =>
17
+ createContentMetadata({
18
+ url,
19
+ labelText: label && tContentField(label, item),
19
20
  }),
20
21
  )
21
22
  ---
@@ -30,11 +30,12 @@ const item = await getMediaItem(mediaId)
30
30
  if (item.data.content.length < minimumItems) {
31
31
  return
32
32
  }
33
- const { t, tMap, direction } = Astro.locals.i18n
33
+ const { t, tContentField, direction } = Astro.locals.i18n
34
34
 
35
- const content = item.data.content.map((contentItem, index) =>
36
- createContentMetadata(contentItem, tMap, {
37
- path: ["media-items", mediaId, "content", index],
35
+ const content = item.data.content.map(({ url, label }) =>
36
+ createContentMetadata({
37
+ url,
38
+ labelText: label && tContentField(label, item),
38
39
  }),
39
40
  )
40
41
  const typeIcons: Record<UrlType, LucideIcon> = {
@@ -26,7 +26,7 @@ const items = (await getEntries(collection.data.mediaItems)).map((item) => ({
26
26
  if (items.length < 2) {
27
27
  return
28
28
  }
29
- const { t, tMap } = Astro.locals.i18n
29
+ const { t, tContentField } = Astro.locals.i18n
30
30
  ---
31
31
 
32
32
  <section class="mx-auto mt-16 max-w-screen-md px-4 md:mt-20 md:px-8">
@@ -34,11 +34,7 @@ const { t, tMap } = Astro.locals.i18n
34
34
  {t("ln.details.part-of-collection")}
35
35
  </h2>
36
36
  <h3 class="mb-6 text-lg font-bold text-gray-700 sm:mb-8">
37
- {
38
- tMap(collection.data.label, {
39
- path: ["media-collections", collectionId, "label"],
40
- })
41
- }
37
+ {tContentField(collection.data.label, collection)}
42
38
  </h3>
43
39
  <MediaList items={items} />
44
40
  </section>
@@ -2,20 +2,33 @@
2
2
  import { ExternalLinkIcon } from "lucide-react"
3
3
 
4
4
  import { getMediaItem } from "../../../../content/get-media-items"
5
- import type { TranslationMap } from "../../../../i18n/translate-map"
5
+ import { getMediaType } from "../../../../content/get-media-types"
6
6
  import { createContentMetadata } from "../../utils/create-content-metadata"
7
7
 
8
8
  interface Props {
9
9
  mediaId: string
10
- openActionLabel: TranslationMap
11
10
  className?: string
12
11
  }
13
- const { mediaId, openActionLabel, className } = Astro.props
14
- const { tMap } = Astro.locals.i18n
12
+ const { mediaId, className } = Astro.props
13
+ const { tContentField, t } = Astro.locals.i18n
15
14
  const item = await getMediaItem(mediaId)
16
- const content = createContentMetadata(item.data.content[0], tMap, {
17
- path: ["media-items", mediaId, "content", 0],
15
+ const { url, label } = item.data.content[0]
16
+ const content = createContentMetadata({
17
+ url,
18
+ labelText: label && tContentField(label, item),
18
19
  })
20
+ const mediaType = await getMediaType(item.data.type.id)
21
+
22
+ const getOpenActionLabel = () => {
23
+ const defaultLabel = t("ln.details.open")
24
+ if (mediaType.data.detailsPage?.layout !== "default") {
25
+ return defaultLabel
26
+ }
27
+ if (!mediaType.data.detailsPage.openActionLabel) {
28
+ return defaultLabel
29
+ }
30
+ return tContentField(mediaType.data.detailsPage.openActionLabel, mediaType)
31
+ }
19
32
  ---
20
33
 
21
34
  <a
@@ -26,16 +39,5 @@ const content = createContentMetadata(item.data.content[0], tMap, {
26
39
  class:list={[className]}
27
40
  >
28
41
  {content.isExternal && <ExternalLinkIcon className="shrink-0" />}
29
- {
30
- content.canBeOpened
31
- ? tMap(openActionLabel, {
32
- path: [
33
- "media-types",
34
- item.data.type.id,
35
- "detailsPage",
36
- "openActionLabel",
37
- ],
38
- })
39
- : Astro.locals.i18n.t("ln.details.download")
40
- }
42
+ {content.canBeOpened ? getOpenActionLabel() : t("ln.details.download")}
41
43
  </a>
@@ -10,10 +10,10 @@ interface Props {
10
10
 
11
11
  const item = await getMediaItem(Astro.props.mediaId)
12
12
 
13
- const { t, tMap, currentLocale } = Astro.locals.i18n
13
+ const { t, tContentField, currentLocale } = Astro.locals.i18n
14
14
 
15
15
  const categories = await Promise.all(
16
- item.data.categories?.map(({ id }) => getCategory(id)) ?? [],
16
+ item.data.categories?.map(({ id }) => getCategory(id, tContentField)) ?? [],
17
17
  )
18
18
  ---
19
19
 
@@ -29,9 +29,7 @@ const categories = await Promise.all(
29
29
  category: category.id,
30
30
  })}
31
31
  >
32
- {tMap(category.label, {
33
- path: ["categories", category.id, "label"],
34
- })}
32
+ {category.labelText}
35
33
  </a>
36
34
  </li>
37
35
  ))}
@@ -14,12 +14,16 @@ const item = await getMediaItem(Astro.props.mediaId)
14
14
  const { mediaId } = Astro.props
15
15
  const translations = await getTranslations(mediaId)
16
16
 
17
- const { t, tMap, currentLocale } = Astro.locals.i18n
18
- const currentLanguage = resolveTranslatedLanguage(item.data.language, tMap)
17
+ const { t, tConfigField, currentLocale } = Astro.locals.i18n
18
+ const currentLanguage = resolveTranslatedLanguage(
19
+ item.data.language,
20
+ tConfigField,
21
+ )
19
22
  const translationLanguages = await Promise.all(
20
23
  translations.map(async (translation) => ({
21
24
  ...translation,
22
- labelText: resolveTranslatedLanguage(translation.language, tMap).labelText,
25
+ labelText: resolveTranslatedLanguage(translation.language, tConfigField)
26
+ .labelText,
23
27
  })),
24
28
  )
25
29
  ---
@@ -1,7 +1,3 @@
1
- import {
2
- type TranslateMapFn,
3
- type TranslationMap,
4
- } from "../../../i18n/translate-map"
5
1
  import { isExternalUrl } from "../../../utils/urls"
6
2
 
7
3
  export type UrlType =
@@ -47,17 +43,13 @@ const KNOWN_EXTENSIONS: Record<
47
43
  docx: { type: "text" },
48
44
  } as const
49
45
 
50
- export function createContentMetadata(
51
- {
52
- url,
53
- label: customLabel,
54
- }: {
55
- url: string
56
- label?: TranslationMap
57
- },
58
- tMap: TranslateMapFn,
59
- context: { path: (string | number)[] },
60
- ) {
46
+ export function createContentMetadata({
47
+ url,
48
+ labelText: customLabel,
49
+ }: {
50
+ url: string
51
+ labelText?: string
52
+ }) {
61
53
  const isExternal = isExternalUrl(url)
62
54
  const path = isExternal ? new URL(url).pathname : url
63
55
 
@@ -72,13 +64,7 @@ export function createContentMetadata(
72
64
  ? lastPathSegment.slice(0, -(extension.length + 1))
73
65
  : undefined
74
66
 
75
- const labelText =
76
- (customLabel &&
77
- tMap(customLabel, {
78
- path: [...context.path, "label"],
79
- })) ??
80
- fileName ??
81
- linkName
67
+ const labelText = customLabel ?? fileName ?? linkName
82
68
  const type = KNOWN_EXTENSIONS[extension]?.type ?? "link"
83
69
  const canBeOpened =
84
70
  !hasExtension || !!KNOWN_EXTENSIONS[extension]?.canBeOpened
@@ -1,8 +1,8 @@
1
1
  import type { APIRoute } from "astro"
2
2
  import { getImage } from "astro:assets"
3
3
 
4
- import type { MediaItemEntry } from "../../../content/content-schema"
5
4
  import { getMediaItems } from "../../../content/get-media-items"
5
+ import type { MediaItemEntry } from "../../../content/schema/media-item"
6
6
  import { markdownToText } from "../../../utils/markdown"
7
7
  import type { SearchItem } from "./search-response"
8
8
 
@@ -2,22 +2,22 @@
2
2
  import config from "virtual:lightnet/config"
3
3
 
4
4
  import { getUsedCategories } from "../../../content/get-categories"
5
- import { getUsedLanguages } from "../../../content/get-languages"
5
+ import { getContentLanguages } from "../../../content/get-languages"
6
6
  import { getUsedMediaTypes } from "../../../content/get-media-types"
7
7
  import { prepareI18nConfig } from "../../../i18n/react/prepare-i18n-config"
8
8
  import SearchFilterReact from "./SearchFilter.tsx"
9
9
 
10
- const { currentLocale, tMap } = Astro.locals.i18n
10
+ const { currentLocale, tConfigField, tContentField } = Astro.locals.i18n
11
11
 
12
- const categories = (await getUsedCategories(currentLocale, tMap)).map(
12
+ const categories = (await getUsedCategories(currentLocale, tContentField)).map(
13
13
  ({ id, labelText }) => ({ id, labelText }),
14
14
  )
15
15
 
16
- const mediaTypes = (await getUsedMediaTypes(currentLocale, tMap)).map(
16
+ const mediaTypes = (await getUsedMediaTypes(currentLocale, tContentField)).map(
17
17
  ({ id, labelText }) => ({ id, labelText }),
18
18
  )
19
19
 
20
- const languages = (await getUsedLanguages(currentLocale, tMap)).map(
20
+ const languages = (await getContentLanguages(currentLocale, tConfigField)).map(
21
21
  ({ code: id, labelText }) => ({ id, labelText }),
22
22
  )
23
23
 
@@ -2,42 +2,45 @@
2
2
  import { getCollection } from "astro:content"
3
3
 
4
4
  import { getUsedCategories } from "../../../content/get-categories"
5
- import { getUsedLanguages } from "../../../content/get-languages"
6
- import { getMediaTypes } from "../../../content/get-media-types"
5
+ import { getContentLanguages } from "../../../content/get-languages"
6
+ import { getTranslatedMediaTypes } from "../../../content/get-media-types"
7
7
  import { prepareI18nConfig } from "../../../i18n/react/prepare-i18n-config"
8
8
  import SearchListReact from "./SearchList.tsx"
9
9
 
10
- const { currentLocale, tMap } = Astro.locals.i18n
10
+ const { currentLocale, tContentField, tConfigField } = Astro.locals.i18n
11
11
 
12
12
  const categories: Record<string, string> = {}
13
- for (const { id, labelText } of await getUsedCategories(currentLocale, tMap)) {
13
+ for (const { id, labelText } of await getUsedCategories(
14
+ currentLocale,
15
+ tContentField,
16
+ )) {
14
17
  categories[id] = labelText
15
18
  }
16
19
 
17
20
  const mediaTypes = Object.fromEntries(
18
- (await getMediaTypes()).map((type) => [
19
- type.id,
20
- {
21
- labelText: tMap(type.data.label, {
22
- path: ["media-types", type.id, "label"],
23
- }),
24
- icon: type.data.icon,
25
- coverImageStyle: type.data.coverImageStyle,
26
- },
27
- ]),
21
+ (await getTranslatedMediaTypes(currentLocale, tContentField)).map(
22
+ ({ labelText, icon, coverImageStyle, id }) => [
23
+ id,
24
+ {
25
+ labelText,
26
+ icon,
27
+ coverImageStyle,
28
+ },
29
+ ],
30
+ ),
28
31
  )
29
32
 
30
- const contentLanguagesList = await getUsedLanguages(currentLocale, tMap)
33
+ const contentLanguages = await getContentLanguages(currentLocale, tConfigField)
31
34
  const languages = Object.fromEntries(
32
- contentLanguagesList.map((language) => [
33
- language.code,
34
- { direction: language.direction, labelText: language.labelText },
35
+ contentLanguages.map(({ code, direction, labelText }) => [
36
+ code,
37
+ { direction, labelText },
35
38
  ]),
36
39
  )
37
40
 
38
41
  const mediaItemsTotal = (await getCollection("media")).length
39
42
 
40
- const showLanguage = contentLanguagesList.length > 1
43
+ const showLanguage = contentLanguages.length > 1
41
44
 
42
45
  const i18nConfig = prepareI18nConfig(Astro.locals.i18n, [
43
46
  "ln.search.no-results",
@@ -1,51 +0,0 @@
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
- }