Skip to content

feat(docs): add multi-language (i18n) support to the docs site — EN + TR #40

Description

@tutkuofnight

Summary

Add internationalization (i18n) to the documentation site (packages/docs) so
it can be read in multiple languages. The site has no i18n today — routing,
chrome strings, content, SEO, and search are all single-language (English).

  • Phase 1 (required, in scope): English (en) and Turkish (tr),
    with en as the default.
  • Phase 2 (future, out of scope here): Russian (ru), Chinese
    (zh, "CN")
    , and Korean (ko, "KR") — added once Phase 1 is in place;
    the system must be left open to them with no further architectural changes.

Current state (what i18n must touch)

  • No existing i18n — <html lang="en"> is hard-coded (index.html).
  • Routing: react-router-dom resolves pages from pageBySlug, built from an
    eager import.meta.glob('./content/**/*.mdx') keyed by frontmatter slug
    (src/pages.ts).
  • Content: 73 MDX pages under src/content/ (getting-started,
    foundations/, guides/, components/, reference/, changelog/, index).
  • Chrome strings (hard-coded in TSX): "On this page"
    (TableOfContents.tsx), "Page not found" (NotFound.tsx), the TopNav brand,
    the sidebar/primary nav labels and group names in src/data/navigation.ts,
    plus the search dialog and footer.
  • SEO: useDocumentHead + src/seo/vite-plugin-seo.ts prerender per-route
    head tags — English-only, no hreflang.
  • Search: src/search/vite-plugin-search.ts builds one MiniSearch index over
    English content text.

How to implement

Build a lightweight, bespoke i18n (no heavy dependency) consistent with the
existing hand-rolled Vite/MDX setup.

1. Locale config — single source of truth

  • Add src/i18n/config.ts exporting locales = ['en', 'tr'] as const,
    defaultLocale = 'en', and a Locale type. Everything (routing, switcher,
    SEO, search, content glob) reads this list
    , so Phase 2 is "append to the
    array + add files", nothing else.

2. Routing — default locale unprefixed, others prefixed

  • en stays unprefixed at /...; other locales are prefixed: /tr/...
    (e.g. /tr/components/button). SEO- and migration-friendly (existing English
    URLs don't change).
  • Add a LocaleProvider that derives the active locale from the pathname;
    update slugFromPath (src/pages.ts) to strip a leading locale segment before
    lookup. Unknown/!translated slug → fall back to the en page (see §5).

3. Locale detection, persistence, <html lang>

  • On first visit, pick from navigator.language (matched against locales),
    else defaultLocale; persist the user's explicit choice in localStorage.
  • An effect sets document.documentElement.lang to the active locale on every
    navigation/locale change (replacing the hard-coded index.html value).

4. Chrome string catalog

  • src/i18n/messages/en.ts + tr.ts, each implementing a shared typed
    Messages interface (compile error if a key is missing in any locale).
  • An I18nProvider exposes a useT() hook returning the active catalog.
  • Replace every hard-coded UI string in TopNav, Sidebar, Footer,
    TableOfContents, SearchDialog, NotFound with t(...). Nav group
    labels
    currently come from GROUP_ORDER/frontmatter, so introduce stable
    group keys and translate them through the catalog (not the raw label).

5. Content — per-locale trees with EN fallback

  • Reorganize src/content/ into per-locale trees: content/en/**,
    content/tr/**. Update the glob in pages.ts to capture the locale from the
    path and build the locale-prefixed slug.
  • pageBySlug resolves the active locale first, then falls back to the en
    page
    when a translation is missing, rendering a subtle "not yet translated"
    banner (a small MDX/shell component). This lets TR ship incrementally.
  • Translate the core pages to TR first (landing, Getting Started, Foundations,
    Guides); component reference pages may fall back to EN initially and be
    translated over time.

6. Language switcher (UI)

  • A switcher in TopNav built with a Manti component (Menu or Select,
    not raw HTML) that navigates to the same slug in the target locale and
    persists the choice.

7. SEO

  • useDocumentHead + vite-plugin-seo: per-locale <title>/description,
    hreflang alternate links between locale variants of each page, correct
    <html lang>, and per-locale entries in the sitemap. The prerender step
    iterates locales × pages.

8. Search

  • Make vite-plugin-search emit one MiniSearch index per locale;
    SearchProvider selects the index for the active locale so results match the
    language being read.

Phase 2 (future — not in this issue)

  • Add ru, zh, ko by appending to locales in src/i18n/config.ts and
    dropping in their message catalogs + translated content trees. If Phase 1 is
    built config-driven, no further architectural changes are required.

Acceptance criteria (Phase 1)

  • en and tr selectable via a Manti-component language switcher in TopNav.
  • Initial locale auto-detected, choice persisted, <html lang> updated live.
  • Locale-aware routing (/tr/...) with EN fallback + "not yet translated"
    notice for untranslated pages.
  • All chrome strings come from typed per-locale catalogs (no hard-coded UI
    copy left in shell components); missing keys fail typecheck.
  • TR translations for the core pages; component pages fall back gracefully.
  • SEO emits hreflang + per-locale titles/sitemap; search is per-locale.
  • Adding a locale is a single-array change (proves Phase 2 readiness).
  • pnpm --filter @manti-ui/docs build passes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationenhancementNew feature or requeststatus:needs-triagecreated by fabrika status bootstrap label-taxonomy

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions