refactor: i18n routing, shared GeneratorLayout, analytics component, content collections

- Add Astro native i18n routing (prefixDefaultLocale: false)
- Introduce GeneratorLayout to deduplicate all generator pages
- Enrich generator data with pageTitle, ruPageTitle, howTo/whenTo arrays
- Make EN and RU generator pages identical using @/ path aliases
- Extract analytics (Yandex Metrika + Top.Mail.Ru) into Analytics.astro
- Move tracker IDs to src/data/config.ts, inject via define:vars
- Migrate generator metadata to Content Collections (src/content/generators/*.json)
- Add Zod schema with .strict() validation in src/lib/generator-schema.ts
- Update CLAUDE.md with new conventions
This commit is contained in:
emil
2026-05-10 20:42:05 +03:00
parent 195c2deb54
commit f1ba82735b
46 changed files with 960 additions and 1369 deletions
+22 -5
View File
@@ -6,6 +6,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
Randify is a bilingual (EN/RU) static site (Astro 4 + Tailwind CSS 4) hosting random-value generators. All 10 generators are live. Deployed to randify.pro on reg.ru via GitHub Actions + rsync on push to `main`.
**i18n is handled via Astro's built-in i18n routing.** English pages live at `src/pages/`, Russian pages at `src/pages/ru/`. Both share identical templates because `BaseLayout`, `GeneratorLayout`, and components derive the active locale from `Astro.currentLocale`.
## Commands
```bash
@@ -27,16 +29,29 @@ No test runner or linter is configured.
Every new generator must be created in **both English and Russian simultaneously**.
1. **`src/data/generators.ts`** — add an entry with all fields: `slug`, `title`, `description`, `icon`, `status: 'live'`, `seoTitle`, `seoDescription`, `ruTitle`, `ruDescription`, `ruSeoTitle`, `ruSeoDescription`.
1. **`src/content/generators/<slug>.json`** — create a JSON file with all fields: `slug`, `title`, `description`, `icon`, `status`, `seoTitle`, `seoDescription`, `ruTitle`, `ruDescription`, `ruSeoTitle`, `ruSeoDescription`, `pageTitle`, `ruPageTitle`, `howTo`, `whenTo`, `ruHowTo`, `ruWhenTo`. The schema is defined in `src/lib/generator-schema.ts` and validated automatically at build time via Zod (`.strict()` — unknown fields will fail the build).
2. **`src/components/generators/<Name>Generator.astro`** — the interactive component. Language detection pattern:
- Frontmatter: `const isRu = Astro.url.pathname.startsWith('/ru'); const T = useT(isRu ? 'ru' : 'en');`
- Client script: `const isRu = document.documentElement.lang === 'ru';`
- Use `T.*` keys for all user-visible strings in the template; use `isRu` ternaries in the `<script>` block.
3. **`src/pages/generators/<slug>.astro`** — English page with `<BaseLayout lang="en">`.
3. **`src/pages/generators/<slug>.astro`** — create the page using `GeneratorLayout`:
```astro
---
import GeneratorLayout from '@/layouts/GeneratorLayout.astro';
import <Name>Generator from '@/components/generators/<Name>Generator.astro';
import { generators } from '@/data/generators';
4. **`src/pages/ru/generators/<slug>.astro`** — Russian page with `<BaseLayout lang="ru">`, `ruSeoTitle`/`ruSeoDescription`, breadcrumb back to `/ru/`, Russian `<SeoBlock>` content.
const generator = generators.find((g) => g.slug === '<slug>')!;
---
<GeneratorLayout generator={generator}>
<<Name>Generator />
</GeneratorLayout>
```
4. **`src/pages/ru/generators/<slug>.astro`** — **copy the English file exactly**. Because both files use `@/` path aliases and `GeneratorLayout` derives the locale from `Astro.currentLocale`, the file content is identical for both languages.
**Critical:** Never use a shared dynamic `[slug].astro` for Russian pages. Astro bundles scripts from all imported components — using a single file that imports all 10 generators causes every Russian page to run all 10 scripts, breaking them. Each page must be its own file importing only its generator.
@@ -46,13 +61,15 @@ Every new generator must be created in **both English and Russian simultaneously
- **`src/components/LanguageSwitcher.astro`** — fixed top-right EN/RU toggle; persists choice in `localStorage('lang-pref')`.
- **`src/layouts/BaseLayout.astro`** — accepts `lang` prop (`'en' | 'ru'`), sets `<html lang>`, injects `hreflang` alternates, and includes auto-redirect script (first visit, Russian browser → `/ru/`).
- English pages: `/generators/<slug>/` — Russian pages: `/ru/generators/<slug>/`
- Path aliases `@/*` resolve to `src/*` and are used in all page files so EN and RU templates can be identical.
- `GeneratorLayout.astro` wraps every generator page: breadcrumb, header, AdBanner, SeoBlock, and slot for the interactive component.
## Key conventions
- Accent color: `#534AB7` (CSS var `--accent` in BaseLayout).
- All SVG icons are inlined strings in the `icons` map inside `GeneratorCard.astro`; add new ones there.
- `GeneratorCard.astro` accepts `lang` prop — pass `lang="ru"` on Russian pages to get `ruTitle`/`ruDescription` and `/ru/` links.
- `GeneratorCard.astro` auto-detects locale from `Astro.currentLocale` when no `lang` prop is passed.
- `SeoBlock.astro` accepts `lang` prop for translated "How to use" / "When to use" headers.
- `AdBanner.astro` randomises between two Yandex referral links; three size variants: `leaderboard`, `rectangle`, `tile`.
- Yandex Metrika counter (ID 109130319) is in `BaseLayout.astro`.
- Yandex Metrika counter (ID `109130319`) and Top.Mail.Ru counter (ID `3765043`) live in `src/components/Analytics.astro`. IDs are configured in `src/data/config.ts` and injected via `define:vars`.
- No client-side router — each generator is a separate static HTML page.