lightnet 4.2.0 → 4.3.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.
- package/CHANGELOG.md +22 -0
- package/package.json +12 -12
- package/src/astro-integration/config.ts +4 -0
- package/src/i18n/translations/ar.yml +1 -1
- package/src/i18n/translations/bn.yml +1 -1
- package/src/i18n/translations/de.yml +1 -1
- package/src/i18n/translations/en.yml +1 -1
- package/src/i18n/translations/es.yml +1 -1
- package/src/i18n/translations/fi.yml +1 -1
- package/src/i18n/translations/fr.yml +1 -1
- package/src/i18n/translations/hi.yml +1 -1
- package/src/i18n/translations/kk.yml +1 -1
- package/src/i18n/translations/pt.yml +1 -1
- package/src/i18n/translations/ru.yml +1 -1
- package/src/i18n/translations/uk.yml +1 -1
- package/src/i18n/translations/ur.yml +1 -1
- package/src/i18n/translations/zh.yml +1 -1
- package/src/i18n/translations.ts +1 -1
- package/src/layouts/components/Footer.astro +112 -40
- package/src/layouts/components/LanguagePicker.astro +12 -27
- package/src/layouts/components/format-footer-text.ts +14 -0
- package/src/layouts/components/get-language-selection-menu-items.ts +47 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# lightnet
|
|
2
2
|
|
|
3
|
+
## 4.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#405](https://github.com/LightNetDev/LightNet/pull/405) [`522ffae`](https://github.com/LightNetDev/LightNet/commit/522ffaed830b9e68f5479567ee326c5439ea0f27) - Add a footer language selector with shared locale-path logic and a footer-specific accessible label.
|
|
8
|
+
|
|
9
|
+
- [#408](https://github.com/LightNetDev/LightNet/pull/408) [`d0aaa12`](https://github.com/LightNetDev/LightNet/commit/d0aaa12477bec601bcd10d46290f287dc2174b60) - Support a `{{year}}` placeholder in localized footer text.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#407](https://github.com/LightNetDev/LightNet/pull/407) [`e5fa59b`](https://github.com/LightNetDev/LightNet/commit/e5fa59bbfce0aca158db08c91201fc8e03ecbaeb) - Update dependencies
|
|
14
|
+
|
|
15
|
+
- [#405](https://github.com/LightNetDev/LightNet/pull/405) [`522ffae`](https://github.com/LightNetDev/LightNet/commit/522ffaed830b9e68f5479567ee326c5439ea0f27) - Rename the shared language selection translation key to `ln.select-language` and update all locale files, comments, and call sites. This changes the public translation key used by downstream custom translations.
|
|
16
|
+
|
|
17
|
+
## 4.2.1
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- [#401](https://github.com/LightNetDev/LightNet/pull/401) [`b3db519`](https://github.com/LightNetDev/LightNet/commit/b3db519e7c0492b66f6aa26875cb91fe841fcdd5) - Adjusted footer link separator and wrapping behavior to use the updated CSS-only footer layout.
|
|
22
|
+
|
|
23
|
+
- [#404](https://github.com/LightNetDev/LightNet/pull/404) [`5acab49`](https://github.com/LightNetDev/LightNet/commit/5acab49ca79078edbba3868b9b7bce249b2fca8a) - Update dependencies.
|
|
24
|
+
|
|
3
25
|
## 4.2.0
|
|
4
26
|
|
|
5
27
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"description": "LightNet makes it easy to run your own digital media library.",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "4.
|
|
6
|
+
"version": "4.3.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/LightNetDev/lightnet",
|
|
@@ -51,29 +51,29 @@
|
|
|
51
51
|
"tailwindcss": ">=3.4.0 <4.0.0"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@astrojs/react": "^5.0.
|
|
55
|
-
"@iconify-json/lucide": "^1.2.
|
|
54
|
+
"@astrojs/react": "^5.0.7",
|
|
55
|
+
"@iconify-json/lucide": "^1.2.111",
|
|
56
56
|
"@iconify-json/mdi": "^1.2.3",
|
|
57
57
|
"@iconify/tailwind": "^1.2.0",
|
|
58
58
|
"@tailwindcss/typography": "^0.5.19",
|
|
59
|
-
"@tanstack/react-virtual": "^3.
|
|
59
|
+
"@tanstack/react-virtual": "^3.14.2",
|
|
60
60
|
"autoprefixer": "^10.5.0",
|
|
61
61
|
"embla-carousel": "^8.6.0",
|
|
62
62
|
"embla-carousel-wheel-gestures": "^8.1.0",
|
|
63
|
-
"fuse.js": "^7.
|
|
64
|
-
"i18next": "^26.
|
|
65
|
-
"lucide-react": "^1.
|
|
66
|
-
"marked": "^18.0.
|
|
67
|
-
"postcss": "^8.5.
|
|
63
|
+
"fuse.js": "^7.4.2",
|
|
64
|
+
"i18next": "^26.3.1",
|
|
65
|
+
"lucide-react": "^1.17.0",
|
|
66
|
+
"marked": "^18.0.5",
|
|
67
|
+
"postcss": "^8.5.15",
|
|
68
68
|
"postcss-load-config": "^6.0.1",
|
|
69
69
|
"yaml": "^2.9.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@playwright/test": "^1.60.0",
|
|
73
|
-
"@types/react": "^19.2.
|
|
74
|
-
"astro": "^6.
|
|
73
|
+
"@types/react": "^19.2.17",
|
|
74
|
+
"astro": "^6.4.4",
|
|
75
75
|
"typescript": "^6.0.3",
|
|
76
|
-
"vitest": "^4.1.
|
|
76
|
+
"vitest": "^4.1.8",
|
|
77
77
|
"@internal/e2e-test-utils": "^0.0.1"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|
|
@@ -115,6 +115,10 @@ export const configSchema = z.object({
|
|
|
115
115
|
credits: z.boolean().default(false),
|
|
116
116
|
/**
|
|
117
117
|
* Optional localized text to display in your site's footer.
|
|
118
|
+
*
|
|
119
|
+
* Use `{{year}}` to render the current calendar year.
|
|
120
|
+
*
|
|
121
|
+
* @example { en: "Copyright {{year}} LightNet" }
|
|
118
122
|
*/
|
|
119
123
|
footerText: translationMapSchema.optional(),
|
|
120
124
|
/**
|
|
@@ -14,7 +14,7 @@ ln.details.download: Download
|
|
|
14
14
|
ln.details.edit: Bearbeiten
|
|
15
15
|
ln.details.share: Teilen
|
|
16
16
|
ln.header.open-main-menu: Öffne Hauptmenü
|
|
17
|
-
ln.
|
|
17
|
+
ln.select-language: Sprache auswählen
|
|
18
18
|
ln.home.title: Startseite
|
|
19
19
|
ln.search.all-categories: Alle Kategorien
|
|
20
20
|
ln.search.all-languages: Alle Sprachen
|
package/src/i18n/translations.ts
CHANGED
|
@@ -82,7 +82,7 @@ export type LightNetTranslationKey =
|
|
|
82
82
|
| "ln.details.part-of-collection"
|
|
83
83
|
| "ln.details.download"
|
|
84
84
|
| "ln.header.open-main-menu"
|
|
85
|
-
| "ln.
|
|
85
|
+
| "ln.select-language"
|
|
86
86
|
| "ln.home.title"
|
|
87
87
|
| "ln.search.all-categories"
|
|
88
88
|
| "ln.search.all-languages"
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { GlobeIcon } from "lucide-react"
|
|
2
3
|
import config from "virtual:lightnet/config"
|
|
3
4
|
|
|
4
5
|
import { getLinkAttributes } from "../../utils/link-attributes"
|
|
5
6
|
import { localizePath } from "../../utils/paths"
|
|
7
|
+
import { formatFooterText } from "./format-footer-text"
|
|
8
|
+
import { getLanguageSelectionMenuItems } from "./get-language-selection-menu-items"
|
|
6
9
|
import LightNetLogo from "./LightNetLogo.svg"
|
|
7
10
|
|
|
8
11
|
const { t, tConfigField, currentLocale } = Astro.locals.i18n
|
|
9
12
|
|
|
10
13
|
// Resolve optional footer text for the current locale.
|
|
11
14
|
const footerText = config.footerText
|
|
12
|
-
? tConfigField(config.footerText, config)
|
|
15
|
+
? formatFooterText(tConfigField(config.footerText, config))
|
|
13
16
|
: undefined
|
|
14
17
|
|
|
15
18
|
// Prepare footer links using the same locale rules as the main menu.
|
|
@@ -22,56 +25,125 @@ const footerLinks = (config.footerLinks ?? []).map(
|
|
|
22
25
|
},
|
|
23
26
|
)
|
|
24
27
|
|
|
28
|
+
const { links: languageMenuItems } = getLanguageSelectionMenuItems({
|
|
29
|
+
currentLocale,
|
|
30
|
+
pathname: Astro.url.pathname,
|
|
31
|
+
tConfigField,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const shouldShowLanguageSelector = languageMenuItems.length > 1
|
|
35
|
+
|
|
25
36
|
// Hide the footer entirely when there is nothing to show.
|
|
26
37
|
const shouldRenderFooter =
|
|
27
|
-
config.credits ||
|
|
38
|
+
config.credits ||
|
|
39
|
+
!!footerText ||
|
|
40
|
+
footerLinks.length > 0 ||
|
|
41
|
+
shouldShowLanguageSelector
|
|
28
42
|
|
|
29
43
|
if (!shouldRenderFooter) {
|
|
30
44
|
return
|
|
31
45
|
}
|
|
46
|
+
|
|
47
|
+
const footerItems = [
|
|
48
|
+
...(footerText ? [{ type: "text" as const, label: footerText }] : []),
|
|
49
|
+
...footerLinks.map((link) => ({ type: "link" as const, ...link })),
|
|
50
|
+
]
|
|
32
51
|
---
|
|
33
52
|
|
|
34
53
|
<footer class="w-full border-t border-gray-200 bg-white">
|
|
35
|
-
<div
|
|
36
|
-
class="
|
|
37
|
-
>
|
|
38
|
-
<div
|
|
39
|
-
class="flex flex-wrap items-center justify-center gap-2 text-sm text-gray-700 md:justify-start"
|
|
40
|
-
>
|
|
41
|
-
{footerText && <span>{footerText}</span>}
|
|
42
|
-
|
|
54
|
+
<div class="mx-auto w-full max-w-screen-xl px-4 md:px-8">
|
|
55
|
+
<div class="flex flex-col gap-4 py-6">
|
|
43
56
|
{
|
|
44
|
-
|
|
45
|
-
<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
57
|
+
shouldShowLanguageSelector && (
|
|
58
|
+
<div class="flex w-full justify-start">
|
|
59
|
+
<label class="inline-flex items-center gap-2 bg-white py-2 text-sm text-gray-800">
|
|
60
|
+
<GlobeIcon
|
|
61
|
+
className="h-4 w-4 shrink-0 text-gray-500"
|
|
62
|
+
aria-hidden="true"
|
|
63
|
+
/>
|
|
64
|
+
<span class="sr-only">{t("ln.select-language")}</span>
|
|
65
|
+
<select
|
|
66
|
+
class="min-w-0 cursor-pointer appearance-none border-0 bg-transparent p-0 pr-0 text-sm font-medium text-gray-800 outline-none focus:ring-0"
|
|
67
|
+
aria-label={t("ln.select-language")}
|
|
68
|
+
data-footer-language-select
|
|
69
|
+
>
|
|
70
|
+
{languageMenuItems.map(({ href, label, locale, active }) => (
|
|
71
|
+
<option value={href} selected={active} lang={locale}>
|
|
72
|
+
{label}
|
|
73
|
+
</option>
|
|
74
|
+
))}
|
|
75
|
+
</select>
|
|
76
|
+
</label>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
55
79
|
}
|
|
56
|
-
</div>
|
|
57
80
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
<div
|
|
82
|
+
class="flex w-full flex-col gap-4 text-sm text-gray-800 md:flex-row md:items-center md:justify-between md:gap-6"
|
|
83
|
+
>
|
|
84
|
+
{
|
|
85
|
+
footerItems.length > 0 && (
|
|
86
|
+
<div class="flex w-full flex-row flex-wrap items-center gap-x-2 gap-y-1 md:w-auto">
|
|
87
|
+
{footerItems.map((item, index) => (
|
|
88
|
+
<span class="inline-flex items-center gap-1.5">
|
|
89
|
+
<span
|
|
90
|
+
class:list={[
|
|
91
|
+
"w-3 shrink-0 justify-center text-center text-[0.8em] leading-none text-gray-400",
|
|
92
|
+
index === 0 ? "hidden" : "inline-flex",
|
|
93
|
+
]}
|
|
94
|
+
aria-hidden="true"
|
|
95
|
+
>
|
|
96
|
+
·
|
|
97
|
+
</span>
|
|
98
|
+
{item.type === "text" ? (
|
|
99
|
+
<span class="text-gray-800">{item.label}</span>
|
|
100
|
+
) : (
|
|
101
|
+
<a
|
|
102
|
+
{...getLinkAttributes(item.href)}
|
|
103
|
+
class="text-gray-800 underline-offset-4 hover:underline"
|
|
104
|
+
>
|
|
105
|
+
{item.label}
|
|
106
|
+
</a>
|
|
107
|
+
)}
|
|
108
|
+
</span>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
{
|
|
115
|
+
config.credits && (
|
|
116
|
+
<div class="flex w-full items-center justify-start md:w-auto md:justify-end">
|
|
117
|
+
<a
|
|
118
|
+
class="flex items-center gap-2 whitespace-nowrap text-gray-800 underline-offset-4 hover:underline"
|
|
119
|
+
{...getLinkAttributes("https://lightnet.community")}
|
|
120
|
+
>
|
|
121
|
+
<img
|
|
122
|
+
src={LightNetLogo.src}
|
|
123
|
+
alt=""
|
|
124
|
+
class="h-5 w-auto"
|
|
125
|
+
loading="lazy"
|
|
126
|
+
/>
|
|
127
|
+
<span>{t("ln.footer.powered-by-lightnet")}</span>
|
|
128
|
+
</a>
|
|
129
|
+
</div>
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
76
134
|
</div>
|
|
77
135
|
</footer>
|
|
136
|
+
|
|
137
|
+
<script>
|
|
138
|
+
const footerLanguageSelect = document.querySelector<HTMLSelectElement>(
|
|
139
|
+
"[data-footer-language-select]",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
footerLanguageSelect?.addEventListener("change", () => {
|
|
143
|
+
if (!footerLanguageSelect.value) {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
window.location.assign(footerLanguageSelect.value)
|
|
148
|
+
})
|
|
149
|
+
</script>
|
|
@@ -1,43 +1,28 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { GlobeIcon } from "lucide-react"
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import { localizePath, pathWithoutBase } from "../../utils/paths"
|
|
4
|
+
import { getLanguageSelectionMenuItems } from "./get-language-selection-menu-items"
|
|
6
5
|
import Menu from "./Menu.astro"
|
|
7
6
|
import MenuItem from "./MenuItem.astro"
|
|
8
7
|
|
|
9
|
-
const {
|
|
8
|
+
const { currentLocale, tConfigField } = Astro.locals.i18n
|
|
10
9
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
locale,
|
|
21
|
-
label: resolveTranslatedLanguage(locale, tConfigField).labelText,
|
|
22
|
-
active: locale === currentLocale,
|
|
23
|
-
href: currentPathWithLocale(locale),
|
|
24
|
-
})),
|
|
25
|
-
)
|
|
26
|
-
).sort((a, b) => a.label.localeCompare(b.label))
|
|
27
|
-
|
|
28
|
-
function currentPathWithLocale(locale: string) {
|
|
29
|
-
const currentPathWithoutLocale = hasLocale
|
|
30
|
-
? currentPath.slice(currentLocale.length + 1)
|
|
31
|
-
: currentPath
|
|
32
|
-
return localizePath(locale, currentPathWithoutLocale)
|
|
33
|
-
}
|
|
10
|
+
const {
|
|
11
|
+
currentPath,
|
|
12
|
+
hasLocale,
|
|
13
|
+
links: translations,
|
|
14
|
+
} = getLanguageSelectionMenuItems({
|
|
15
|
+
currentLocale,
|
|
16
|
+
pathname: Astro.url.pathname,
|
|
17
|
+
tConfigField,
|
|
18
|
+
})
|
|
34
19
|
|
|
35
20
|
const disabled = !hasLocale && currentPath !== "/"
|
|
36
21
|
---
|
|
37
22
|
|
|
38
23
|
{
|
|
39
24
|
translations.length > 1 && (
|
|
40
|
-
<Menu disabled={disabled} label="ln.
|
|
25
|
+
<Menu disabled={disabled} label="ln.select-language">
|
|
41
26
|
<GlobeIcon slot="icon" />
|
|
42
27
|
{translations.map(({ label, locale, active, href }) => (
|
|
43
28
|
<MenuItem href={href} hreflang={locale} active={active}>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaces supported placeholders in localized footer text.
|
|
3
|
+
*
|
|
4
|
+
* Use `{{year}}` to render the current calendar year.
|
|
5
|
+
*
|
|
6
|
+
* @param text Localized footer text from LightNet config.
|
|
7
|
+
* @param currentYear Year to render. Defaults to the current system year.
|
|
8
|
+
*/
|
|
9
|
+
export const formatFooterText = (
|
|
10
|
+
text: string,
|
|
11
|
+
currentYear = new Date().getFullYear(),
|
|
12
|
+
) => {
|
|
13
|
+
return text.replaceAll("{{year}}", String(currentYear))
|
|
14
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import config from "virtual:lightnet/config"
|
|
2
|
+
|
|
3
|
+
import { resolveLanguage } from "../../i18n/resolve-language"
|
|
4
|
+
import type { TranslateConfigFieldFn } from "../../i18n/translate-map"
|
|
5
|
+
import { localizePath, pathWithoutBase } from "../../utils/paths"
|
|
6
|
+
|
|
7
|
+
export type LanguageLink = {
|
|
8
|
+
active: boolean
|
|
9
|
+
href: string
|
|
10
|
+
label: string
|
|
11
|
+
locale: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getLanguageSelectionMenuItems({
|
|
15
|
+
currentLocale,
|
|
16
|
+
pathname,
|
|
17
|
+
tConfigField,
|
|
18
|
+
}: {
|
|
19
|
+
currentLocale: string
|
|
20
|
+
pathname: string
|
|
21
|
+
tConfigField: TranslateConfigFieldFn
|
|
22
|
+
}) {
|
|
23
|
+
const currentPath = pathWithoutBase(pathname)
|
|
24
|
+
const hasLocale = Boolean(
|
|
25
|
+
currentLocale &&
|
|
26
|
+
(currentPath.startsWith(`/${currentLocale}/`) ||
|
|
27
|
+
currentPath === `/${currentLocale}`),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const links = config.locales
|
|
31
|
+
.map((locale) => ({
|
|
32
|
+
locale,
|
|
33
|
+
label: tConfigField(resolveLanguage(locale).label, config),
|
|
34
|
+
active: locale === currentLocale,
|
|
35
|
+
href: localizePath(
|
|
36
|
+
locale,
|
|
37
|
+
hasLocale ? currentPath.slice(currentLocale.length + 1) : currentPath,
|
|
38
|
+
),
|
|
39
|
+
}))
|
|
40
|
+
.sort((a, b) => a.label.localeCompare(b.label, currentLocale))
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
currentPath,
|
|
44
|
+
hasLocale,
|
|
45
|
+
links,
|
|
46
|
+
}
|
|
47
|
+
}
|