Skip to content

Add multilingual blog support and publish OpenClaw Meetup #5#48

Merged
wauputr4 merged 3 commits into
mainfrom
codex/blog-multilingual
Jul 23, 2026
Merged

Add multilingual blog support and publish OpenClaw Meetup #5#48
wauputr4 merged 3 commits into
mainfrom
codex/blog-multilingual

Conversation

@wauputr4

@wauputr4 wauputr4 commented Jul 23, 2026

Copy link
Copy Markdown
Member

What changed

  • adds Indonesian-default and English blog routing
  • connects translated articles through translationKey
  • emits language-aware html lang, Open Graph locale, and hreflang metadata
  • adds an English blog index and language switcher
  • syncs and publishes the Indonesian and English OpenClaw Jakarta Meetup Plan GitHub Discussions forum integration #5 articles
  • includes both language routes in the sitemap

Why

IndopenSource articles previously had a single Indonesian-only route and metadata model. This adds a small bilingual foundation while preserving every existing Indonesian URL.

Validation

  • npm test — 22 passing
  • npm run build — 113 pages, 0 errors/warnings/hints
  • local preview returned HTTP 200 for both article routes
  • verified html lang, og:locale, reciprocal hreflang, language switch links, centered inline photo, and sitemap entries

Summary by CodeRabbit

  • New Features

    • Added English blog index and article pages.
    • Added Indonesian/English language switching and translated article links.
    • Added language-aware metadata, alternate-language links, and localized URLs.
    • Added two OpenClaw Jakarta Meetup #5 articles in Indonesian and English.
    • Added draft preview notices and improved article metadata, author details, images, and source links.
  • Bug Fixes

    • Updated sitemap entries to include localized blog URLs.
  • Tests

    • Added coverage for language routing, previews, and translation pairing.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@wauputr4, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d1d9fdeb-d6c9-40b7-9b97-133c2be96b0b

📥 Commits

Reviewing files that changed from the base of the PR and between 0593e3a and c17cfc9.

📒 Files selected for processing (8)
  • src/components/SiteFooter.astro
  • src/components/SiteHeader.astro
  • src/layouts/BaseLayout.astro
  • src/lib/content.ts
  • src/pages/blog.astro
  • src/pages/blog/[...slug].astro
  • src/pages/en/blog/[...slug].astro
  • test/lib.test.mjs
📝 Walkthrough

Walkthrough

The blog now supports Indonesian and English content, translation-linked articles, localized routes, language-aware SEO metadata, shared article rendering, and sitemap entries for both languages.

Changes

Multilingual blog

Layer / File(s) Summary
Content metadata and routing helpers
scripts/sync-blog-posts.mjs, src/lib/content.ts, src/data/blog-posts.json, test/lib.test.mjs
Blog posts now include language and translation metadata; shared helpers generate localized paths and find matching translations, with routing tests covering published and draft behavior.
Localized article rendering
src/components/BlogArticlePage.astro, src/layouts/BaseLayout.astro
Article rendering is centralized and includes localized dates, SEO data, JSON-LD, translation links, author details, language attributes, Open Graph locale, and hreflang links.
Localized indexes and route integration
src/pages/blog*, src/pages/en/blog*, src/pages/sitemap.xml.ts
Indonesian and English indexes and dynamic routes filter by language, handle preview paths, pass translations to the shared component, and generate localized sitemap URLs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Visitor
  participant BlogIndex
  participant ArticleRoute
  participant BlogArticlePage
  participant BaseLayout
  Visitor->>BlogIndex: open Indonesian or English blog index
  BlogIndex->>ArticleRoute: follow localized article URL
  ArticleRoute->>BlogArticlePage: pass post and translations
  BlogArticlePage->>BaseLayout: pass localized metadata and alternates
  BaseLayout-->>Visitor: render localized article page
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: multilingual blog support plus publishing the OpenClaw Meetup #5 posts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/blog-multilingual

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 23, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
indopensource-org c17cfc9 Commit Preview URL

