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.
- package/CHANGELOG.md +29 -0
- package/exports/content.ts +5 -7
- package/package.json +11 -11
- package/src/astro-integration/config.ts +15 -25
- package/src/components/CategoriesSection.astro +2 -2
- package/src/components/MediaGallerySection.astro +4 -9
- package/src/components/MediaList.astro +13 -13
- package/src/components/VideoPlayer.astro +4 -4
- package/src/content/content-schema.ts +5 -292
- package/src/content/get-categories.ts +26 -29
- package/src/content/get-languages.ts +13 -26
- package/src/content/get-media-collections.ts +1 -1
- package/src/content/get-media-items.ts +1 -1
- package/src/content/get-media-types.ts +21 -14
- package/src/content/query-media-items.ts +2 -1
- package/src/content/schema/category.ts +40 -0
- package/src/content/schema/media-collection.ts +31 -0
- package/src/content/schema/media-item.ts +137 -0
- package/src/content/schema/media-type.ts +90 -0
- package/src/i18n/locals.d.ts +22 -0
- package/src/i18n/locals.ts +3 -1
- package/src/i18n/record-translation.ts +74 -0
- package/src/i18n/resolve-language.ts +12 -5
- package/src/i18n/translate-map.ts +129 -19
- package/src/i18n/translate.ts +38 -0
- package/src/i18n/translation-map-schema.ts +17 -0
- package/src/i18n/translations/TRANSLATION-STATUS.md +13 -41
- package/src/i18n/translations/ar.yml +1 -0
- package/src/i18n/translations/bn.yml +1 -0
- package/src/i18n/translations/de.yml +1 -0
- package/src/i18n/translations/en.yml +24 -21
- package/src/i18n/translations/es.yml +1 -0
- package/src/i18n/translations/fi.yml +1 -0
- package/src/i18n/translations/fr.yml +1 -0
- package/src/i18n/translations/hi.yml +1 -0
- package/src/i18n/translations/kk.yml +3 -0
- package/src/i18n/translations/pt.yml +1 -0
- package/src/i18n/translations/ru.yml +1 -0
- package/src/i18n/translations/uk.yml +1 -0
- package/src/i18n/translations/ur.yml +1 -0
- package/src/i18n/translations/zh.yml +1 -0
- package/src/i18n/translations.ts +5 -2
- package/src/layouts/Page.astro +3 -4
- package/src/layouts/components/Footer.astro +63 -10
- package/src/layouts/components/LanguagePicker.astro +2 -2
- package/src/layouts/components/MenuItem.astro +4 -4
- package/src/layouts/components/PageNavigation.astro +21 -29
- package/src/layouts/components/PageTitle.astro +4 -13
- package/src/pages/details-page/DefaultDetailsPage.astro +2 -15
- package/src/pages/details-page/components/AudioPanel.astro +5 -4
- package/src/pages/details-page/components/AudioPlayer.astro +4 -4
- package/src/pages/details-page/components/ContentSection.astro +42 -43
- package/src/pages/details-page/components/MediaCollection.astro +2 -6
- package/src/pages/details-page/components/main-details/OpenButton.astro +25 -19
- package/src/pages/details-page/components/main-details/ShareButton.astro +1 -1
- package/src/pages/details-page/components/more-details/Categories.astro +3 -5
- package/src/pages/details-page/components/more-details/Languages.astro +7 -3
- package/src/pages/details-page/utils/create-content-metadata.ts +40 -56
- package/src/pages/search-page/api/search.ts +1 -1
- package/src/pages/search-page/components/SearchFilter.astro +5 -5
- package/src/pages/search-page/components/SearchList.astro +22 -19
- package/src/utils/is-external-url.ts +34 -0
- package/src/utils/link-attributes.ts +10 -0
- package/src/utils/paths.ts +6 -0
- package/src/utils/urls.ts +12 -24
- package/src/astro-integration/validators/validate-inline-translations.ts +0 -51
package/src/i18n/translations.ts
CHANGED
|
@@ -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 =
|
package/src/layouts/Page.astro
CHANGED
|
@@ -20,10 +20,9 @@ interface Props {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const { title, description, mainClass, locale } = Astro.props
|
|
23
|
-
const currentLocale
|
|
24
|
-
const
|
|
25
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
-
<
|
|
16
|
-
class="flex items-center
|
|
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
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
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,
|
|
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,
|
|
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
|
|
9
|
+
const { href, hreflang, active } = Astro.props
|
|
9
10
|
---
|
|
10
11
|
|
|
11
12
|
<li>
|
|
12
13
|
<a
|
|
13
|
-
|
|
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,
|
|
12
|
+
const { t, tConfigField, currentLocale } = Astro.locals.i18n
|
|
13
13
|
|
|
14
|
-
const items = (config.mainMenu ?? []).map(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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,
|
|
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
|
|
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,
|
|
17
|
+
const { currentLocale, tConfigField } = Astro.locals.i18n
|
|
18
18
|
|
|
19
19
|
const logoAlt = (() => {
|
|
20
20
|
if (config.logo?.alt) {
|
|
21
|
-
return
|
|
22
|
-
path: ["config", "logo", "alt"],
|
|
23
|
-
})
|
|
21
|
+
return tConfigField(config.logo.alt, config)
|
|
24
22
|
}
|
|
25
23
|
if (config.logo?.replacesTitle) {
|
|
26
|
-
return
|
|
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 {
|
|
12
|
+
const { tContentField } = Astro.locals.i18n
|
|
13
13
|
|
|
14
14
|
const item = await getMediaItem(mediaId)
|
|
15
15
|
|
|
16
|
-
const content = item.data.content.map((
|
|
17
|
-
createContentMetadata(
|
|
18
|
-
|
|
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
|
-
|
|
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,
|
|
34
|
+
const { t, tContentField, direction } = Astro.locals.i18n
|
|
34
35
|
|
|
35
|
-
const content = item.data.content.map((
|
|
36
|
-
createContentMetadata(
|
|
37
|
-
|
|
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
|
-
|
|
58
|
-
const TypeIcon = typeIcons[type]
|
|
58
|
+
content.map(({ extension, labelText, type, isDownload, url }, index) => {
|
|
59
|
+
const TypeIcon = typeIcons[type]
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
>
|
|
67
|
-
<
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
</
|
|
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,
|
|
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
|
|
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,
|
|
14
|
-
const {
|
|
13
|
+
const { mediaId, className } = Astro.props
|
|
14
|
+
const { tContentField, t } = Astro.locals.i18n
|
|
15
15
|
const item = await getMediaItem(mediaId)
|
|
16
|
-
const
|
|
17
|
-
|
|
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
|
-
|
|
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.
|
|
31
|
-
|
|
32
|
-
|
|
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,
|
|
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
|
-
{
|
|
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,
|
|
18
|
-
const currentLanguage = resolveTranslatedLanguage(
|
|
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,
|
|
25
|
+
labelText: resolveTranslatedLanguage(translation.language, tConfigField)
|
|
26
|
+
.labelText,
|
|
23
27
|
})),
|
|
24
28
|
)
|
|
25
29
|
---
|