lightnet 4.0.8 → 4.1.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 (66) hide show
  1. package/CHANGELOG.md +29 -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/components/VideoPlayer.astro +4 -4
  9. package/src/content/content-schema.ts +5 -292
  10. package/src/content/get-categories.ts +26 -29
  11. package/src/content/get-languages.ts +13 -26
  12. package/src/content/get-media-collections.ts +1 -1
  13. package/src/content/get-media-items.ts +1 -1
  14. package/src/content/get-media-types.ts +21 -14
  15. package/src/content/query-media-items.ts +2 -1
  16. package/src/content/schema/category.ts +40 -0
  17. package/src/content/schema/media-collection.ts +31 -0
  18. package/src/content/schema/media-item.ts +137 -0
  19. package/src/content/schema/media-type.ts +90 -0
  20. package/src/i18n/locals.d.ts +22 -0
  21. package/src/i18n/locals.ts +3 -1
  22. package/src/i18n/record-translation.ts +74 -0
  23. package/src/i18n/resolve-language.ts +12 -5
  24. package/src/i18n/translate-map.ts +129 -19
  25. package/src/i18n/translate.ts +38 -0
  26. package/src/i18n/translation-map-schema.ts +17 -0
  27. package/src/i18n/translations/TRANSLATION-STATUS.md +13 -41
  28. package/src/i18n/translations/ar.yml +1 -0
  29. package/src/i18n/translations/bn.yml +1 -0
  30. package/src/i18n/translations/de.yml +1 -0
  31. package/src/i18n/translations/en.yml +24 -21
  32. package/src/i18n/translations/es.yml +1 -0
  33. package/src/i18n/translations/fi.yml +1 -0
  34. package/src/i18n/translations/fr.yml +1 -0
  35. package/src/i18n/translations/hi.yml +1 -0
  36. package/src/i18n/translations/kk.yml +3 -0
  37. package/src/i18n/translations/pt.yml +1 -0
  38. package/src/i18n/translations/ru.yml +1 -0
  39. package/src/i18n/translations/uk.yml +1 -0
  40. package/src/i18n/translations/ur.yml +1 -0
  41. package/src/i18n/translations/zh.yml +1 -0
  42. package/src/i18n/translations.ts +5 -2
  43. package/src/layouts/Page.astro +3 -4
  44. package/src/layouts/components/Footer.astro +63 -10
  45. package/src/layouts/components/LanguagePicker.astro +2 -2
  46. package/src/layouts/components/MenuItem.astro +4 -4
  47. package/src/layouts/components/PageNavigation.astro +21 -29
  48. package/src/layouts/components/PageTitle.astro +4 -13
  49. package/src/pages/details-page/DefaultDetailsPage.astro +2 -15
  50. package/src/pages/details-page/components/AudioPanel.astro +5 -4
  51. package/src/pages/details-page/components/AudioPlayer.astro +4 -4
  52. package/src/pages/details-page/components/ContentSection.astro +42 -43
  53. package/src/pages/details-page/components/MediaCollection.astro +2 -6
  54. package/src/pages/details-page/components/main-details/OpenButton.astro +25 -19
  55. package/src/pages/details-page/components/main-details/ShareButton.astro +1 -1
  56. package/src/pages/details-page/components/more-details/Categories.astro +3 -5
  57. package/src/pages/details-page/components/more-details/Languages.astro +7 -3
  58. package/src/pages/details-page/utils/create-content-metadata.ts +40 -56
  59. package/src/pages/search-page/api/search.ts +1 -1
  60. package/src/pages/search-page/components/SearchFilter.astro +5 -5
  61. package/src/pages/search-page/components/SearchList.astro +22 -19
  62. package/src/utils/is-external-url.ts +34 -0
  63. package/src/utils/link-attributes.ts +10 -0
  64. package/src/utils/paths.ts +6 -0
  65. package/src/utils/urls.ts +12 -24
  66. package/src/astro-integration/validators/validate-inline-translations.ts +0 -51
@@ -17,6 +17,7 @@ ln.search.all-categories: 所有分类
17
17
  ln.search.no-results: 没有结果
18
18
  ln.details.open: 打开
19
19
  ln.details.share: 分享
20
+ ln.details.edit: 编辑
20
21
  ln.details.part-of-collection: 属于一个合集
21
22
  ln.details.download: 下载
22
23
  ln.share.url-copied-to-clipboard: 链接已复制到剪贴板
@@ -1,3 +1,4 @@
1
+ import { z } from "astro/zod"
1
2
  import YAML from "yaml"
