lightnet 4.6.3 → 4.7.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 +16 -0
- package/package.json +4 -2
- package/src/api/robots.ts +11 -0
- package/src/astro-integration/integration.ts +15 -1
- package/src/astro-integration/sitemap.ts +50 -0
- package/src/i18n/record-translation.ts +14 -0
- package/src/i18n/translate.ts +13 -37
- package/src/i18n/translations.ts +40 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# lightnet
|
|
2
2
|
|
|
3
|
+
## 4.7.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#469](https://github.com/LightNetDev/LightNet/pull/469) [`a0961c4`](https://github.com/LightNetDev/LightNet/commit/a0961c40fdc30a93d3d5c814343f9c12ec26b2ef) - Warn about user-defined ln.* translation overrides that no longer match a built-in English key. Include source filenames and check unused keys without failing check-translations for stale overrides alone.
|
|
8
|
+
|
|
9
|
+
- [#469](https://github.com/LightNetDev/LightNet/pull/469) [`a0961c4`](https://github.com/LightNetDev/LightNet/commit/a0961c40fdc30a93d3d5c814343f9c12ec26b2ef) - Only record translation keys requested during the build, so check-translations no longer reports missing translations for unused built-in or user keys.
|
|
10
|
+
|
|
11
|
+
## 4.7.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- [#465](https://github.com/LightNetDev/LightNet/pull/465) [`24f5b61`](https://github.com/LightNetDev/LightNet/commit/24f5b614c50ef355f595644b9b0763e00a2210cd) - Generate XML sitemaps automatically using Astro's sitemap integration and LightNet's site languages. Respect an explicitly configured `@astrojs/sitemap` integration for custom sitemap behavior.
|
|
16
|
+
|
|
17
|
+
- [#465](https://github.com/LightNetDev/LightNet/pull/465) [`24f5b61`](https://github.com/LightNetDev/LightNet/commit/24f5b614c50ef355f595644b9b0763e00a2210cd) - Automatically generate robots.txt allowing crawling and linking to the sitemap index using Astro's site and base configuration. Preserve user-provided robots.txt files in the public directory and skip generation when search indexing is disabled.
|
|
18
|
+
|
|
3
19
|
## 4.6.3
|
|
4
20
|
|
|
5
21
|
### Patch 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.7.1",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/LightNetDev/lightnet",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"./pages/SearchPageRoute.astro": "./src/pages/search-page/SearchPageRoute.astro",
|
|
43
43
|
"./pages/DetailsPageRoute.astro": "./src/pages/details-page/DetailsPageRoute.astro",
|
|
44
44
|
"./api/internal/search.ts": "./src/pages/search-page/api/search.ts",
|
|
45
|
-
"./api/versions.ts": "./src/api/versions.ts"
|
|
45
|
+
"./api/versions.ts": "./src/api/versions.ts",
|
|
46
|
+
"./api/robots.ts": "./src/api/robots.ts"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
|
48
49
|
"astro": "^6.0.0",
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
},
|
|
53
54
|
"dependencies": {
|
|
54
55
|
"@astrojs/react": "^5.0.7",
|
|
56
|
+
"@astrojs/sitemap": "^3.7.4",
|
|
55
57
|
"@iconify-json/lucide": "^1.2.145",
|
|
56
58
|
"@iconify-json/mdi": "^1.2.3",
|
|
57
59
|
"@iconify/tailwind": "^1.2.0",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { APIRoute } from "astro"
|
|
2
|
+
|
|
3
|
+
export const GET: APIRoute = ({ site }) => {
|
|
4
|
+
const base = new URL(import.meta.env.BASE_URL, site)
|
|
5
|
+
if (!base.pathname.endsWith("/")) base.pathname += "/"
|
|
6
|
+
const sitemap = new URL("sitemap-index.xml", base)
|
|
7
|
+
|
|
8
|
+
return new Response(`User-agent: *\nAllow: /\n\nSitemap: ${sitemap.href}\n`, {
|
|
9
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
10
|
+
})
|
|
11
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/// <reference path="../i18n/locals.d.ts" />
|
|
2
|
+
import { existsSync } from "node:fs"
|
|
3
|
+
|
|
2
4
|
import react from "@astrojs/react"
|
|
3
5
|
import type { AstroIntegration } from "astro"
|
|
4
6
|
import { AstroError } from "astro/errors"
|
|
5
7
|
|
|
6
8
|
import { verifySchema } from "../utils/verify-schema"
|
|
7
9
|
import { extendedConfigSchema, type LightnetConfig } from "./config"
|
|
10
|
+
import { sitemap } from "./sitemap"
|
|
8
11
|
import tailwind from "./tailwind"
|
|
9
12
|
import { vitePluginLightnetConfig } from "./vite-plugin-lightnet-config"
|
|
10
13
|
|
|
@@ -33,6 +36,17 @@ export function lightnet(lightnetConfig: LightnetConfig): AstroIntegration {
|
|
|
33
36
|
"Fix these errors on the LightNet configuration:",
|
|
34
37
|
)
|
|
35
38
|
|
|
39
|
+
if (
|
|
40
|
+
!config.disallowSearchIndexing &&
|
|
41
|
+
!existsSync(new URL("robots.txt", astroConfig.publicDir))
|
|
42
|
+
) {
|
|
43
|
+
injectRoute({
|
|
44
|
+
pattern: "/robots.txt",
|
|
45
|
+
entrypoint: "lightnet/api/robots.ts",
|
|
46
|
+
prerender: true,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
36
50
|
injectRoute({
|
|
37
51
|
pattern: "404",
|
|
38
52
|
entrypoint: "lightnet/pages/404Route.astro",
|
|
@@ -72,7 +86,7 @@ export function lightnet(lightnetConfig: LightnetConfig): AstroIntegration {
|
|
|
72
86
|
addMiddleware({ entrypoint: "lightnet/locals", order: "pre" })
|
|
73
87
|
|
|
74
88
|
updateConfig({
|
|
75
|
-
integrations: [tailwind(), react()],
|
|
89
|
+
integrations: [tailwind(), react(), ...sitemap(config, astroConfig)],
|
|
76
90
|
vite: {
|
|
77
91
|
plugins: [vitePluginLightnetConfig(config, astroConfig, logger)],
|
|
78
92
|
},
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import astroSitemap from "@astrojs/sitemap"
|
|
2
|
+
import type { AstroConfig, AstroIntegration } from "astro"
|
|
3
|
+
|
|
4
|
+
import type { ExtendedLightnetConfig } from "./config"
|
|
5
|
+
|
|
6
|
+
export const sitemap = (
|
|
7
|
+
lightnetConfig: ExtendedLightnetConfig,
|
|
8
|
+
astroConfig: AstroConfig,
|
|
9
|
+
): AstroIntegration[] => {
|
|
10
|
+
if (
|
|
11
|
+
astroConfig.integrations.some(({ name }) => name === "@astrojs/sitemap")
|
|
12
|
+
) {
|
|
13
|
+
return []
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (lightnetConfig.disallowSearchIndexing) {
|
|
17
|
+
return []
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let adminRoutePatterns: RegExp[] = []
|
|
21
|
+
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
name: "lightnet/sitemap-exclusions",
|
|
25
|
+
hooks: {
|
|
26
|
+
"astro:routes:resolved": ({ routes }) => {
|
|
27
|
+
// Astro resolves the package entrypoint to its source file, including
|
|
28
|
+
// workspace paths when the admin package is linked locally.
|
|
29
|
+
adminRoutePatterns = routes
|
|
30
|
+
.filter(({ entrypoint }) =>
|
|
31
|
+
/(?:^|\/)sveltia-admin\/src\/Admin\.astro$/.test(entrypoint),
|
|
32
|
+
)
|
|
33
|
+
.map(({ patternRegex }) => patternRegex)
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
astroSitemap({
|
|
38
|
+
filter: (page) =>
|
|
39
|
+
!adminRoutePatterns.some((pattern) =>
|
|
40
|
+
pattern.test(new URL(page).pathname),
|
|
41
|
+
),
|
|
42
|
+
i18n: {
|
|
43
|
+
defaultLocale: lightnetConfig.defaultLocale,
|
|
44
|
+
locales: Object.fromEntries(
|
|
45
|
+
lightnetConfig.locales.map((locale) => [locale, locale]),
|
|
46
|
+
),
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -25,6 +25,20 @@ const recordedTranslations = new Set<string>()
|
|
|
25
25
|
|
|
26
26
|
const translationStore = lazy(() => createTranslationStore())
|
|
27
27
|
|
|
28
|
+
export async function writeStaleTranslationOverrides(
|
|
29
|
+
overrides: { key: string; path: string }[],
|
|
30
|
+
) {
|
|
31
|
+
if (!import.meta.env.PROD) {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
await mkdir(lightnetCachePath, { recursive: true })
|
|
35
|
+
await writeFile(
|
|
36
|
+
resolve(lightnetCachePath, "stale-translation-overrides.json"),
|
|
37
|
+
JSON.stringify(overrides),
|
|
38
|
+
"utf-8",
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
const writeLanguagesManifest = async () => {
|
|
29
43
|
const manifestPath = resolve(lightnetCachePath, "languages.json")
|
|
30
44
|
const { defaultLocale, locales } = config
|
package/src/i18n/translate.ts
CHANGED
|
@@ -68,7 +68,6 @@ export async function useTranslate(
|
|
|
68
68
|
const resolvedLocale = bcp47 ?? config.defaultLocale
|
|
69
69
|
const translations = await i18nextTranslations.get()
|
|
70
70
|
const availableTranslationKeys = new Set(await translationKeys.get())
|
|
71
|
-
recordAllTranslations()
|
|
72
71
|
|
|
73
72
|
const i18n = i18next.createInstance()
|
|
74
73
|
|
|
@@ -87,6 +86,19 @@ export async function useTranslate(
|
|
|
87
86
|
"en",
|
|
88
87
|
]
|
|
89
88
|
return (input, options) => {
|
|
89
|
+
if (import.meta.env.PROD) {
|
|
90
|
+
recordTranslation({
|
|
91
|
+
key: input,
|
|
92
|
+
type: input.startsWith("ln.") ? "lightnet" : "user",
|
|
93
|
+
values: Object.fromEntries(
|
|
94
|
+
config.locales.map((locale) => [
|
|
95
|
+
locale,
|
|
96
|
+
translations[locale]?.translation[input],
|
|
97
|
+
]),
|
|
98
|
+
),
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
const t = i18n.getFixedT(resolvedLocale) as (
|
|
91
103
|
key: TranslationKey,
|
|
92
104
|
options?: TOptions,
|
|
@@ -101,39 +113,3 @@ export async function useTranslate(
|
|
|
101
113
|
return value
|
|
102
114
|
}
|
|
103
115
|
}
|
|
104
|
-
|
|
105
|
-
async function recordAllTranslations() {
|
|
106
|
-
if (!import.meta.env.PROD) {
|
|
107
|
-
// only record translations during build time
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const { locales } = config
|
|
112
|
-
const localesIncludingEnglish = [...locales.filter((l) => l !== "en"), "en"]
|
|
113
|
-
const keys = await translationKeys.get()
|
|
114
|
-
const translations = await i18nextTranslations.get()
|
|
115
|
-
|
|
116
|
-
const collectValues = (key: string, locales: string[]) => {
|
|
117
|
-
const values = {} as Record<string, string | undefined>
|
|
118
|
-
for (const locale of locales) {
|
|
119
|
-
values[locale] = translations[locale]?.translation[key]
|
|
120
|
-
}
|
|
121
|
-
return values
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
for (const key of keys) {
|
|
125
|
-
if (key.startsWith("ln.")) {
|
|
126
|
-
recordTranslation({
|
|
127
|
-
key,
|
|
128
|
-
values: collectValues(key, localesIncludingEnglish),
|
|
129
|
-
type: "lightnet",
|
|
130
|
-
})
|
|
131
|
-
} else {
|
|
132
|
-
recordTranslation({
|
|
133
|
-
key,
|
|
134
|
-
values: collectValues(key, locales),
|
|
135
|
-
type: "user",
|
|
136
|
-
})
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
package/src/i18n/translations.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { z } from "astro/zod"
|
|
2
2
|
import YAML from "yaml"
|
|
3
3
|
|
|
4
|
+
import { lazy } from "../utils/lazy"
|
|
5
|
+
import { writeStaleTranslationOverrides } from "./record-translation"
|
|
6
|
+
|
|
4
7
|
const builtInTranslations = {
|
|
5
8
|
ar: () => import("./translations/ar.yml?raw"),
|
|
6
9
|
bn: () => import("./translations/bn.yml?raw"),
|
|
@@ -22,24 +25,52 @@ type BuiltInLanguage = keyof typeof builtInTranslations
|
|
|
22
25
|
|
|
23
26
|
const translationFileSchema = z.record(z.string(), z.string())
|
|
24
27
|
|
|
28
|
+
const userTranslationFiles = import.meta.glob<string>(
|
|
29
|
+
["/src/translations/*.(yml|yaml)"],
|
|
30
|
+
{ query: "?raw", import: "default" },
|
|
31
|
+
)
|
|
32
|
+
|
|
25
33
|
const userTranslations = Object.fromEntries(
|
|
26
|
-
Object.entries(
|
|
27
|
-
import.meta.glob(["/src/translations/*.(yml|yaml)"], {
|
|
28
|
-
query: "?raw",
|
|
29
|
-
import: "default",
|
|
30
|
-
}),
|
|
31
|
-
).map(([path, translationImport]) => {
|
|
34
|
+
Object.entries(userTranslationFiles).map(([path, translationImport]) => {
|
|
32
35
|
const [fileName] = path.split("/").slice(-1)
|
|
33
36
|
const lang = fileName.replace(/\.ya?ml/, "")
|
|
34
37
|
return [lang, translationImport]
|
|
35
38
|
}),
|
|
36
39
|
)
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
const staleOverrides = lazy(async () => {
|
|
42
|
+
const english = await loadBuiltInTranslations(builtInTranslations, "en")
|
|
43
|
+
await writeStaleTranslationOverrides(
|
|
44
|
+
await findStaleTranslationOverrides(english, userTranslationFiles),
|
|
45
|
+
)
|
|
41
46
|
})
|
|
42
47
|
|
|
48
|
+
export async function findStaleTranslationOverrides(
|
|
49
|
+
english: Record<string, string>,
|
|
50
|
+
files: Record<string, () => Promise<string>>,
|
|
51
|
+
) {
|
|
52
|
+
const overrides: { key: string; path: string }[] = []
|
|
53
|
+
for (const [path, load] of Object.entries(files)) {
|
|
54
|
+
const translations = translationFileSchema.parse(YAML.parse(await load()))
|
|
55
|
+
for (const key of Object.keys(translations)) {
|
|
56
|
+
if (key.startsWith("ln.") && !Object.hasOwn(english, key)) {
|
|
57
|
+
overrides.push({ key, path: path.replace(/^\//, "") })
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return overrides
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const loadTranslations = async (bcp47: string) => {
|
|
65
|
+
if (import.meta.env.PROD) {
|
|
66
|
+
await staleOverrides.get()
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...(await loadBuiltInTranslations(builtInTranslations, bcp47)),
|
|
70
|
+
...(await loadUserTranslations(bcp47)),
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
43
74
|
function hasTranslations(
|
|
44
75
|
translationMap: Record<string, unknown>,
|
|
45
76
|
bcp47: string,
|