mirror of
https://github.com/Ocelot-Social-Community/ocelot.social.git
synced 2026-04-04 16:45:17 +00:00
Merge branch 'master' into blog-3-14-1
This commit is contained in:
commit
9b10ad9d4f
41
.github/workflows/check-english-articles.yml
vendored
Normal file
41
.github/workflows/check-english-articles.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Check English Article Availability
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
check-english-articles:
|
||||
name: Every article must have an English version
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check for missing English articles
|
||||
run: |
|
||||
missing=0
|
||||
|
||||
for locale_dir in docs/de/news docs/es/news docs/fr/news; do
|
||||
[ -d "$locale_dir" ] || continue
|
||||
locale=$(echo "$locale_dir" | cut -d/ -f2)
|
||||
|
||||
for slug_dir in "$locale_dir"/*/; do
|
||||
[ -d "$slug_dir" ] || continue
|
||||
slug=$(basename "$slug_dir")
|
||||
en_file="docs/en/news/$slug/README.md"
|
||||
|
||||
if [ ! -f "$en_file" ]; then
|
||||
echo "::error::Missing English article: $en_file (exists in $locale)"
|
||||
missing=$((missing + 1))
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
if [ "$missing" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "Found $missing article(s) without an English version."
|
||||
echo "Every article must have an English translation in docs/en/news/."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All articles have an English version."
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@
|
||||
/docs/.vuepress/.cache/
|
||||
/docs/.vuepress/.temp/
|
||||
/.github/webhooks/hooks.json
|
||||
**/README.stub.md
|
||||
|
||||
@ -74,11 +74,26 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
// Nur Artikel des aktuellen Locales + Top X
|
||||
const FALLBACK_LOCALE = '/en/';
|
||||
|
||||
// Slug = path without locale prefix (e.g. "news/2025-07-05-release/")
|
||||
const slugOf = (a) => a.path.replace(a.locale, '');
|
||||
|
||||
// Articles for current locale + EN fallback for missing ones
|
||||
const items = computed(() => {
|
||||
const loc = locale.value || "/";
|
||||
const list = (articles || []).filter(a => a.locale === loc);
|
||||
return list.slice(0, props.topArticlesCount);
|
||||
const all = articles || [];
|
||||
|
||||
const localeArticles = all.filter(a => a.locale === loc);
|
||||
const localeSlugs = new Set(localeArticles.map(slugOf));
|
||||
|
||||
const fallbacks = loc === FALLBACK_LOCALE
|
||||
? []
|
||||
: all.filter(a => a.locale === FALLBACK_LOCALE && !localeSlugs.has(slugOf(a)));
|
||||
|
||||
return [...localeArticles, ...fallbacks]
|
||||
.sort((a, b) => (Date.parse(b.date) || 0) - (Date.parse(a.date) || 0))
|
||||
.slice(0, props.topArticlesCount);
|
||||
});
|
||||
|
||||
const articleIndex = computed(() =>
|
||||
|
||||
@ -1,11 +1,78 @@
|
||||
import { getDirname, path } from "vuepress/utils"
|
||||
import { defineUserConfig } from 'vuepress'
|
||||
import { viteBundler } from '@vuepress/bundler-vite'
|
||||
import fs from 'node:fs'
|
||||
|
||||
import meta from './config/meta'
|
||||
import theme from './config/theme'
|
||||
|
||||
const __dirname = getDirname(import.meta.url)
|
||||
const docsDir = path.resolve(__dirname, '..')
|
||||
|
||||
const FALLBACK_LOCALE = 'en'
|
||||
const OTHER_LOCALES = ['de', 'es', 'fr']
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Before VuePress starts: for EN articles missing in other locales, create
|
||||
// README.stub.md files with a permalink so VuePress treats them as pages.
|
||||
// These .stub.md files are gitignored.
|
||||
// ---------------------------------------------------------------------------
|
||||
;(() => {
|
||||
// Clean up old stubs first
|
||||
for (const locale of OTHER_LOCALES) {
|
||||
const localeNewsDir = path.resolve(docsDir, locale, 'news')
|
||||
if (!fs.existsSync(localeNewsDir)) continue
|
||||
for (const entry of fs.readdirSync(localeNewsDir, { withFileTypes: true })) {
|
||||
if (!entry.isDirectory()) continue
|
||||
const stubFile = path.resolve(localeNewsDir, entry.name, 'README.stub.md')
|
||||
if (fs.existsSync(stubFile)) {
|
||||
fs.unlinkSync(stubFile)
|
||||
// Remove directory if now empty
|
||||
const dir = path.resolve(localeNewsDir, entry.name)
|
||||
if (fs.readdirSync(dir).length === 0) fs.rmdirSync(dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const enNewsDir = path.resolve(docsDir, FALLBACK_LOCALE, 'news')
|
||||
if (!fs.existsSync(enNewsDir)) return
|
||||
|
||||
const enSlugs = fs.readdirSync(enNewsDir, { withFileTypes: true })
|
||||
.filter((d) => d.isDirectory())
|
||||
.map((d) => d.name)
|
||||
|
||||
for (const locale of OTHER_LOCALES) {
|
||||
const localeNewsDir = path.resolve(docsDir, locale, 'news')
|
||||
if (!fs.existsSync(localeNewsDir)) fs.mkdirSync(localeNewsDir, { recursive: true })
|
||||
|
||||
const existingSlugs = new Set(
|
||||
fs.readdirSync(localeNewsDir, { withFileTypes: true })
|
||||
.filter((d) => d.isDirectory())
|
||||
.map((d) => d.name)
|
||||
)
|
||||
|
||||
for (const slug of enSlugs) {
|
||||
if (existingSlugs.has(slug)) continue
|
||||
|
||||
const enFile = path.resolve(enNewsDir, slug, 'README.md')
|
||||
if (!fs.existsSync(enFile)) continue
|
||||
|
||||
const enContent = fs.readFileSync(enFile, 'utf-8')
|
||||
|
||||
// Inject permalink into frontmatter so VuePress maps the page
|
||||
// to the correct locale path (e.g. /fr/news/slug/)
|
||||
const permalink = `/${locale}/news/${slug}/`
|
||||
const stubContent = enContent.replace(
|
||||
/^---\n/,
|
||||
`---\npermalink: ${permalink}\n`
|
||||
)
|
||||
|
||||
const targetDir = path.resolve(localeNewsDir, slug)
|
||||
fs.mkdirSync(targetDir, { recursive: true })
|
||||
fs.writeFileSync(path.resolve(targetDir, 'README.stub.md'), stubContent)
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
export default defineUserConfig({
|
||||
...meta,
|
||||
|
||||
@ -250,6 +250,26 @@ export default hopeTheme({
|
||||
'/fr/': ['fr-FR', 'fr'],
|
||||
},
|
||||
localeFallback: false,
|
||||
|
||||
// Redirect to English for pages missing in other locales
|
||||
config: (app) => {
|
||||
const fallback = '/en/'
|
||||
const locales = ['/de/', '/es/', '/fr/']
|
||||
const pagePaths = new Set(app.pages.map((p) => p.path))
|
||||
const enPages = app.pages.filter((p) => p.path.startsWith(fallback)).map((p) => p.path)
|
||||
const redirects = {}
|
||||
|
||||
for (const locale of locales) {
|
||||
for (const enPath of enPages) {
|
||||
const localePath = enPath.replace(fallback, locale)
|
||||
if (!pagePaths.has(localePath)) {
|
||||
redirects[localePath] = enPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return redirects
|
||||
},
|
||||
},
|
||||
slimsearch: {
|
||||
indexContent: true,
|
||||
|
||||
@ -52,6 +52,14 @@ h1, h2, h3, h4 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vp-article-type-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.blog-page-wrapper {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
// 2 columns from desktop width for article overviews
|
||||
@media (min-width: 1024px) {
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user