2
3
 
3
4
  const builtInTranslations = {
@@ -19,6 +20,8 @@ const builtInTranslations = {
19
20
 
20
21
  type BuiltInLanguage = keyof typeof builtInTranslations
21
22
 
23
+ const translationFileSchema = z.record(z.string(), z.string())
24
+
22
25
  const userTranslations = Object.fromEntries(
23
26
  Object.entries(
24
27
  import.meta.glob(["/src/translations/*.(yml|yaml)"], {
@@ -52,7 +55,7 @@ const loadBuiltInTranslations = async (
52
55
  return {}
53
56
  }
54
57
  const yml = (await translationMap[bcp47]()).default
55
- return YAML.parse(yml)
58
+ return translationFileSchema.parse(YAML.parse(yml))
56
59
  }
57
60
 
58
61
  const loadUserTranslations = async (bcp47: string) => {
@@ -60,7 +63,7 @@ const loadUserTranslations = async (bcp47: string) => {
60
63
  return {}
61
64
  }
62
65
  const yml = (await userTranslations[bcp47]()) as string
63
- return YAML.parse(yml)
66
+ return translationFileSchema.parse(YAML.parse(yml))
64
67
  }
65
68
 
66
69
  export type LightNetTranslationKey =
@@ -20,10 +20,9 @@ interface Props {
20
20
  }
21
21
 
22
22
  const { title, description, mainClass, locale } = Astro.props
23
- const currentLocale = locale ?? Astro.locals.i18n.currentLocale
24
- const configTitle = Astro.locals.i18n.tMap(config.title, {
25
- path: ["config", "title"],
26
- })
23
+ const { currentLocale: pathCurrentLocale, tConfigField } = Astro.locals.i18n
24
+ const currentLocale = locale ?? pathCurrentLocale
25
+ const configTitle = tConfigField(config.title, config)
27
26
  const { direction } = resolveLanguage(currentLocale)
28
27
  ---
29
28
 
@@ -1,24 +1,77 @@
1
1
  ---
2
2
  import config from "virtual:lightnet/config"
3
3
 
4
+ import { getLinkAttributes } from "../../utils/link-attributes"
5
+ import { localizePath } from "../../utils/paths"
4
6
  import LightNetLogo from "./LightNetLogo.svg"
5
7
 
6
- if (!config.credits) {
8
+ const { t, tConfigField, currentLocale } = Astro.locals.i18n
9
+
10
+ // Resolve optional footer text for the current locale.
11
+ const footerText = config.footerText
12
+ ? tConfigField(config.footerText, config)
13
+ : undefined
14
+
15
+ // Prepare footer links using the same locale rules as the main menu.
16
+ const footerLinks = (config.footerLinks ?? []).map(
17
+ ({ href, label, requiresLocale }) => {
18
+ return {
19
+ href: requiresLocale ? localizePath(currentLocale, href) : href,
20
+ label: tConfigField(label, config),
21
+ }
22
+ },
23
+ )
24
+
25
+ // Hide the footer entirely when there is nothing to show.
26
+ const shouldRenderFooter =
27
+ config.credits || !!footerText || footerLinks.length > 0
28
+
29
+ if (!shouldRenderFooter) {
7
30
  return
8
31
  }
9
32
  ---
10
33
 
11
- <footer class="w-full border-t border-gray-300">
34
+ <footer class="w-full border-t border-gray-200 bg-white">
12
35
  <div
13
- class="mx-auto flex w-full max-w-screen-xl justify-end px-4 py-4 md:px-8"
36
+ class="mx-auto flex w-full max-w-screen-xl flex-col items-center justify-between gap-4 px-4 py-6 md:flex-row md:px-8"
14
37
  >
15
- <a
16
- class="flex items-center gap-2 py-2 text-sm text-gray-800 hover:underline"
17
- href="https://lightnet.community"
18
- target="_blank"
38
+ <div
39
+ class="flex flex-wrap items-center justify-center gap-2 text-sm text-gray-700 md:justify-start"
19
40
  >
20
- <img src={LightNetLogo.src} alt="" class="h-5 w-auto" loading="lazy" />
21
- {Astro.locals.i18n.t("ln.footer.powered-by-lightnet")}
22
- </a>
41
+ {footerText && <span>{footerText}</span>}
42
+
43
+ {
44
+ footerLinks.map((link, index) => (
45
+ <Fragment>
46
+ {(footerText || index > 0) && <span class="text-gray-400">·</span>}
47
+ <a
48
+ {...getLinkAttributes(link.href)}
49
+ class="underline-offset-4 hover:underline"
50
+ >
51
+ {link.label}
52
+ </a>
53
+ </Fragment>
54
+ ))
55
+ }
56
+ </div>
57
+
58
+ {
59
+ config.credits && (
60
+ <div class="flex items-center text-sm">
61
+ <a
62
+ class="flex items-center gap-2 text-gray-800 underline-offset-4 hover:underline"
63
+ {...getLinkAttributes("https://lightnet.community")}
64
+ >
65
+ <img
66
+ src={LightNetLogo.src}
67
+ alt=""
68
+ class="h-5 w-auto"
69
+ loading="lazy"
70
+ />
71
+ <span>{t("ln.footer.powered-by-lightnet")}</span>
72
+ </a>
73
+ </div>
74
+ )
75
+ }
23
76
  </div>
24
77
  </footer>
@@ -6,7 +6,7 @@ import { localizePath, pathWithoutBase } from "../../utils/paths"
6
6
  import Menu from "./Menu.astro"
7
7
  import MenuItem from "./MenuItem.astro"
8
8
 
9
- const { locales, currentLocale, tMap } = Astro.locals.i18n
9
+ const { locales, currentLocale, tConfigField } = Astro.locals.i18n
10
10
 
11
11
  const currentPath = pathWithoutBase(Astro.url.pathname)
12
12
  const hasLocale =
@@ -18,7 +18,7 @@ const translations = (
18
18
  await Promise.all(
19
19
  locales.map(async (locale) => ({
20
20
  locale,
21
- label: resolveTranslatedLanguage(locale, tMap).labelText,
21
+ label: resolveTranslatedLanguage(locale, tConfigField).labelText,
22
22
  active: locale === currentLocale,
23
23
  href: currentPathWithLocale(locale),
24
24
  })),
@@ -1,18 +1,18 @@
1
1
  ---
2
+ import { getLinkAttributes } from "../../utils/link-attributes"
3
+
2
4
  interface Props {
3
5
  href: string
4
6
  active: boolean
5
7
  hreflang?: string
6
- target?: "_blank" | "_self"
7
8
  }
8
- const { href, hreflang, active, target } = Astro.props
9
+ const { href, hreflang, active } = Astro.props
9
10
  ---
10
11
 
11
12
  <li>
12
13
  <a
13
- href={href}
14
+ {...getLinkAttributes(href)}
14
15
  hreflang={hreflang}
15
- target={target}
16
16
  class="flex items-center gap-2 px-6 py-3 decoration-gray-800"
17
17
  class:list={[active ? "font-bold" : "hover:underline"]}
18
18
  >
@@ -2,35 +2,28 @@
2
2
  import { ExternalLinkIcon, MenuIcon, SearchIcon } from "lucide-react"
3
3
  import config from "virtual:lightnet/config"
4
4
 
5
+ import { isExternalUrl } from "../../utils/is-external-url"
5
6
  import { localizePath, searchPagePath } from "../../utils/paths"
6
- import { isExternalUrl } from "../../utils/urls"
7
7
  import LanguagePicker from "./LanguagePicker.astro"
8
8
  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 isActive =
17
+ currentPath === localizePath(currentLocale, href) ||
18
+ currentPath === localizePath(currentLocale, `${href}/`) ||
19
+ currentPath === href
20
+ return {
21
+ href: requiresLocale ? localizePath(currentLocale, href) : href,
22
+ labelText: tConfigField(label, config),
23
+ isActive,
24
+ isExternal,
25
+ }
26
+ })
34
27
  ---
35
28
 
36
29
  <nav class="-me-3 flex items-center">
@@ -53,15 +46,14 @@ const items = (config.mainMenu ?? []).map(
53
46
  !!items.length && (
54
47
  <Menu label="ln.header.open-main-menu">
55
48
  <MenuIcon slot="icon" />
56
- {items.map(({ labelText, path, isActive, isExternal }) => (
57
- <MenuItem
58
- href={path}
59
- active={isActive}
60
- target={isExternal ? "_blank" : "_self"}
61
- >
49
+ {items.map(({ labelText, href, isActive, isExternal }) => (
50
+ <MenuItem href={href} active={isActive}>
62
51
  {labelText}
63
52
  {isExternal && (
64
- <ExternalLinkIcon size="1.2rem" className="shrink-0" />
53
+ <ExternalLinkIcon
54
+ size="1.2rem"
55
+ className="shrink-0 rtl:scale-x-[-1]"
56
+ />
65
57
  )}
66
58
  </MenuItem>
67
59
  ))}
@@ -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
  ---
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  import { ExternalLinkIcon } from "lucide-react"
3
3
 
4
+ import { getLinkAttributes } from "../../../utils/link-attributes"
5
+
4
6
  type Props = {
5
7
  src: string
6
8
  className?: string
@@ -20,9 +22,7 @@ const isMp3 = audioExtension.endsWith(".mp3")
20
22
  <a
21
23
  class="group flex w-full items-center gap-4 rounded-2xl border border-gray-200 bg-white px-4 py-4 shadow-sm transition hover:border-gray-300 hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-gray-900/70"
22
24
  class:list={[className]}
23
- href={src}
24
- target="_blank"
25
- rel="noreferrer noopener"
25
+ {...getLinkAttributes(src)}
26
26
  aria-label={t("ln.external-link")}
27
27
  >
28
28
  <span class="flex h-12 w-12 items-center justify-center rounded-full border border-gray-700 text-gray-700 shadow-sm transition group-hover:scale-105">
@@ -37,7 +37,7 @@ const isMp3 = audioExtension.endsWith(".mp3")
37
37
  </span>
38
38
  <div class="flex h-full grow flex-col gap-3">
39
39
  <span class="flex items-center gap-2 font-semibold text-gray-900">
40
- <ExternalLinkIcon className="shrink-0" />
40
+ <ExternalLinkIcon className="shrink-0 rtl:scale-x-[-1]" />
41
41
  {t("ln.external-link")}
42
42
  </span>
43
43
  <div class="h-2 w-full rounded-full bg-gray-200">
@@ -13,6 +13,7 @@ import {
13
13
  } from "lucide-react"
14
14
 
15
15
  import { getMediaItem } from "../../../content/get-media-items"
16
+ import { getLinkAttributes } from "../../../utils/link-attributes"
16
17
  import {
17
18
  createContentMetadata,
18
19
  type UrlType,
@@ -30,11 +31,12 @@ const item = await getMediaItem(mediaId)
30
31
  if (item.data.content.length < minimumItems) {
31
32
  return
32
33
  }
33
- const { t, tMap, direction } = Astro.locals.i18n
34
+ const { t, tContentField, direction } = Astro.locals.i18n
34
35
 
35
- const content = item.data.content.map((contentItem, index) =>
36
- createContentMetadata(contentItem, tMap, {
37
- path: ["media-items", mediaId, "content", index],
36
+ const content = item.data.content.map(({ url, label }) =>
37
+ createContentMetadata({
38
+ url,
39
+ labelText: label && tContentField(label, item),
38
40
  }),
39
41
  )
40
42
  const typeIcons: Record<UrlType, LucideIcon> = {
@@ -53,47 +55,44 @@ const iconDirectionClass = direction === "rtl" ? "scale-x-[-1]" : ""
53
55
  class="mx-auto mt-16 max-w-screen-md overflow-hidden bg-gray-200 md:mt-20 md:rounded-xl"
54
56
  >
55
57
  {
56
- content.map(
57
- ({ extension, labelText, type, canBeOpened, url, target }, index) => {
58
- const TypeIcon = typeIcons[type]
58
+ content.map(({ extension, labelText, type, isDownload, url }, index) => {
59
+ const TypeIcon = typeIcons[type]
59
60
 
60
- return (
61
- <li class="group -mt-px px-4 transition-colors ease-in-out hover:bg-gray-300 md:px-8">
62
- <a
63
- href={url}
64
- target={target}
65
- class="flex items-center justify-between py-8"
66
- >
67
- <span class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gray-800 text-gray-200">
68
- <TypeIcon size="1.2rem" />
69
- </span>
61
+ return (
62
+ <li class="group -mt-px px-4 transition-colors ease-in-out hover:bg-gray-300 md:px-8">
63
+ <a
64
+ {...getLinkAttributes(url)}
65
+ class="flex items-center justify-between py-8"
66
+ >
67
+ <span class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-gray-800 text-gray-200">
68
+ <TypeIcon size="1.2rem" />
69
+ </span>
70
70
 
71
- <div class="ms-4 line-clamp-1 shrink grow overflow-hidden sm:ms-8">
72
- {labelText}
73
- </div>
74
- <div class="me-4 ms-2 shrink-0 font-bold uppercase text-gray-600 sm:me-8">
75
- {extension}
76
- </div>
77
- {canBeOpened ? (
78
- <>
79
- <ChevronRightIcon
80
- className={`shrink-0 text-gray-600 group-hover:text-gray-800 ${iconDirectionClass}`}
81
- />
82
- <span class="sr-only">{t("ln.details.open")}</span>
83
- </>
84
- ) : (
85
- <>
86
- <DownloadIcon className="shrink-0 text-gray-600 group-hover:text-gray-800" />
87
- <span class="sr-only">{t("ln.details.download")}</span>
88
- </>
89
- )}
90
- </a>
91
- {index !== content.length - 1 && (
92
- <div class="h-px w-full bg-gray-300" />
71
+ <div class="ms-4 line-clamp-1 shrink grow overflow-hidden sm:ms-8">
72
+ {labelText}
73
+ </div>
74
+ <div class="me-4 ms-2 shrink-0 font-bold uppercase text-gray-600 sm:me-8">
75
+ {extension}
76
+ </div>
77
+ {isDownload ? (
78
+ <>
79
+ <DownloadIcon className="shrink-0 text-gray-600 group-hover:text-gray-800" />
80
+ <span class="sr-only">{t("ln.details.download")}</span>
81
+ </>
82
+ ) : (
83
+ <>
84
+ <ChevronRightIcon
85
+ className={`shrink-0 text-gray-600 group-hover:text-gray-800 ${iconDirectionClass}`}
86
+ />
87
+ <span class="sr-only">{t("ln.details.open")}</span>
88
+ </>
93
89
  )}
94
- </li>
95
- )
96
- },
97
- )
90
+ </a>
91
+ {index !== content.length - 1 && (
92
+ <div class="h-px w-full bg-gray-300" />
93
+ )}
94
+ </li>
95
+ )
96
+ })
98
97
  }
99
98
  </ol>
@@ -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,40 +2,46 @@
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
+ import { getLinkAttributes } from "../../../../utils/link-attributes"
6
7
  import { createContentMetadata } from "../../utils/create-content-metadata"
7
8
 
8
9
  interface Props {
9
10
  mediaId: string
10
- openActionLabel: TranslationMap
11
11
  className?: string
12
12
  }
13
- const { mediaId, openActionLabel, className } = Astro.props
14
- const { tMap } = Astro.locals.i18n
13
+ const { mediaId, className } = Astro.props
14
+ const { tContentField, t } = Astro.locals.i18n
15
15
  const item = await getMediaItem(mediaId)
16
- const content = createContentMetadata(item.data.content[0], tMap, {
17
- path: ["media-items", mediaId, "content", 0],
16
+ const { url, label } = item.data.content[0]
17
+ const content = createContentMetadata({
18
+ url,
19
+ labelText: label && tContentField(label, item),
18
20
  })
21
+ const mediaType = await getMediaType(item.data.type.id)
22
+
23
+ const getOpenActionLabel = () => {
24
+ const defaultLabel = t("ln.details.open")
25
+ if (mediaType.data.detailsPage?.layout !== "default") {
26
+ return defaultLabel
27
+ }
28
+ if (!mediaType.data.detailsPage.openActionLabel) {
29
+ return defaultLabel
30
+ }
31
+ return tContentField(mediaType.data.detailsPage.openActionLabel, mediaType)
32
+ }
19
33
  ---
20
34
 
21
35
  <a
22
36
  class="flex min-w-52 items-center justify-center gap-2 rounded-2xl bg-gray-800 px-6 py-3 font-bold text-gray-100 shadow-sm hover:bg-gray-950 hover:text-gray-300"
23
- href={content.url}
24
- target={content.target}
37
+ {...getLinkAttributes(content.url)}
25
38
  hreflang={item.data.language}
26
39
  class:list={[className]}
27
40
  >
28
- {content.isExternal && <ExternalLinkIcon className="shrink-0" />}
29
41
  {
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")
42
+ content.isExternal && (
43
+ <ExternalLinkIcon className="shrink-0 rtl:scale-x-[-1]" />
44
+ )
40
45
  }
46
+ {content.isDownload ? t("ln.details.download") : getOpenActionLabel()}
41
47
  </a>
@@ -14,7 +14,7 @@ const { t } = Astro.locals.i18n
14
14
  class="flex cursor-pointer items-center gap-2 font-bold text-gray-700 underline"
15
15
  class:list={[Astro.props.className]}
16
16
  id="share-btn"
17
- ><ForwardIcon />
17
+ ><ForwardIcon className="rtl:scale-x-[-1]" />
18
18
  {t("ln.details.share")}</button
19
19
  >
20
20
  <Toast id="share-success" variant="success">
@@ -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
  ---