diff --git a/scripts/sync-blog-posts.mjs b/scripts/sync-blog-posts.mjs
index a6153a9..a01d6d4 100644
--- a/scripts/sync-blog-posts.mjs
+++ b/scripts/sync-blog-posts.mjs
@@ -280,6 +280,8 @@ for (const path of articleFiles) {
date: editorialDate,
tags: data.tags || [],
status: data.status || 'draft',
+ lang: data.lang === 'en' ? 'en' : 'id',
+ translationKey: data.translationKey || '',
thumbnail: resolveThumbnail(data.thumbnail || data.image || data.cover || '', path, defaultBranch),
content,
sourceUrl: file.html_url,
diff --git a/src/components/BlogArticlePage.astro b/src/components/BlogArticlePage.astro
new file mode 100644
index 0000000..2537e4f
--- /dev/null
+++ b/src/components/BlogArticlePage.astro
@@ -0,0 +1,154 @@
+---
+import BaseLayout from '../layouts/BaseLayout.astro';
+import PageHeader from './PageHeader.astro';
+import { withBase, safeHref } from '../lib/urls';
+import { articleHref, articleLanguage, isPublished, renderArticle, type BlogPost } from '../lib/content';
+
+interface Props {
+ post: BlogPost;
+ translations: BlogPost[];
+}
+
+const { post, translations } = Astro.props;
+const language = articleLanguage(post);
+const english = language === 'en';
+const locale = english ? 'en-US' : 'id-ID';
+const published = isPublished(post);
+const articlePath = articleHref(post).replace(/\/$/, '');
+const html = renderArticle(post.content);
+const authors = post.authors?.length ? post.authors : post.author ? [post.author] : [];
+const authorDate = post.author?.committedAt
+ ? new Intl.DateTimeFormat(locale, { dateStyle: 'long' }).format(new Date(post.author.committedAt))
+ : '';
+const modifiedDate = post.lastModifiedAt
+ ? new Intl.DateTimeFormat(locale, { dateStyle: 'long' }).format(new Date(post.lastModifiedAt))
+ : '';
+const latestCommitUrl = safeHref(post.latestCommitUrl);
+const seoTitle = post.slug === 'bagaimana-open-source-mampu-bertahan'
+ ? 'Mengapa Open Source Bertahan Puluhan Tahun - IndopenSource'
+ : `${post.title} - IndopenSource`;
+const releaseDate = post.releasedAt
+ ? new Intl.DateTimeFormat(locale, { dateStyle: 'long', timeZone: 'UTC' }).format(new Date(post.releasedAt))
+ : '';
+const thumbnail = post.thumbnail || '/brand/indopensource-hero.jpg';
+const thumbnailUrl = /^https?:\/\//.test(thumbnail) ? thumbnail : withBase(thumbnail);
+const heroAlt = english ? `Article thumbnail: ${post.title}` : `Thumbnail artikel: ${post.title}`;
+const authorByline = post.authorFromFrontmatter
+ ? english
+ ? `Named in the article frontmatter${authorDate ? ` (first commit ${authorDate})` : ''}.`
+ : `Disebutkan di frontmatter artikel${authorDate ? ` (commit pertama ${authorDate})` : ''}.`
+ : english
+ ? `Contributors are derived from the Markdown source history${authorDate ? published ? `. Published ${authorDate}` : `. First commit ${authorDate}` : ''}${modifiedDate ? ` · updated ${modifiedDate}` : ''}.`
+ : `Kontributor diambil dari riwayat commit sumber Markdown${authorDate ? published ? `. Terbit ${authorDate}` : `. Commit pertama ${authorDate}` : ''}${modifiedDate ? ` · diperbarui ${modifiedDate}` : ''}.`;
+const alternates = [post, ...translations].map((translation) => ({
+ lang: articleLanguage(translation),
+ href: articleHref(translation)
+}));
+
+const jsonLd = {
+ '@context': 'https://schema.org',
+ '@type': 'BlogPosting',
+ headline: post.title,
+ description: post.description,
+ image: thumbnailUrl,
+ inLanguage: locale,
+ ...(published && post.releasedAt ? { datePublished: post.releasedAt } : {}),
+ ...(post.lastModifiedAt ? { dateModified: post.lastModifiedAt } : {}),
+ ...(authors.length
+ ? { author: authors.map((author) => ({ '@type': 'Person', name: author.name, ...(safeHref(author.url) ? { url: safeHref(author.url) } : {}) })) }
+ : {}),
+ publisher: { '@type': 'Organization', name: 'IndopenSource' }
+};
+---
+
+ {post.description}
+
- Ruang kerja terbuka untuk ekosistem open source Indonesia, dibikin - bareng, dirawat bareng. + {english + ? 'An open workspace for Indonesia’s open source ecosystem, built and maintained together.' + : 'Ruang kerja terbuka untuk ekosistem open source Indonesia, dibikin bareng, dirawat bareng.'}
Open source · Indonesia @@ -64,27 +87,27 @@ const year = new Date().getFullYear();