SHA256
Vorbereitung mit install.sh
This commit is contained in:
+45
-7
@@ -36,7 +36,20 @@ public static class Builder
|
||||
Directory.CreateDirectory(Directories.DistDir);
|
||||
CopyAssets(verbose);
|
||||
|
||||
var langLinksHtml = BuildLangLinksHtml();
|
||||
// Precompute, for every page, its slug per language (keyed by order|section),
|
||||
// so the language switcher can link to the equivalent page in each language
|
||||
// even when slugs differ between translations.
|
||||
var pageSlugs = new Dictionary<string, Dictionary<string, string>>();
|
||||
foreach (var l in cfg.LanguageCodes())
|
||||
{
|
||||
foreach (var p in ContentLoader.LoadContent(Directories.ContentDir, l))
|
||||
{
|
||||
var key = $"{p.Meta.Order ?? -1}|{p.Meta.Section ?? ""}";
|
||||
if (!pageSlugs.TryGetValue(key, out var m))
|
||||
pageSlugs[key] = m = new Dictionary<string, string>();
|
||||
m[l] = p.Href;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var lang in cfg.LanguageCodes())
|
||||
{
|
||||
@@ -90,6 +103,17 @@ public static class Builder
|
||||
var sidebarTocHtml = BuildSidebarTocHtml(page.Content);
|
||||
var pageNavHtml = (page.Meta.Order == 1) ? "" : BuildPageNavHtml(contents, page, lang);
|
||||
|
||||
var introHtml = string.IsNullOrEmpty(page.Meta.Intro)
|
||||
? "" : MarkdownProcessor.RenderMarkdown(page.Meta.Intro);
|
||||
var positioningHtml = string.IsNullOrEmpty(page.Meta.Positioning)
|
||||
? "" : MarkdownProcessor.RenderMarkdown(page.Meta.Positioning);
|
||||
var publisherHtml = string.IsNullOrEmpty(page.Meta.Publisher)
|
||||
? "" : MarkdownProcessor.RenderMarkdown(page.Meta.Publisher);
|
||||
|
||||
var langKey = $"{page.Meta.Order ?? -1}|{page.Meta.Section ?? ""}";
|
||||
var langLinksHtml = BuildLangLinksHtml(
|
||||
pageSlugs.TryGetValue(langKey, out var m) ? m : null);
|
||||
|
||||
var pageHtml = layout
|
||||
.Replace("{{seo_head}}", seoHead).Replace("{{ seo_head }}", seoHead)
|
||||
.Replace("{{ json_ld }}", "").Replace("{{json_ld}}", "")
|
||||
@@ -102,6 +126,9 @@ public static class Builder
|
||||
.Replace("{{lang_links}}", langLinksHtml).Replace("{{ lang_links }}", langLinksHtml)
|
||||
.Replace("{{glossary_href}}", GlossaryHref(lang)).Replace("{{ glossary_href }}", GlossaryHref(lang))
|
||||
.Replace("{{glossary_label}}", GlossaryLabel(lang)).Replace("{{ glossary_label }}", GlossaryLabel(lang))
|
||||
.Replace("{{intro}}", introHtml).Replace("{{ intro }}", introHtml)
|
||||
.Replace("{{positioning}}", positioningHtml).Replace("{{ positioning }}", positioningHtml)
|
||||
.Replace("{{publisher}}", publisherHtml).Replace("{{ publisher }}", publisherHtml)
|
||||
.Replace("{{content}}", fullBody).Replace("{{ content }}", fullBody)
|
||||
.Replace("{{body}}", fullBody).Replace("{{ body }}", fullBody);
|
||||
|
||||
@@ -188,7 +215,13 @@ public static class Builder
|
||||
File.AppendAllText(Directories.BuildLogPath, $" Assets copied to: {dstDir}\n");
|
||||
}
|
||||
|
||||
private static string BuildLangLinksHtml()
|
||||
/// <summary>
|
||||
/// Build the language-switch dropdown for the current page. <paramref name="langSlug"/>
|
||||
/// maps each language code to the current page's slug in that language (resolved by
|
||||
/// shared identity — order + section — so each language links to its own equivalent
|
||||
/// page). When null, all links point to the language root.
|
||||
/// </summary>
|
||||
private static string BuildLangLinksHtml(Dictionary<string, string>? langSlug)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var kv in Directories.Config.Languages)
|
||||
@@ -197,7 +230,10 @@ public static class Builder
|
||||
var li = kv.Value;
|
||||
var label = li.Label ?? li.Name;
|
||||
var code = (li.HrefLang ?? lang).ToUpperInvariant();
|
||||
sb.AppendLine($" <li><a href=\"/{lang}/\" hreflang=\"{li.HrefLang ?? lang}\"><span>{HtmlEncode(label)}</span> <span class=\"lang-dropdown__code\">{code}</span></a></li>");
|
||||
string slug = "";
|
||||
if (langSlug != null && langSlug.TryGetValue(lang, out var h)) slug = h.Trim('/');
|
||||
var href = string.IsNullOrEmpty(slug) ? $"/{lang}/" : $"/{lang}/{slug}/";
|
||||
sb.AppendLine($" <li><a href=\"{href}\" hreflang=\"{li.HrefLang ?? lang}\"><span>{HtmlEncode(label)}</span> <span class=\"lang-dropdown__code\">{code}</span></a></li>");
|
||||
}
|
||||
return sb.ToString().Trim();
|
||||
}
|
||||
@@ -227,7 +263,7 @@ public static class Builder
|
||||
.OrderBy(p => p.Meta.Order ?? 999).ToList();
|
||||
var heroPage = contents.FirstOrDefault(p => p.Meta.Order == 1);
|
||||
if (heroPage != null)
|
||||
sb.AppendLine(" <li><a href=\"index.html\"><span class=\"nav-idx\">★</span> Startseite</a></li>");
|
||||
sb.AppendLine($" <li><a href=\"index.html\"><span class=\"nav-idx\">★</span> {I18n.Get(lang, "nav.home")}</a></li>");
|
||||
|
||||
foreach (var page in navPages)
|
||||
{
|
||||
@@ -285,7 +321,7 @@ public static class Builder
|
||||
var prevLabel = (prev.Meta.Eyebrow ?? prev.Meta.LocalizedTitle(lang, prev.Meta.Title ?? ""));
|
||||
if (prevLabel.Contains(" — ")) prevLabel = prevLabel.Substring(prevLabel.IndexOf(" — ") + 3);
|
||||
sb.AppendLine($" <a class=\"page-nav__link\" href=\"{prev.Href.Trim('/')}.html\">");
|
||||
sb.AppendLine($" <span class=\"page-nav__label\">← Vorherige</span>");
|
||||
sb.AppendLine($" <span class=\"page-nav__label\">{I18n.Get(lang, "nav.prev")}</span>");
|
||||
sb.AppendLine($" <span class=\"page-nav__title\">{HtmlEncode(prevLabel)}</span>");
|
||||
sb.AppendLine(" </a>");
|
||||
}
|
||||
@@ -297,7 +333,7 @@ public static class Builder
|
||||
var nextLabel = (next.Meta.Eyebrow ?? next.Meta.LocalizedTitle(lang, next.Meta.Title ?? ""));
|
||||
if (nextLabel.Contains(" — ")) nextLabel = nextLabel.Substring(nextLabel.IndexOf(" — ") + 3);
|
||||
sb.AppendLine($" <a class=\"page-nav__link page-nav__link--next\" href=\"{next.Href.Trim('/')}.html\">");
|
||||
sb.AppendLine($" <span class=\"page-nav__label\">Nächste →</span>");
|
||||
sb.AppendLine($" <span class=\"page-nav__label\">{I18n.Get(lang, "nav.next")}</span>");
|
||||
sb.AppendLine($" <span class=\"page-nav__title\">{HtmlEncode(nextLabel)}</span>");
|
||||
sb.AppendLine(" </a>");
|
||||
}
|
||||
@@ -324,7 +360,6 @@ public static class Builder
|
||||
var legalDir = Directories.LegalDir;
|
||||
if (!Directory.Exists(legalDir)) return;
|
||||
var layout = LoadLayout();
|
||||
var langLinksHtml = BuildLangLinksHtml();
|
||||
|
||||
foreach (var lang in cfg.LanguageCodes())
|
||||
{
|
||||
@@ -358,6 +393,9 @@ public static class Builder
|
||||
var seoHead = SeoHelper.RenderSeoHead(lang, title, "", $"{env.BaseUrl}{canonical}",
|
||||
env.Cdn, defaultTitle, defaultDesc);
|
||||
var sidebarTocHtml = BuildSidebarTocHtml(content);
|
||||
var legalSlug = $"/{Path.GetFileNameWithoutExtension(baseName)}/";
|
||||
var legalLangSlug = cfg.LanguageCodes().ToDictionary(l => l, l => legalSlug);
|
||||
var langLinksHtml = BuildLangLinksHtml(legalLangSlug);
|
||||
|
||||
var pageHtml = layout
|
||||
.Replace("{{seo_head}}", seoHead).Replace("{{ seo_head }}", seoHead)
|
||||
|
||||
+35
-26
@@ -7,49 +7,58 @@ namespace GCBoK.WebStaticBuilder;
|
||||
/// <summary>Content loading and YAML frontmatter parsing.</summary>
|
||||
public static class ContentLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Load all content files for a language. Files are grouped by their base name
|
||||
/// (language suffix stripped). The requested language is preferred; if it is
|
||||
/// missing, the German (<c>.de.md</c>) variant is used as fallback, and finally
|
||||
/// an un-suffixed default. This makes German the canonical default content
|
||||
/// source, derived explicitly from <c>.de.md</c> rather than an ambiguous
|
||||
/// suffixless file.
|
||||
/// </summary>
|
||||
public static List<PageInfo> LoadContent(string contentDir, string lang)
|
||||
{
|
||||
var results = new List<PageInfo>();
|
||||
var byBase = new Dictionary<string, Dictionary<string, string>>();
|
||||
|
||||
foreach (var filepath in Directory.GetFiles(contentDir, "*.md", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
var nameWithoutExt = Path.GetFileNameWithoutExtension(filepath);
|
||||
|
||||
string? fileLang = null;
|
||||
if (nameWithoutExt.Contains("."))
|
||||
if (nameWithoutExt.Contains('.'))
|
||||
{
|
||||
var parts = nameWithoutExt.Split('.');
|
||||
if (parts.Length >= 2)
|
||||
{
|
||||
var lastPart = parts[^1].ToLowerInvariant();
|
||||
if (lastPart == "de" || lastPart == "en" || lastPart == "vi")
|
||||
{
|
||||
fileLang = lastPart;
|
||||
if (fileLang != lang) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
var lastPart = nameWithoutExt.Split('.')[^1].ToLowerInvariant();
|
||||
if (lastPart is "de" or "en" or "vi")
|
||||
fileLang = lastPart;
|
||||
else
|
||||
continue;
|
||||
}
|
||||
|
||||
var text = File.ReadAllText(filepath);
|
||||
var baseName = fileLang == null
|
||||
? nameWithoutExt
|
||||
: nameWithoutExt.Substring(0, nameWithoutExt.Length - fileLang.Length - 1);
|
||||
|
||||
if (!byBase.TryGetValue(baseName, out var map))
|
||||
byBase[baseName] = map = new Dictionary<string, string>();
|
||||
map[fileLang ?? ""] = filepath;
|
||||
}
|
||||
|
||||
var results = new List<PageInfo>();
|
||||
foreach (var map in byBase.Values)
|
||||
{
|
||||
string? chosen = null;
|
||||
if (map.TryGetValue(lang, out var langPath)) chosen = langPath;
|
||||
else if (map.TryGetValue("de", out var dePath)) chosen = dePath;
|
||||
else if (map.TryGetValue("", out var defPath)) chosen = defPath;
|
||||
if (chosen == null) continue;
|
||||
|
||||
var text = File.ReadAllText(chosen);
|
||||
var (meta, content) = ParseFrontmatter(text);
|
||||
|
||||
var langs = meta.Langs ?? "";
|
||||
if (!string.IsNullOrEmpty(langs) && !langs.Split(',').Select(l => l.Trim()).Contains(lang))
|
||||
continue;
|
||||
|
||||
if (fileLang == null && lang != "de")
|
||||
{
|
||||
var baseName = nameWithoutExt;
|
||||
var langSpecificPath = Path.Combine(contentDir, $"{baseName}.{lang}.md");
|
||||
if (File.Exists(langSpecificPath))
|
||||
continue;
|
||||
}
|
||||
|
||||
results.Add(new PageInfo(filepath, meta, GetHref(meta), content));
|
||||
results.Add(new PageInfo(chosen, meta, GetHref(meta), content));
|
||||
}
|
||||
|
||||
return results.OrderBy(x => x.Meta.Order ?? 999).ToList();
|
||||
|
||||
+5
-4
@@ -73,10 +73,11 @@ if (argsList.Contains("init"))
|
||||
}
|
||||
|
||||
// Build
|
||||
var envName = "work";
|
||||
if (Has("--preview")) envName = "work";
|
||||
if (Has("--work")) envName = "work";
|
||||
if (Has("--review")) envName = "review";
|
||||
// "development" folds work, preview and review into a single local/dev environment.
|
||||
var envName = "development";
|
||||
if (Has("--preview")) envName = "development";
|
||||
if (Has("--work")) envName = "development";
|
||||
if (Has("--review")) envName = "development";
|
||||
if (Has("--staging")) envName = "staging";
|
||||
if (Has("--deploy")) envName = "deploy";
|
||||
if (Val("--env") is { } ev) envName = ev;
|
||||
|
||||
Reference in New Issue
Block a user