Branch Preview URL
Jul 23 2026, 04:54 PM

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0593e3aef9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/pages/en/blog.astro
<section class="py-16 md:py-20">
<div class="mx-auto grid w-[min(1120px,calc(100%-32px))] gap-6">
{publishedPosts.map((post) => {
const articleUrl = articleHref(post);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the base path for English article links

When the site is built with ASTRO_BASE set for a subpath deployment, articleHref(post) returns a root-relative URL such as /en/blog/foo/. The raw title and thumbnail anchors use that value directly, so clicks leave the configured base path and 404, while the button works because BaseButton normalizes its href. Use a base-aware href for the DOM links and keep the unbased value only for display/canonical uses.

Useful? React with 👍 / 👎.


<!doctype html>
<html lang="id">
<html lang={language}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Mark Indonesian chrome when switching document language

For English blog pages this changes the entire document to lang="en", but the shared layout still renders Indonesian chrome such as the skip link, header, and footer labels. Screen readers will pronounce those Indonesian sections as English on /en/blog/*; either localize the shared chrome for English pages or mark those unchanged regions with lang="id" while the main article content is English.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/layouts/BaseLayout.astro (2)

228-228: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Skip-link text isn't language-aware.

Lewati ke konten is hardcoded Indonesian even when language="en" (e.g. the English blog and its articles), so English pages ship an accessibility string in the wrong language while every other piece of document metadata (html lang, og:locale, JSON-LD inLanguage) now correctly switches.

🌐 Suggested fix
-    <a href="`#main-content`" class="skip-link">Lewati ke konten</a>
+    <a href="`#main-content`" class="skip-link">{language === 'en' ? 'Skip to content' : 'Lewati ke konten'}</a>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/layouts/BaseLayout.astro` at line 228, Update the skip-link in BaseLayout
to render language-aware text based on the active language, using the English
label when language="en" and retaining the Indonesian label for Indonesian
pages. Keep the existing href and accessibility behavior unchanged.

229-233: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Localize header/footer for English pages

BaseLayout branches on language, but <SiteHeader /> and <SiteFooter /> are rendered unconditionally and hardcode Indonesian text such as Proyek, Belajar, Donasi, Lewati ke konten, and the footer Dibuat dengan Joko UI · dibikin di Indonesia / Komunitas Open Source Indonesia. Pass the language/context into these components or inject page-level translations so English blog pages do not wrap English content in Indonesian UI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/layouts/BaseLayout.astro` around lines 229 - 233, Update BaseLayout’s
SiteHeader and SiteFooter rendering to receive the current language/context or
page-level translations from the existing language branch. Ensure English pages
display localized navigation, skip-link, and footer text instead of Indonesian
defaults, while preserving the existing Indonesian text for Indonesian pages.
🧹 Nitpick comments (6)
src/layouts/BaseLayout.astro (1)

177-179: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding an x-default hreflang entry.

Google's multi-lingual guidance recommends an x-default alternate alongside per-language ones for the fallback/default version. Optional, not required for correctness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/layouts/BaseLayout.astro` around lines 177 - 179, Add an x-default
alternate link alongside the entries generated by the alternates.map block in
BaseLayout, pointing it to the fallback/default page URL using the existing
site-origin URL construction. Preserve all existing per-language alternate
links.
test/lib.test.mjs (1)

123-137: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the status-mismatch and missing-translationKey branches.

The new suite doesn't assert that articleTranslations returns [] when post.translationKey is unset, or that a draft/published status mismatch excludes an otherwise-matching candidate — both are documented behaviors of the function being tested.

✅ Suggested additional assertions
   it('connects published translations through their shared key', () => {
     const id = { slug: 'contoh', status: 'published', lang: 'id', translationKey: 'same' };
     const en = { slug: 'example', status: 'published', lang: 'en', translationKey: 'same' };
     const unrelated = { slug: 'other', status: 'published', lang: 'en', translationKey: 'other' };
     assert.deepEqual(articleTranslations([id, en, unrelated], id), [en]);
   });
+
+  it('returns no translations without a translationKey', () => {
+    assert.deepEqual(articleTranslations([{ slug: 'a', status: 'published', lang: 'id' }], { slug: 'a', status: 'published', lang: 'id' }), []);
+  });
+
+  it('excludes a translation whose publish status differs', () => {
+    const published = { slug: 'contoh', status: 'published', lang: 'id', translationKey: 'k' };
+    const draftEn = { slug: 'example', status: 'draft', lang: 'en', translationKey: 'k' };
+    assert.deepEqual(articleTranslations([published, draftEn], published), []);
+  });
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/lib.test.mjs` around lines 123 - 137, Extend the “connects published
translations” test for articleTranslations to assert that a post without
translationKey returns [] and that a candidate sharing the translationKey but
having a different status is excluded. Keep the existing matching published
translation assertion unchanged.
src/pages/blog/[...slug].astro (2)

6-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated getStaticPaths pattern with the English route.

This block is identical to src/pages/en/blog/[...slug].astro lines 6-14 except for the 'id'/'en' literal. See consolidated comment below for a suggested shared-helper fix.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/blog/`[...slug].astro around lines 6 - 14, Extract the shared
static-path generation logic from getStaticPaths in the Indonesian and English
blog routes into a reusable helper that accepts the target language. Update both
route-level getStaticPaths functions to call that helper with their respective
language identifier, preserving the existing slug, preview, and translation
behavior.

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shared getStaticPaths logic duplicated across the two language routes. Both dynamic slug routes implement identical filter/params/props construction, differing only by the hardcoded language literal — a single shared helper would remove the duplication and prevent the two from drifting as more languages/behavior are added.

  • src/pages/blog/[...slug].astro#L6-14: replace the inline getStaticPaths body with a call to a new shared helper, e.g. getLangStaticPaths(blogPosts as BlogPost[], 'id') exported from src/lib/content.ts.
  • src/pages/en/blog/[...slug].astro#L6-14: same replacement, passing 'en' instead of 'id'.
♻️ Suggested shared helper (src/lib/content.ts)
export function getLangStaticPaths(posts: BlogPost[], lang: 'id' | 'en') {
  return posts
    .filter((post) => articleLanguage(post) === lang)
    .map((post) => ({
      params: { slug: isPublished(post) ? post.slug : `preview/${post.slug}` },
      props: { post, translations: articleTranslations(posts, post) }
    }));
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/blog/`[...slug].astro at line 1, Extract the duplicated
language-specific filtering, slug parameter, and props construction into an
exported getLangStaticPaths helper in content.ts, accepting BlogPost[] and the
'id' | 'en' language. Replace the inline getStaticPaths implementations in both
slug routes with this helper, passing 'id' for the default route and 'en' for
the English route, while preserving the existing articleLanguage, isPublished,
and articleTranslations behavior.
src/pages/blog.astro (1)

79-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the already-computed articleUrl instead of re-deriving the path.

Line 90 hand-builds /blog/{post.slug} instead of reusing articleUrl (computed one line above from articleHref). It's harmless today since this list is pre-filtered to published/id posts, but it duplicates the href logic and diverges from src/pages/en/blog.astro, which correctly displays {articleUrl} in the equivalent spot.

♻️ Suggested fix
-                    <span class="text-brand-dark">/blog/{post.slug}</span>
+                    <span class="text-brand-dark">{articleUrl}</span>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/blog.astro` around lines 79 - 90, In the blog article metadata
rendered by the loop, replace the hand-built `/blog/{post.slug}` display with
the already-computed `articleUrl` from `articleHref(post)`, matching the
equivalent English blog page and keeping the existing article link logic
centralized.
src/pages/en/blog/[...slug].astro (1)

6-14: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated getStaticPaths pattern with the Indonesian route.

Identical to src/pages/blog/[...slug].astro lines 6-14 except for the 'en' literal. See consolidated comment below.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/en/blog/`[...slug].astro around lines 6 - 14, Consolidate the
duplicated getStaticPaths logic used by the English and Indonesian routes into a
shared implementation, preserving each route’s language filter and existing slug
and translation behavior. Update the English getStaticPaths and its Indonesian
counterpart to reuse that shared implementation instead of maintaining identical
mapping logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/layouts/BaseLayout.astro`:
- Line 228: Update the skip-link in BaseLayout to render language-aware text
based on the active language, using the English label when language="en" and
retaining the Indonesian label for Indonesian pages. Keep the existing href and
accessibility behavior unchanged.
- Around line 229-233: Update BaseLayout’s SiteHeader and SiteFooter rendering
to receive the current language/context or page-level translations from the
existing language branch. Ensure English pages display localized navigation,
skip-link, and footer text instead of Indonesian defaults, while preserving the
existing Indonesian text for Indonesian pages.

---

Nitpick comments:
In `@src/layouts/BaseLayout.astro`:
- Around line 177-179: Add an x-default alternate link alongside the entries
generated by the alternates.map block in BaseLayout, pointing it to the
fallback/default page URL using the existing site-origin URL construction.
Preserve all existing per-language alternate links.

In `@src/pages/blog.astro`:
- Around line 79-90: In the blog article metadata rendered by the loop, replace
the hand-built `/blog/{post.slug}` display with the already-computed
`articleUrl` from `articleHref(post)`, matching the equivalent English blog page
and keeping the existing article link logic centralized.

In `@src/pages/blog/`[...slug].astro:
- Around line 6-14: Extract the shared static-path generation logic from
getStaticPaths in the Indonesian and English blog routes into a reusable helper
that accepts the target language. Update both route-level getStaticPaths
functions to call that helper with their respective language identifier,
preserving the existing slug, preview, and translation behavior.
- Line 1: Extract the duplicated language-specific filtering, slug parameter,
and props construction into an exported getLangStaticPaths helper in content.ts,
accepting BlogPost[] and the 'id' | 'en' language. Replace the inline
getStaticPaths implementations in both slug routes with this helper, passing
'id' for the default route and 'en' for the English route, while preserving the
existing articleLanguage, isPublished, and articleTranslations behavior.

In `@src/pages/en/blog/`[...slug].astro:
- Around line 6-14: Consolidate the duplicated getStaticPaths logic used by the
English and Indonesian routes into a shared implementation, preserving each
route’s language filter and existing slug and translation behavior. Update the
English getStaticPaths and its Indonesian counterpart to reuse that shared
implementation instead of maintaining identical mapping logic.

In `@test/lib.test.mjs`:
- Around line 123-137: Extend the “connects published translations” test for
articleTranslations to assert that a post without translationKey returns [] and
that a candidate sharing the translationKey but having a different status is
excluded. Keep the existing matching published translation assertion unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: aa24dfc8-1793-41f2-9b17-ffcb2ae734f7

📥 Commits

Reviewing files that changed from the base of the PR and between e8bf891 and 0593e3a.

📒 Files selected for processing (11)
  • scripts/sync-blog-posts.mjs
  • src/components/BlogArticlePage.astro
  • src/data/blog-posts.json
  • src/layouts/BaseLayout.astro
  • src/lib/content.ts
  • src/pages/blog.astro
  • src/pages/blog/[...slug].astro
  • src/pages/en/blog.astro
  • src/pages/en/blog/[...slug].astro
  • src/pages/sitemap.xml.ts
  • test/lib.test.mjs

@wauputr4
wauputr4 merged commit 1cc9006 into main Jul 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant