Skip to content

feat(docs): add Traditional Chinese translations with path-based locale routing#170

Merged
appleboy merged 1 commit intomainfrom
worktree-docs
Apr 22, 2026
Merged

feat(docs): add Traditional Chinese translations with path-based locale routing#170
appleboy merged 1 commit intomainfrom
worktree-docs

Conversation

@appleboy
Copy link
Copy Markdown
Member

Summary

  • Adds Traditional Chinese translations for all 8 integrator-facing docs pages under internal/templates/docs/zh-TW/
  • Introduces path-based locale URLs (/docs/<lang>/<slug>) for crawler-friendly, bookmarkable pages — legacy /docs/<slug> URLs auto-redirect to the detected locale
  • Detects locale from URL path → docs_lang cookie → Accept-Language header → default (en)
  • Adds an editorial-style language switcher (EN · 繁中) to the docs sidebar; non-canonical casings (e.g. /docs/zh-tw/…) 301-redirect to the canonical case to keep one indexable URL per page
  • Precomputes sidebar entries and switcher options per locale at boot so each page render passes pointers to immutable slices — no per-request allocations
  • Cookie is only rewritten when the value actually changes (avoids busting shared caches); Secure flag follows the project convention (isProduction)

Test plan

  • Visit /docs with a clean browser → lands on /docs/en/getting-started
  • Visit /docs with Accept-Language: zh-TW → lands on /docs/zh-TW/getting-started
  • Click 繁中 in the sidebar → URL becomes /docs/zh-TW/<current-slug>; all sidebar titles, headings, and body are Chinese
  • Visit /docs/zh-tw/tokens → 301 to /docs/zh-TW/tokens → renders
  • Visit /docs/ja-JP/tokens → 302 to /docs
  • Visit /docs/getting-started (legacy) → 302 to /docs/<detected-locale>/getting-started
  • Confirm docs_lang cookie is only set on first visit and on locale change, not on every render
  • Verify mermaid diagrams and Prism.js code highlighting still render in both locales
  • make generate && make fmt && make lint && make test all green

🤖 Generated with Claude Code

…le routing

- Add Traditional Chinese translations for all 8 integrator-facing docs pages
- Introduce path-based locale URLs (/docs/<lang>/<slug>) with legacy redirects
- Detect locale from URL path, cookie, or Accept-Language header
- Add an editorial language switcher to the docs sidebar with canonical-case 301 redirects
- Precompute sidebar entries and switcher options per locale to avoid per-request allocations
- Persist the docs_lang cookie only when the locale actually changes
- Cover locale parity, routing, and cookie behavior with handler tests
Copilot AI review requested due to automatic review settings April 22, 2026 15:18
@appleboy appleboy merged commit f0edb27 into main Apr 22, 2026
13 of 17 checks passed
@appleboy appleboy deleted the worktree-docs branch April 22, 2026 15:18
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Traditional Chinese (zh-TW) docs support and introduces canonical, crawlable locale-in-path documentation routes, plus a sidebar language switcher and comprehensive handler tests.

Changes:

  • Introduces /docs/<locale>/<slug> canonical routing with locale detection + redirects from legacy entry points.
  • Adds zh-TW translations for the integrator-facing docs pages.
  • Adds a docs language switcher UI and precomputes locale-specific sidebar/switcher props at boot, with tests covering routing/detection.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
