Skip to content

feat(i18n): localize the site into ru, es-ES and es-AR - #20

Merged
jigarkhwar merged 3 commits into
mainfrom
feat/i18n
Aug 4, 2026
Merged

feat(i18n): localize the site into ru, es-ES and es-AR#20
jigarkhwar merged 3 commits into
mainfrom
feat/i18n

Conversation

@jigarkhwar

Copy link
Copy Markdown
Contributor

What changed

Three locales alongside English, on Astro's own routing — no i18n dependency, devDependency footprint unchanged.

Locale URL <html lang>
English (default) / en
Русский /ru/ ru
Español (España) /es-es/ es-ES
Español (Argentina) /es-ar/ es-AR

Scope

Per the call on scope: the interface is translated, repository-specific text is not. Product summaries, details, bullets, code captions and archived-repo notes stay English in src/data/site.ts, matching the READMEs and code they describe. Everything else — nav, hero, section headings, group labels, community cards, Telegram panel, manifesto, priority block, footer, and the micro-labels (// About, Read the README, Copy, Archived) — is localized.

How it works

  • Routing — one src/pages/[...locale].astro renders all four pages via getStaticPaths; English at the root, the rest prefixed. Single template, no duplication.
  • Copy — every UI string moved into src/i18n/ui.ts, typed as Record<Locale, Strings>. A missing or misspelled key is a typecheck failure, not a blank spot on the page — verified by deleting one key and watching astro check name it:
    src/i18n/ui.ts:209:3 - error ts(2741): Property 'ctaExplore' is missing in type … but required in type …
    
    Group ids are typed the same way, so a group in site.ts without matching copy won't compile. This is the same "types instead of tests" line as the validation PR.
  • Rich text — strings mark their emphasized fragment with *asterisks* and a small <Emphasis> component renders it as span/em/b. Locale files stay free of markup and there's no set:html anywhere. It also keeps the hero wordplay intact: eco|system splits the same way in Одна *эко*/система and Un *eco*/sistema.
  • es-AR is not a copy of es-ES — voseo throughout (Explorá, Abrí, Mirá, entendé, seguí, Probá, Compartí, Unite, Necesitás, usás) plus roadmap over hoja de ruta. It spreads the shared es-ES strings and overrides only what actually differs, so future edits to common copy stay in one place.
  • SEO / a11y — per-locale title, description, canonical, and hreflang alternates including x-default; switcher links carry full language names as their accessible label rather than the two-letter chip.
  • Client scripts — theme-toggle and copy-button labels come from the rendered locale via data attributes, so Copiar → Copiado and Светлая / Тёмная work without shipping a dictionary to the browser.

Verification

All four locales checked in the browser: content, lang, switcher state and navigation, plus theme toggle / accordion / copy still working with localized labels. Topbar gains a fourth control, so at ≤640px the switcher tightens and the GitHub button drops to its mark — checked at 390px, zero overflow. npm run validate green; build emits 4 pages.

🤖 Generated with Claude Code

Adds three locales alongside English, using Astro's own routing — no
i18n dependency, so the devDependency footprint is unchanged.

Routing: a single [...locale].astro renders all four pages via
getStaticPaths, English at / and the rest prefixed (/ru/, /es-es/,
/es-ar/). One template, no duplication.

Copy: every UI string moves out of the markup into src/i18n/ui.ts,
typed as Record<Locale, Strings>. A missing or misspelled key is a
typecheck failure rather than a blank spot on the page — verified by
deleting one key and watching astro check name it. Group ids are typed
the same way, so a group in site.ts without matching copy will not
compile.

Repository-specific text — product summaries, details, bullets, code
captions and archived-repo notes — stays English, matching the READMEs
and code it describes.

Rich text: strings mark their emphasized fragment with *asterisks* and
<Emphasis> renders it as a span/em/b. Locale files stay free of markup
and there is no set:html anywhere. This also keeps the hero wordplay
intact — 'eco*system*' splits the same way in Одна *эко*/система and
Un *eco*/sistema.

es-AR is not a copy of es-ES: it uses voseo throughout (Explorá, Abrí,
Mirá, entendé, seguí, Probá, Compartí, Unite, Necesitás, usás) and
'roadmap' over 'hoja de ruta', spreading the shared es-ES strings and
overriding only what actually differs.

Also: language switcher in the topbar (full language names as the
accessible label), per-locale <html lang>, title, description,
canonical and hreflang alternates including x-default, and localized
labels for the theme toggle and copy button passed to the client
scripts via data attributes.

Topbar gains a fourth control, so at <=640px the switcher tightens and
the GitHub button drops to its mark — checked at 390px, no overflow.
Four inline chips took 104px of topbar and still only showed opaque
two-letter codes; AR in particular reads as a country, not a Spanish
variant. A dropdown trims the trigger to ~45px on phones and gives the
menu room to name each language in full — English / Русский / Español
(España) / Español (Argentina) — with the code kept as a secondary
column.

Built on <details>/<summary>, so open/close and keyboard operation are
native with no JS; the small component script only adds outside-click
and Escape (returning focus to the trigger). The four options remain
plain links, so navigation still works with JS disabled.

The narrower trigger also lets the mobile rules relax: it keeps the
globe and drops the code, since the open menu names the language
anyway.
Extends localization to the repository prose that was deliberately left
English: 12 summaries, 12 details and 44 bullets, plus the two archived
-repo notes.

site.ts keeps structure only — tag, name, code snippet, the lang caption
naming the file a snippet belongs to, and URLs. Prose moves to the locale
files keyed by product name, with ProductName and ArchivedName typed as
unions of the actual entries, so a product added to site.ts without copy
in all four locales fails astro check rather than rendering blank.

The dictionary was outgrowing one file, so it splits per locale:
types.ts holds the Strings shape, en/ru/es-es/es-ar hold the copy, ui.ts
just assembles the Record<Locale, Strings>.

Not translated, because they are identical in every language: repository
names, code snippets, version lines (Gatling 3.13.5 · Scala 2.13.16 ·
Java 17+), API identifiers (query / queryP, matchByValue, insertInto,
*PluginEnabled, --set), and the lang caption (scala · build.sbt). All 26
such identifiers verified present verbatim in each locale's built HTML.

es-AR overrides six products where the copy genuinely differs — voseo
(agregá, usá, necesitás, elegí) and LatAm vocabulary (archivo over
fichero, notebook over portátil) — and inherits the rest from es-ES.
@jigarkhwar
jigarkhwar merged commit 54a9fb6 into main Aug 4, 2026
1 check 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