internal/templates/static/css/pages/docs.css Styles the new sidebar language switcher and adjusts sidebar header layout.
internal/templates/props.go Extends docs template props to include locale, slug, localized strings, and switcher options.
internal/templates/docs_page.templ Updates docs links to locale paths and adds the language switcher + localized chrome.
internal/templates/docs/zh-TW/*.md Adds Traditional Chinese versions of the docs pages.
internal/handlers/docs.go Implements locale-aware docs loading, routing/redirects, cookie persistence, and locale detection helpers.
internal/handlers/docs_test.go Adds tests for locale matching, redirects, canonicalisation, and fallback behavior.
internal/bootstrap/router.go Replaces legacy docs route with entry + canonical locale routes.
internal/bootstrap/handlers.go Updates docs handler initialization to pass production secure-cookie setting.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/handlers/docs.go
Comment on lines +77 to +97
// docsStrings holds UI strings shown on the docs chrome (not the Markdown body), per locale.
type docsStrings struct {
SidebarTitle string
LangSwitcherLabel string
LangEnglish string
LangZhTW string
}

var docsStringsByLocale = map[Locale]docsStrings{
LocaleEN: {
SidebarTitle: "Documentation",
LangSwitcherLabel: "Language",
LangEnglish: "English",
LangZhTW: "繁體中文",
},
LocaleZHTW: {
SidebarTitle: "技術文件",
LangSwitcherLabel: "語言",
LangEnglish: "English",
LangZhTW: "繁體中文",
},
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

PR description mentions the language switcher copy as "EN · 繁中", but the actual labels wired into docsStringsByLocale / DocsLocaleOption.Label are "English" and "繁體中文". If the intended UI is short codes, update these strings (and possibly use locale codes directly); otherwise update the PR description to match what will render.

Copilot uses AI. Check for mistakes.
Comment thread internal/handlers/docs.go
Comment on lines +257 to +262
loc, ok := exactLocaleTag(langParam)
if !ok {
// Unknown slug → redirect to first page
if len(h.pages) > 0 {
c.Redirect(http.StatusFound, "/docs/"+h.pages[0].Slug)
} else {
c.Redirect(http.StatusFound, "/")
}
// Unknown locale in the URL — bounce to the detection entry so the
// user ends up on a valid canonical URL.
c.Redirect(http.StatusFound, "/docs")
return
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

/docs/:lang/:slug currently rejects locale aliases like zh/zh-Hant (because ShowDocsPage uses exactLocaleTag), so a request like /docs/zh/tokens redirects to /docs and loses the slug. Since /docs/:lang does accept these aliases via matchLocaleTag, this behavior is inconsistent and breaks deep links people are likely to try/share. Consider: if exactLocaleTag fails but matchLocaleTag(langParam) succeeds, 301/302 redirect to the canonical /docs/<canonical-locale>/<slug> (preserving :slug) instead of bouncing to /docs.

Copilot uses AI. Check for mistakes.
Comment thread internal/handlers/docs.go

if loc, ok := matchLocaleTag(raw); ok {
if pages := h.pages[loc]; len(pages) > 0 {
c.Redirect(http.StatusFound, docsPageURL(loc, pages[0].Slug))
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

When ShowDocsEntry is hit with a supported locale code but non-canonical casing (e.g. /docs/zh-tw), it redirects with 302 Found. If the goal is a single indexable URL per locale entry point (similar to the 301 canonicalisation in ShowDocsPage), consider emitting 301 Moved Permanently when the input differs only by case from the canonical locale code (i.e. exactLocaleTag(raw) matches but raw != string(loc)).

Suggested change
c.Redirect(http.StatusFound, docsPageURL(loc, pages[0].Slug))
status := http.StatusFound
if raw != string(loc) && strings.EqualFold(raw, string(loc)) {
status = http.StatusMovedPermanently
}
c.Redirect(status, docsPageURL(loc, pages[0].Slug))

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +9
<div class="docs-layout" lang={ props.Locale }>
<aside class="docs-sidebar" aria-label={ props.SidebarTitle }>
<div class="docs-sidebar-header">
<span class="docs-sidebar-title">Documentation</span>
<span class="docs-sidebar-title">{ props.SidebarTitle }</span>
@docsLangSwitcher(props)
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

The sidebar <aside> aria-label was changed from a descriptive phrase ("Documentation navigation") to just the localized title (props.SidebarTitle). This makes the landmark label less clear to screen readers. Consider keeping a dedicated localized navigation label (e.g. "Documentation navigation" / "技術文件導覽") or applying the aria-label to the <nav> element instead.

Copilot uses AI. Check for mistakes.
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.

2 participants