From f1ba82735b148d370449e385ccd8fda3a15fbdf2 Mon Sep 17 00:00:00 2001 From: emil Date: Sun, 10 May 2026 20:42:05 +0300 Subject: [PATCH] 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 --- .astro/settings.json | 5 - .astro/types.d.ts | 1 - .gitignore | 1 + CLAUDE.md | 27 +++- astro.config.mjs | 15 ++- src/components/Analytics.astro | 91 ++++++++++++++ src/components/GeneratorCard.astro | 3 +- src/components/LanguageSwitcher.astro | 28 ++--- src/content/config.ts | 11 ++ src/content/generators/cards.json | 38 ++++++ src/content/generators/coin.json | 38 ++++++ src/content/generators/colors.json | 36 ++++++ src/content/generators/dice.json | 43 +++++++ src/content/generators/list.json | 38 ++++++ src/content/generators/lottery.json | 38 ++++++ src/content/generators/numbers.json | 39 ++++++ src/content/generators/password.json | 38 ++++++ src/content/generators/uuid.json | 38 ++++++ src/content/generators/wheel.json | 38 ++++++ src/data/config.ts | 6 + src/data/generators.ts | 163 +++---------------------- src/layouts/BaseLayout.astro | 148 ++++++++++------------ src/layouts/GeneratorLayout.astro | 76 ++++++++++++ src/lib/generator-schema.ts | 25 ++++ src/pages/generators/cards.astro | 80 ++---------- src/pages/generators/coin.astro | 59 ++------- src/pages/generators/colors.astro | 79 ++---------- src/pages/generators/dice.astro | 82 ++----------- src/pages/generators/list.astro | 59 ++------- src/pages/generators/lottery.astro | 80 ++---------- src/pages/generators/numbers.astro | 80 ++---------- src/pages/generators/password.astro | 80 ++---------- src/pages/generators/uuid.astro | 59 ++------- src/pages/generators/wheel.astro | 80 ++---------- src/pages/index.astro | 85 +++++++------ src/pages/ru/generators/cards.astro | 43 ++----- src/pages/ru/generators/coin.astro | 43 ++----- src/pages/ru/generators/colors.astro | 42 ++----- src/pages/ru/generators/dice.astro | 46 ++----- src/pages/ru/generators/list.astro | 43 ++----- src/pages/ru/generators/lottery.astro | 43 ++----- src/pages/ru/generators/numbers.astro | 44 ++----- src/pages/ru/generators/password.astro | 43 ++----- src/pages/ru/generators/uuid.astro | 43 ++----- src/pages/ru/generators/wheel.astro | 43 ++----- src/pages/ru/index.astro | 89 +++++++------- 46 files changed, 960 insertions(+), 1369 deletions(-) delete mode 100644 .astro/settings.json delete mode 100644 .astro/types.d.ts create mode 100644 src/components/Analytics.astro create mode 100644 src/content/config.ts create mode 100644 src/content/generators/cards.json create mode 100644 src/content/generators/coin.json create mode 100644 src/content/generators/colors.json create mode 100644 src/content/generators/dice.json create mode 100644 src/content/generators/list.json create mode 100644 src/content/generators/lottery.json create mode 100644 src/content/generators/numbers.json create mode 100644 src/content/generators/password.json create mode 100644 src/content/generators/uuid.json create mode 100644 src/content/generators/wheel.json create mode 100644 src/data/config.ts create mode 100644 src/layouts/GeneratorLayout.astro create mode 100644 src/lib/generator-schema.ts diff --git a/.astro/settings.json b/.astro/settings.json deleted file mode 100644 index a24f29b..0000000 --- a/.astro/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "_variables": { - "lastUpdateCheck": 1778278468140 - } -} \ No newline at end of file diff --git a/.astro/types.d.ts b/.astro/types.d.ts deleted file mode 100644 index f964fe0..0000000 --- a/.astro/types.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/.gitignore b/.gitignore index aa0926a..f6d8805 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ dist/ .env *.log +.astro/ diff --git a/CLAUDE.md b/CLAUDE.md index 495fa0b..c869123 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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/.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/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 ` + + + + + + + diff --git a/src/components/GeneratorCard.astro b/src/components/GeneratorCard.astro index c43ae90..0e88051 100644 --- a/src/components/GeneratorCard.astro +++ b/src/components/GeneratorCard.astro @@ -8,7 +8,8 @@ interface Props { lang?: Lang; } -const { generator, lang = 'en' } = Astro.props; +const { generator, lang: langProp } = Astro.props; +const lang = (langProp || (Astro.currentLocale as Lang) || 'en'); const T = useT(lang); const { slug, icon, status } = generator; const isLive = status === 'live'; diff --git a/src/components/LanguageSwitcher.astro b/src/components/LanguageSwitcher.astro index a352d9b..aeb8e3f 100644 --- a/src/components/LanguageSwitcher.astro +++ b/src/components/LanguageSwitcher.astro @@ -1,29 +1,27 @@ --- -interface Props { - lang: 'en' | 'ru'; - alternatePath: string; -} - -const { lang, alternatePath } = Astro.props; -const targetLang = lang === 'en' ? 'ru' : 'en'; +const currentPath = Astro.url.pathname; +const isRu = currentPath.startsWith('/ru'); +const alternatePath = isRu + ? (currentPath.replace(/^\/ru/, '') || '/') + : ('/ru' + currentPath); ---
EN RU
diff --git a/src/content/config.ts b/src/content/config.ts new file mode 100644 index 0000000..6ac065d --- /dev/null +++ b/src/content/config.ts @@ -0,0 +1,11 @@ +import { defineCollection } from "astro:content"; +import { generatorSchema } from "@/lib/generator-schema"; + +const generators = defineCollection({ + type: "data", + schema: generatorSchema, +}); + +export const collections = { + generators, +}; diff --git a/src/content/generators/cards.json b/src/content/generators/cards.json new file mode 100644 index 0000000..bca1181 --- /dev/null +++ b/src/content/generators/cards.json @@ -0,0 +1,38 @@ +{ + "slug": "cards", + "title": "Card", + "description": "Draw a random playing card from a standard deck.", + "icon": "square-stack", + "status": "live", + "seoTitle": "Random Card Generator — Draw from a Deck | Randify", + "seoDescription": "Draw random playing cards from a standard 52-card deck. Pick any number of cards, with or without replacement. Free online card picker.", + "ruTitle": "Карта", + "ruDescription": "Вытащите случайную карту из стандартной колоды.", + "ruSeoTitle": "Генератор случайных карт | Randify", + "ruSeoDescription": "Тяните случайные карты из колоды 52 карт. С повторениями или без. Бесплатный онлайн-генератор карт.", + "pageTitle": "Card Generator", + "ruPageTitle": "Генератор карт", + "howTo": [ + "Set how many cards to draw (1–52).", + "Toggle Allow duplicates if you want cards to repeat.", + "Click Draw — cards are shown as mini card tiles.", + "Click Copy to copy the result (e.g. A♠, K♥) to your clipboard." + ], + "whenTo": [ + "Drawing a random hand for card games.", + "Practising card probability and statistics.", + "Simulating a random card pull for magic tricks or puzzles.", + "Running fair draws without a physical deck." + ], + "ruHowTo": [ + "Укажите количество карт для розыгрыша.", + "Включите «Разрешить повторения», если нужны дубли.", + "Нажмите «Тянуть» — карты появятся на экране.", + "Нажмите «Копировать», чтобы скопировать результат." + ], + "ruWhenTo": [ + "Случайная раздача карт для карточных игр.", + "Выбор случайных карт для гаданий.", + "Генерация случайных комбинаций карт для тестирования." + ] +} diff --git a/src/content/generators/coin.json b/src/content/generators/coin.json new file mode 100644 index 0000000..b551e37 --- /dev/null +++ b/src/content/generators/coin.json @@ -0,0 +1,38 @@ +{ + "slug": "coin", + "title": "Coin Flip", + "description": "Flip one or more coins and see heads or tails.", + "icon": "circle-dollar-sign", + "status": "live", + "seoTitle": "Coin Flip Online — Heads or Tails | Randify", + "seoDescription": "Flip a virtual coin online. Flip multiple coins at once and see how many heads and tails you get. Free random coin toss simulator.", + "ruTitle": "Монетка", + "ruDescription": "Подбросьте одну или несколько монет — орёл или решка.", + "ruSeoTitle": "Подбросить монетку онлайн — орёл или решка | Randify", + "ruSeoDescription": "Подбрасывайте виртуальную монету онлайн. Одну или несколько сразу. Бесплатный симулятор подбрасывания монеты.", + "pageTitle": "Coin Flip", + "ruPageTitle": "Подбросить монетку", + "howTo": [ + "Drag the slider to choose how many coins to flip (1–20).", + "Click Flip — each coin shows H for heads or T for tails.", + "When flipping more than one coin, the heads/tails count is shown below.", + "Click Copy to copy all results to the clipboard." + ], + "whenTo": [ + "Making a quick heads-or-tails decision.", + "Simulating a coin toss for a sports match or game.", + "Demonstrating probability and the law of large numbers.", + "Settling a fair 50/50 choice between two options." + ], + "ruHowTo": [ + "Выберите количество монет с помощью ползунка.", + "Нажмите «Подбросить» — орёл или решка.", + "При нескольких монетах отображается итоговый счёт.", + "Нажмите «Копировать», чтобы сохранить результат." + ], + "ruWhenTo": [ + "Принятие решений по принципу «орёл или решка».", + "Определение очерёдности в игре.", + "Симуляция случайных событий с равной вероятностью." + ] +} diff --git a/src/content/generators/colors.json b/src/content/generators/colors.json new file mode 100644 index 0000000..77080ef --- /dev/null +++ b/src/content/generators/colors.json @@ -0,0 +1,36 @@ +{ + "slug": "colors", + "title": "Color", + "description": "Generate a random color in HEX, RGB, or HSL format.", + "icon": "palette", + "status": "live", + "seoTitle": "Random Color Generator — HEX, RGB, HSL | Randify", + "seoDescription": "Get a random color in HEX, RGB or HSL format with one click. Free online color randomizer for designers and developers.", + "ruTitle": "Цвет", + "ruDescription": "Генерируйте случайный цвет в форматах HEX, RGB или HSL.", + "ruSeoTitle": "Генератор случайных цветов — HEX, RGB, HSL | Randify", + "ruSeoDescription": "Получайте случайные цвета в форматах HEX, RGB и HSL одним нажатием. Бесплатный рандомайзер цветов для дизайнеров.", + "pageTitle": "Color Generator", + "ruPageTitle": "Генератор цветов", + "howTo": [ + "Click Generate to produce a random color.", + "Switch between HEX, RGB, and HSL tabs to see the value in your preferred format.", + "Click the color value or the copy icon to copy it to the clipboard." + ], + "whenTo": [ + "Finding inspiration for a new design palette.", + "Picking a placeholder color while prototyping.", + "Selecting a random theme color for a project or presentation.", + "Teaching or exploring color theory and formats." + ], + "ruHowTo": [ + "Нажмите «Сгенерировать», чтобы получить случайный цвет.", + "Цвет отображается в форматах HEX, RGB и HSL одновременно.", + "Нажмите на иконку копирования рядом с нужным форматом." + ], + "ruWhenTo": [ + "Поиск вдохновения для цветовой палитры.", + "Случайный выбор цвета для дизайна или иллюстрации.", + "Генерация тестовых данных с цветовыми значениями." + ] +} diff --git a/src/content/generators/dice.json b/src/content/generators/dice.json new file mode 100644 index 0000000..098d4ce --- /dev/null +++ b/src/content/generators/dice.json @@ -0,0 +1,43 @@ +{ + "slug": "dice", + "title": "Dice", + "description": "Roll one or more dice with any number of sides.", + "icon": "dice-6", + "status": "live", + "seoTitle": "Dice Roller — d4 d6 d20 + Full Notation | D&D Dice | Randify", + "seoDescription": "Free online dice roller with full XdY+Z notation, keep/drop (4d6dl1), exploding dice (3d6!), reroll, advantage/disadvantage, and roll history. Perfect for D&D, Pathfinder, and Savage Worlds.", + "ruTitle": "Кубик", + "ruDescription": "Бросьте один или несколько кубиков с любым числом граней.", + "ruSeoTitle": "Бросить кубик онлайн — d4 d6 d8 d10 d12 d20 | D&D | Randify", + "ruSeoDescription": "Бесплатный онлайн-бросок кубиков с полной нотацией XdY+Z, keep/drop (4d6dl1), взрывающимися кубиками (3d6!), перебросом, преимуществом/помехой и историей. Для D&D, Pathfinder и Savage Worlds.", + "pageTitle": "Dice Generator", + "ruPageTitle": "Бросить кубик онлайн", + "howTo": [ + "Type a notation like 2d6+3 or 1d20 — or use the die buttons and count.", + "Pick a mode: Normal, Advantage, or Disadvantage (single die only).", + "Click Roll — each die result appears with the total sum.", + "Maximum rolls are highlighted in purple; ones are highlighted in red." + ], + "whenTo": [ + "Rolling dice for D&D, Pathfinder, or any tabletop RPG.", + "Using dice notation shortcuts like 2d6+3 or 1d20 for fast rolling.", + "Rolling with advantage or disadvantage for D&D 5e mechanics.", + "Generating character stats with 4d6 drop-lowest method.", + "Rolling exploding dice for Savage Worlds or house rules (e.g. 3d6!).", + "Using keep/drop modifiers like 4d6kh3 to keep highest rolls." + ], + "ruHowTo": [ + "Введите нотацию, например 2d6+3 или 1d20 — или настройте кубики вручную.", + "Выберите режим: Обычный, Преимущество или Помеха (только для одного кубика).", + "Нажмите «Бросить» — результаты отобразятся мгновенно.", + "Максимумы подсвечены фиолетовым, единицы — красным." + ], + "ruWhenTo": [ + "Броски в настольных ролевых играх (D&D и другие).", + "Быстрые броски через нотацию: 2d6+3, 1d20 и любые другие.", + "Преимущество и помеха для механик D&D 5e.", + "Бросок характеристик: 4d6, отбросить наименьший (4d6dl1).", + "Взрывающиеся кубики для Savage Worlds или хаус-рулов (например, 3d6!).", + "Использование keep/drop модификаторов типа 4d6kh3 (оставить три лучших)." + ] +} diff --git a/src/content/generators/list.json b/src/content/generators/list.json new file mode 100644 index 0000000..ffa448a --- /dev/null +++ b/src/content/generators/list.json @@ -0,0 +1,38 @@ +{ + "slug": "list", + "title": "List Picker", + "description": "Paste a list of items and pick random winners.", + "icon": "list", + "status": "live", + "seoTitle": "Random List Picker — Pick a Random Winner | Randify", + "seoDescription": "Paste any list of names or items and pick random winners instantly. Great for giveaways, choosing who goes first, or any random selection.", + "ruTitle": "Список", + "ruDescription": "Вставьте список элементов и выберите случайных победителей.", + "ruSeoTitle": "Случайный выбор из списка | Randify", + "ruSeoDescription": "Вставьте список имён или вариантов и выберите случайных победителей мгновенно. Идеально для розыгрышей и жеребьёвок.", + "pageTitle": "List Picker", + "ruPageTitle": "Случайный выбор из списка", + "howTo": [ + "Type or paste your items into the text area — one item per line.", + "Set how many items to pick in the Pick field.", + "Toggle Allow duplicates if the same item can be chosen more than once.", + "Click Pick (or press Ctrl+Enter) — the selected items appear as chips." + ], + "whenTo": [ + "Choosing a random winner for a giveaway or contest.", + "Picking who presents first in a meeting or class.", + "Organising a Secret Santa or gift exchange draw.", + "Selecting a random movie, restaurant, or activity from a list." + ], + "ruHowTo": [ + "Введите список элементов — по одному на строку.", + "Укажите, сколько элементов выбрать.", + "При необходимости разрешите повторения.", + "Нажмите «Выбрать» — результаты появятся на экране." + ], + "ruWhenTo": [ + "Розыгрыш победителей конкурса.", + "Случайное распределение задач между участниками команды.", + "Выбор случайного варианта из списка." + ] +} diff --git a/src/content/generators/lottery.json b/src/content/generators/lottery.json new file mode 100644 index 0000000..044461a --- /dev/null +++ b/src/content/generators/lottery.json @@ -0,0 +1,38 @@ +{ + "slug": "lottery", + "title": "Lottery", + "description": "Draw a set of unique numbers for lottery-style picks.", + "icon": "ticket", + "status": "live", + "seoTitle": "Lottery Number Generator — Random Pick | Randify", + "seoDescription": "Draw a set of unique random lottery numbers from any range. Perfect for lotteries, raffles, and lucky draws. Free online lottery picker.", + "ruTitle": "Лотерея", + "ruDescription": "Вытащите набор уникальных чисел в стиле лотереи.", + "ruSeoTitle": "Генератор лотерейных номеров | Randify", + "ruSeoDescription": "Тяните уникальные случайные числа для лотереи, розыгрыша или жеребьёвки. Бесплатный онлайн-генератор лотерейных номеров.", + "pageTitle": "Lottery Generator", + "ruPageTitle": "Генератор лотереи", + "howTo": [ + "Set the From and To fields to define your number range (e.g. 1 to 49).", + "Enter how many numbers to pick in the Pick field.", + "Click Draw — a sorted set of unique numbers appears.", + "Click Copy to copy all numbers to the clipboard." + ], + "whenTo": [ + "Generating lottery or lotto ticket numbers.", + "Running a fair raffle draw with unique entries.", + "Selecting a random sample from a numbered list.", + "Choosing lottery numbers for Powerball, EuroMillions, or local draws." + ], + "ruHowTo": [ + "Укажите диапазон чисел в полях «От» и «До».", + "Задайте количество чисел для розыгрыша.", + "Нажмите «Тянуть» — выпавшие числа появятся на экране.", + "Нажмите «Копировать», чтобы сохранить результат." + ], + "ruWhenTo": [ + "Розыгрыш лотерейных номеров.", + "Жеребьёвка участников конкурса.", + "Случайный выбор нескольких уникальных чисел." + ] +} diff --git a/src/content/generators/numbers.json b/src/content/generators/numbers.json new file mode 100644 index 0000000..0ec7fe3 --- /dev/null +++ b/src/content/generators/numbers.json @@ -0,0 +1,39 @@ +{ + "slug": "numbers", + "title": "Number", + "description": "Pick a random number within any range you choose.", + "icon": "hash", + "status": "live", + "seoTitle": "Random Number Generator | Randify", + "seoDescription": "Generate a random number between any two values instantly. Free online random number picker — perfect for giveaways, games, and decisions.", + "ruTitle": "Число", + "ruDescription": "Выберите случайное число в любом диапазоне.", + "ruSeoTitle": "Генератор случайных чисел | Randify", + "ruSeoDescription": "Генерируйте случайное число в любом диапазоне мгновенно. Идеально для розыгрышей, игр и принятия решений.", + "pageTitle": "Number Generator", + "ruPageTitle": "Генератор чисел", + "howTo": [ + "Enter the minimum value in the From field.", + "Enter the maximum value in the To field.", + "Click Generate — a random number within your range appears instantly.", + "Click the number to copy it to the clipboard." + ], + "whenTo": [ + "Picking a winner in a giveaway or raffle.", + "Choosing a random starting player in a board game.", + "Making a quick decision between numbered options.", + "Generating test data with values in a specific range." + ], + "ruHowTo": [ + "Введите минимальное значение в поле «От».", + "Введите максимальное значение в поле «До».", + "Нажмите «Сгенерировать» — случайное число появится мгновенно.", + "Нажмите на число, чтобы скопировать его в буфер обмена." + ], + "ruWhenTo": [ + "Выбор победителя в розыгрыше или конкурсе.", + "Определение случайного первого игрока в настольной игре.", + "Быстрое принятие решения между пронумерованными вариантами.", + "Генерация тестовых данных с числами в заданном диапазоне." + ] +} diff --git a/src/content/generators/password.json b/src/content/generators/password.json new file mode 100644 index 0000000..3726b70 --- /dev/null +++ b/src/content/generators/password.json @@ -0,0 +1,38 @@ +{ + "slug": "password", + "title": "Password", + "description": "Create a strong, random password with custom rules.", + "icon": "lock", + "status": "live", + "seoTitle": "Random Password Generator | Randify", + "seoDescription": "Create a strong random password with custom length, uppercase, lowercase, digits and symbols. Free and secure — runs entirely in your browser.", + "ruTitle": "Пароль", + "ruDescription": "Создайте надёжный случайный пароль с настраиваемыми правилами.", + "ruSeoTitle": "Генератор случайных паролей | Randify", + "ruSeoDescription": "Создавайте надёжные пароли с заглавными буквами, цифрами и символами. Бесплатно и безопасно — работает прямо в браузере.", + "pageTitle": "Password Generator", + "ruPageTitle": "Генератор паролей", + "howTo": [ + "Drag the length slider to set how many characters you need (4–64).", + "Toggle uppercase, lowercase, digits, and symbols on or off.", + "Click Generate — a new password appears instantly.", + "Click the password to copy it to your clipboard." + ], + "whenTo": [ + "Creating a strong password for a new account.", + "Generating a temporary access credential.", + "Producing a random secret key for development or testing.", + "Replacing a weak or reused password." + ], + "ruHowTo": [ + "Задайте длину пароля с помощью ползунка.", + "Выберите типы символов: заглавные, строчные, цифры, спецсимволы.", + "Нажмите «Сгенерировать» — готовый пароль появится на экране.", + "Нажмите на пароль, чтобы скопировать его." + ], + "ruWhenTo": [ + "Создание надёжного пароля для нового аккаунта.", + "Генерация секретного ключа или токена.", + "Регулярное обновление паролей для повышения безопасности." + ] +} diff --git a/src/content/generators/uuid.json b/src/content/generators/uuid.json new file mode 100644 index 0000000..022c57d --- /dev/null +++ b/src/content/generators/uuid.json @@ -0,0 +1,38 @@ +{ + "slug": "uuid", + "title": "UUID / Token", + "description": "Generate UUIDs, hex tokens, or random base64 strings.", + "icon": "fingerprint", + "status": "live", + "seoTitle": "UUID Generator — UUID v4, Hex & Base64 Tokens | Randify", + "seoDescription": "Generate random UUID v4, hex tokens, or base64 strings online. Cryptographically secure, runs in your browser. Free UUID and token generator.", + "ruTitle": "UUID / Токен", + "ruDescription": "Генерируйте UUID, hex-токены или случайные Base64-строки.", + "ruSeoTitle": "Генератор UUID и токенов | Randify", + "ruSeoDescription": "Генерируйте UUID v4, hex-токены и Base64-строки онлайн. Криптографически безопасно, работает в браузере.", + "pageTitle": "UUID / Token", + "ruPageTitle": "Генератор UUID и токенов", + "howTo": [ + "Choose a token type: UUID v4, Hex, or Base64.", + "For Hex and Base64, set the byte length (4–64 bytes).", + "Enter how many tokens to generate (1–20).", + "Click Generate, then Copy all to copy every token to the clipboard." + ], + "whenTo": [ + "Generating unique IDs for database records or API resources.", + "Creating random session tokens or API keys during development.", + "Producing test data with realistic-looking identifiers.", + "Generating a one-time secret for environment variables or config files." + ], + "ruHowTo": [ + "Выберите тип: UUID v4, Hex или Base64.", + "Для Hex и Base64 задайте длину в байтах.", + "Укажите количество токенов.", + "Нажмите «Сгенерировать» и скопируйте результат." + ], + "ruWhenTo": [ + "Генерация уникальных идентификаторов для баз данных.", + "Создание токенов для API и сессий.", + "Генерация случайных ключей шифрования." + ] +} diff --git a/src/content/generators/wheel.json b/src/content/generators/wheel.json new file mode 100644 index 0000000..833e758 --- /dev/null +++ b/src/content/generators/wheel.json @@ -0,0 +1,38 @@ +{ + "slug": "wheel", + "title": "Spin the Wheel", + "description": "Add your items, spin the wheel, and let chance decide.", + "icon": "pie-chart", + "status": "live", + "seoTitle": "Spin the Wheel — Random Picker | Randify", + "seoDescription": "Spin a customizable wheel of fortune to pick a random winner, choice, or option. Add your own items and let the wheel decide.", + "ruTitle": "Колесо фортуны", + "ruDescription": "Добавьте элементы, крутите колесо и пусть случай решит.", + "ruSeoTitle": "Колесо фортуны онлайн — случайный выбор | Randify", + "ruSeoDescription": "Крутите колесо фортуны онлайн. Добавьте свои варианты и пусть случай решит победителя.", + "pageTitle": "Spin the Wheel", + "ruPageTitle": "Колесо фортуны онлайн", + "howTo": [ + "Type your items into the text box — one per line.", + "Click \"Update wheel\" to apply changes.", + "Hit \"Spin\" and watch the wheel decide.", + "Copy the result or spin again." + ], + "whenTo": [ + "Picking a winner for a giveaway or raffle.", + "Deciding whose turn it is in a game or meeting.", + "Choosing a random activity, movie, or restaurant.", + "Any decision you want to leave to chance." + ], + "ruHowTo": [ + "Введите варианты в поле — по одному на строку (от 2 до 24).", + "Колесо обновляется автоматически.", + "Нажмите «Крутить» и дождитесь результата.", + "Победитель выделяется — скопируйте его кнопкой «Копировать»." + ], + "ruWhenTo": [ + "Случайный выбор победителя розыгрыша.", + "Принятие решений в команде.", + "Распределение ролей или задач между участниками." + ] +} diff --git a/src/data/config.ts b/src/data/config.ts new file mode 100644 index 0000000..5ed5832 --- /dev/null +++ b/src/data/config.ts @@ -0,0 +1,6 @@ +export const siteUrl = 'https://randify.pro'; + +export const analytics = { + yandexMetrikaId: '109130319', + topMailRuId: '3765043', +} as const; diff --git a/src/data/generators.ts b/src/data/generators.ts index 6d60749..ee8f26f 100644 --- a/src/data/generators.ts +++ b/src/data/generators.ts @@ -1,146 +1,21 @@ -export interface Generator { - slug: string; - title: string; - description: string; - icon: string; - status: 'live' | 'coming-soon'; - seoTitle: string; - seoDescription: string; - ruTitle: string; - ruDescription: string; - ruSeoTitle: string; - ruSeoDescription: string; +import { generatorSchema } from "@/lib/generator-schema"; +import type { Generator } from "@/lib/generator-schema"; + +const modules = import.meta.glob<{ default: unknown }>( + "../content/generators/*.json", + { + eager: true, + }, +); + +export const generators: Generator[] = Object.values(modules).map((mod) => + generatorSchema.parse(mod.default), +); + +export function findGenerator(slug: string): Generator { + const g = generators.find((gen) => gen.slug === slug); + if (!g) throw new Error(`Generator not found: ${slug}`); + return g; } -export const generators: Generator[] = [ - { - slug: 'numbers', - title: 'Number', - description: 'Pick a random number within any range you choose.', - icon: 'hash', - status: 'live', - seoTitle: 'Random Number Generator | Randify', - seoDescription: 'Generate a random number between any two values instantly. Free online random number picker — perfect for giveaways, games, and decisions.', - ruTitle: 'Число', - ruDescription: 'Выберите случайное число в любом диапазоне.', - ruSeoTitle: 'Генератор случайных чисел | Randify', - ruSeoDescription: 'Генерируйте случайное число в любом диапазоне мгновенно. Идеально для розыгрышей, игр и принятия решений.', - }, - { - slug: 'dice', - title: 'Dice', - description: 'Roll one or more dice with any number of sides.', - icon: 'dice-6', - status: 'live', - seoTitle: 'Dice Roller — d4 d6 d20 + Full Notation | D&D Dice | Randify', - seoDescription: 'Free online dice roller with full XdY+Z notation, keep/drop (4d6dl1), exploding dice (3d6!), reroll, advantage/disadvantage, and roll history. Perfect for D&D, Pathfinder, and Savage Worlds.', - ruTitle: 'Кубик', - ruDescription: 'Бросьте один или несколько кубиков с любым числом граней.', - ruSeoTitle: 'Бросить кубик онлайн — d4 d6 d8 d10 d12 d20 | D&D | Randify', - ruSeoDescription: 'Бесплатный онлайн-бросок кубиков с полной нотацией XdY+Z, keep/drop (4d6dl1), взрывающимися кубиками (3d6!), перебросом, преимуществом/помехой и историей. Для D&D, Pathfinder и Savage Worlds.', - }, - { - slug: 'wheel', - title: 'Spin the Wheel', - description: 'Add your items, spin the wheel, and let chance decide.', - icon: 'pie-chart', - status: 'live', - seoTitle: 'Spin the Wheel — Random Picker | Randify', - seoDescription: 'Spin a customizable wheel of fortune to pick a random winner, choice, or option. Add your own items and let the wheel decide.', - ruTitle: 'Колесо фортуны', - ruDescription: 'Добавьте элементы, крутите колесо и пусть случай решит.', - ruSeoTitle: 'Колесо фортуны онлайн — случайный выбор | Randify', - ruSeoDescription: 'Крутите колесо фортуны онлайн. Добавьте свои варианты и пусть случай решит победителя.', - }, - { - slug: 'colors', - title: 'Color', - description: 'Generate a random color in HEX, RGB, or HSL format.', - icon: 'palette', - status: 'live', - seoTitle: 'Random Color Generator — HEX, RGB, HSL | Randify', - seoDescription: 'Get a random color in HEX, RGB or HSL format with one click. Free online color randomizer for designers and developers.', - ruTitle: 'Цвет', - ruDescription: 'Генерируйте случайный цвет в форматах HEX, RGB или HSL.', - ruSeoTitle: 'Генератор случайных цветов — HEX, RGB, HSL | Randify', - ruSeoDescription: 'Получайте случайные цвета в форматах HEX, RGB и HSL одним нажатием. Бесплатный рандомайзер цветов для дизайнеров.', - }, - { - slug: 'password', - title: 'Password', - description: 'Create a strong, random password with custom rules.', - icon: 'lock', - status: 'live', - seoTitle: 'Random Password Generator | Randify', - seoDescription: 'Create a strong random password with custom length, uppercase, lowercase, digits and symbols. Free and secure — runs entirely in your browser.', - ruTitle: 'Пароль', - ruDescription: 'Создайте надёжный случайный пароль с настраиваемыми правилами.', - ruSeoTitle: 'Генератор случайных паролей | Randify', - ruSeoDescription: 'Создавайте надёжные пароли с заглавными буквами, цифрами и символами. Бесплатно и безопасно — работает прямо в браузере.', - }, - { - slug: 'lottery', - title: 'Lottery', - description: 'Draw a set of unique numbers for lottery-style picks.', - icon: 'ticket', - status: 'live', - seoTitle: 'Lottery Number Generator — Random Pick | Randify', - seoDescription: 'Draw a set of unique random lottery numbers from any range. Perfect for lotteries, raffles, and lucky draws. Free online lottery picker.', - ruTitle: 'Лотерея', - ruDescription: 'Вытащите набор уникальных чисел в стиле лотереи.', - ruSeoTitle: 'Генератор лотерейных номеров | Randify', - ruSeoDescription: 'Тяните уникальные случайные числа для лотереи, розыгрыша или жеребьёвки. Бесплатный онлайн-генератор лотерейных номеров.', - }, - { - slug: 'cards', - title: 'Card', - description: 'Draw a random playing card from a standard deck.', - icon: 'square-stack', - status: 'live', - seoTitle: 'Random Card Generator — Draw from a Deck | Randify', - seoDescription: 'Draw random playing cards from a standard 52-card deck. Pick any number of cards, with or without replacement. Free online card picker.', - ruTitle: 'Карта', - ruDescription: 'Вытащите случайную карту из стандартной колоды.', - ruSeoTitle: 'Генератор случайных карт | Randify', - ruSeoDescription: 'Тяните случайные карты из колоды 52 карт. С повторениями или без. Бесплатный онлайн-генератор карт.', - }, - { - slug: 'coin', - title: 'Coin Flip', - description: 'Flip one or more coins and see heads or tails.', - icon: 'circle-dollar-sign', - status: 'live', - seoTitle: 'Coin Flip Online — Heads or Tails | Randify', - seoDescription: 'Flip a virtual coin online. Flip multiple coins at once and see how many heads and tails you get. Free random coin toss simulator.', - ruTitle: 'Монетка', - ruDescription: 'Подбросьте одну или несколько монет — орёл или решка.', - ruSeoTitle: 'Подбросить монетку онлайн — орёл или решка | Randify', - ruSeoDescription: 'Подбрасывайте виртуальную монету онлайн. Одну или несколько сразу. Бесплатный симулятор подбрасывания монеты.', - }, - { - slug: 'list', - title: 'List Picker', - description: 'Paste a list of items and pick random winners.', - icon: 'list', - status: 'live', - seoTitle: 'Random List Picker — Pick a Random Winner | Randify', - seoDescription: 'Paste any list of names or items and pick random winners instantly. Great for giveaways, choosing who goes first, or any random selection.', - ruTitle: 'Список', - ruDescription: 'Вставьте список элементов и выберите случайных победителей.', - ruSeoTitle: 'Случайный выбор из списка | Randify', - ruSeoDescription: 'Вставьте список имён или вариантов и выберите случайных победителей мгновенно. Идеально для розыгрышей и жеребьёвок.', - }, - { - slug: 'uuid', - title: 'UUID / Token', - description: 'Generate UUIDs, hex tokens, or random base64 strings.', - icon: 'fingerprint', - status: 'live', - seoTitle: 'UUID Generator — UUID v4, Hex & Base64 Tokens | Randify', - seoDescription: 'Generate random UUID v4, hex tokens, or base64 strings online. Cryptographically secure, runs in your browser. Free UUID and token generator.', - ruTitle: 'UUID / Токен', - ruDescription: 'Генерируйте UUID, hex-токены или случайные Base64-строки.', - ruSeoTitle: 'Генератор UUID и токенов | Randify', - ruSeoDescription: 'Генерируйте UUID v4, hex-токены и Base64-строки онлайн. Криптографически безопасно, работает в браузере.', - }, -]; +export type { Generator }; diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 3a57537..f7cde42 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -1,107 +1,93 @@ --- -import LanguageSwitcher from '../components/LanguageSwitcher.astro'; -import { useT } from '../i18n/translations'; -import type { Lang } from '../i18n/translations'; +import { getRelativeLocaleUrl } from "astro:i18n"; +import LanguageSwitcher from "../components/LanguageSwitcher.astro"; +import Analytics from "../components/Analytics.astro"; +import { useT } from "../i18n/translations"; +import type { Lang } from "../i18n/translations"; interface Props { - title?: string; - description?: string; - lang?: Lang; + title?: string; + description?: string; } -const { lang = 'en' } = Astro.props; +const lang = (Astro.currentLocale as Lang) || "en"; const T = useT(lang); -const { - title = T.defaultTitle, - description = T.defaultDesc, -} = Astro.props; +const { title = T.defaultTitle, description = T.defaultDesc } = Astro.props; -const currentPath = Astro.url.pathname; -const alternatePath = lang === 'ru' - ? (currentPath.replace(/^\/ru/, '') || '/') - : ('/ru' + currentPath); +const pathWithoutLocale = Astro.url.pathname.replace(/^\/ru/, "") || "/"; +const enUrl = `https://randify.pro${getRelativeLocaleUrl("en", pathWithoutLocale)}`; +const ruUrl = `https://randify.pro${getRelativeLocaleUrl("ru", pathWithoutLocale)}`; --- - - - - - - - - - - - - {title} - - - - - - - - - - - - - + + + + + + + + + + + + {title} + + + + + + diff --git a/src/layouts/GeneratorLayout.astro b/src/layouts/GeneratorLayout.astro new file mode 100644 index 0000000..89e637e --- /dev/null +++ b/src/layouts/GeneratorLayout.astro @@ -0,0 +1,76 @@ +--- +import BaseLayout from './BaseLayout.astro'; +import AdBanner from '../components/AdBanner.astro'; +import SeoBlock from '../components/SeoBlock.astro'; +import { useT } from '../i18n/translations'; +import type { Lang } from '../i18n/translations'; +import type { Generator } from '../data/generators'; + +interface Props { + generator: Generator; +} + +const { generator } = Astro.props; +const lang = (Astro.currentLocale as Lang) || 'en'; +const T = useT(lang); +const isRu = lang === 'ru'; +--- + + +
+ + +
+
+ + randify +
+

+ {isRu ? generator.ruPageTitle : generator.pageTitle} +

+

{isRu ? generator.ruDescription : generator.description}

+
+ +
+ +
+ +
+ +
+ + +
+
diff --git a/src/lib/generator-schema.ts b/src/lib/generator-schema.ts new file mode 100644 index 0000000..943d2e9 --- /dev/null +++ b/src/lib/generator-schema.ts @@ -0,0 +1,25 @@ +import { z } from "zod"; + +export const generatorSchema = z + .object({ + slug: z.string(), + title: z.string(), + description: z.string(), + icon: z.string(), + status: z.enum(["live", "coming-soon"]), + seoTitle: z.string(), + seoDescription: z.string(), + ruTitle: z.string(), + ruDescription: z.string(), + ruSeoTitle: z.string(), + ruSeoDescription: z.string(), + pageTitle: z.string(), + ruPageTitle: z.string(), + howTo: z.array(z.string()), + whenTo: z.array(z.string()), + ruHowTo: z.array(z.string()), + ruWhenTo: z.array(z.string()), + }) + .strict(); + +export type Generator = z.infer; diff --git a/src/pages/generators/cards.astro b/src/pages/generators/cards.astro index 9f19a5d..601a1ff 100644 --- a/src/pages/generators/cards.astro +++ b/src/pages/generators/cards.astro @@ -1,77 +1,11 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import CardGenerator from '../../components/generators/CardGenerator.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import { generators } from '../../data/generators'; -import SeoBlock from '../../components/SeoBlock.astro'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import CardGenerator from "@/components/generators/CardGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'cards')!; +const generator = generators.find((g) => g.slug === "cards")!; --- - -
- - -
-
- - randify -
-

- {generator.title} Generator -

-

{generator.description}

-
- -
- -
- -
- -
- - -
-
+ + + diff --git a/src/pages/generators/coin.astro b/src/pages/generators/coin.astro index 493392e..3affc26 100644 --- a/src/pages/generators/coin.astro +++ b/src/pages/generators/coin.astro @@ -1,56 +1,11 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import CoinGenerator from '../../components/generators/CoinGenerator.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import { generators } from '../../data/generators'; -import SeoBlock from '../../components/SeoBlock.astro'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import CoinGenerator from "@/components/generators/CoinGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'coin')!; +const generator = generators.find((g) => g.slug === "coin")!; --- - -
- -
-
- - randify -
-

{generator.title}

-

{generator.description}

-
-
-
- -
- - -
-
+ + + diff --git a/src/pages/generators/colors.astro b/src/pages/generators/colors.astro index 28b41a9..5ab6aac 100644 --- a/src/pages/generators/colors.astro +++ b/src/pages/generators/colors.astro @@ -1,76 +1,11 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import ColorGenerator from '../../components/generators/ColorGenerator.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import { generators } from '../../data/generators'; -import SeoBlock from '../../components/SeoBlock.astro'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import ColorGenerator from "@/components/generators/ColorGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'colors')!; +const generator = generators.find((g) => g.slug === "colors")!; --- - -
- - -
-
- - randify -
-

- {generator.title} Generator -

-

{generator.description}

-
- -
- -
- -
- -
- - -
-
+ + + diff --git a/src/pages/generators/dice.astro b/src/pages/generators/dice.astro index 19a90c3..96e2fd5 100644 --- a/src/pages/generators/dice.astro +++ b/src/pages/generators/dice.astro @@ -1,79 +1,11 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import DiceGenerator from '../../components/generators/DiceGenerator.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import { generators } from '../../data/generators'; -import SeoBlock from '../../components/SeoBlock.astro'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import DiceGenerator from "@/components/generators/DiceGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'dice')!; +const generator = generators.find((g) => g.slug === "dice")!; --- - -
- - -
-
- - randify -
-

- {generator.title} Generator -

-

{generator.description}

-
- -
- -
- -
- -
- - -
-
+ + + diff --git a/src/pages/generators/list.astro b/src/pages/generators/list.astro index 4b4d2f4..031a112 100644 --- a/src/pages/generators/list.astro +++ b/src/pages/generators/list.astro @@ -1,56 +1,11 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import ListGenerator from '../../components/generators/ListGenerator.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import { generators } from '../../data/generators'; -import SeoBlock from '../../components/SeoBlock.astro'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import ListGenerator from "@/components/generators/ListGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'list')!; +const generator = generators.find((g) => g.slug === "list")!; --- - -
- -
-
- - randify -
-

{generator.title}

-

{generator.description}

-
-
-
- -
- - -
-
+ + + diff --git a/src/pages/generators/lottery.astro b/src/pages/generators/lottery.astro index f295e41..47c78e5 100644 --- a/src/pages/generators/lottery.astro +++ b/src/pages/generators/lottery.astro @@ -1,77 +1,11 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import LotteryGenerator from '../../components/generators/LotteryGenerator.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import { generators } from '../../data/generators'; -import SeoBlock from '../../components/SeoBlock.astro'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import LotteryGenerator from "@/components/generators/LotteryGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'lottery')!; +const generator = generators.find((g) => g.slug === "lottery")!; --- - -
- - -
-
- - randify -
-

- {generator.title} Generator -

-

{generator.description}

-
- -
- -
- -
- -
- - -
-
+ + + diff --git a/src/pages/generators/numbers.astro b/src/pages/generators/numbers.astro index 2ecc23e..d1c9fd2 100644 --- a/src/pages/generators/numbers.astro +++ b/src/pages/generators/numbers.astro @@ -1,77 +1,11 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import NumberGenerator from '../../components/generators/NumberGenerator.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import { generators } from '../../data/generators'; -import SeoBlock from '../../components/SeoBlock.astro'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import NumberGenerator from "@/components/generators/NumberGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'numbers')!; +const generator = generators.find((g) => g.slug === "numbers")!; --- - -
- - -
-
- - randify -
-

- {generator.title} Generator -

-

{generator.description}

-
- -
- -
- -
- -
- - -
-
+ + + diff --git a/src/pages/generators/password.astro b/src/pages/generators/password.astro index 6e33fdb..43233ad 100644 --- a/src/pages/generators/password.astro +++ b/src/pages/generators/password.astro @@ -1,77 +1,11 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import PasswordGenerator from '../../components/generators/PasswordGenerator.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import { generators } from '../../data/generators'; -import SeoBlock from '../../components/SeoBlock.astro'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import PasswordGenerator from "@/components/generators/PasswordGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'password')!; +const generator = generators.find((g) => g.slug === "password")!; --- - -
- - -
-
- - randify -
-

- {generator.title} Generator -

-

{generator.description}

-
- -
- -
- -
- -
- - -
-
+ + + diff --git a/src/pages/generators/uuid.astro b/src/pages/generators/uuid.astro index fd56329..b28faaa 100644 --- a/src/pages/generators/uuid.astro +++ b/src/pages/generators/uuid.astro @@ -1,56 +1,11 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import UuidGenerator from '../../components/generators/UuidGenerator.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import { generators } from '../../data/generators'; -import SeoBlock from '../../components/SeoBlock.astro'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import UuidGenerator from "@/components/generators/UuidGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'uuid')!; +const generator = generators.find((g) => g.slug === "uuid")!; --- - -
- -
-
- - randify -
-

{generator.title}

-

{generator.description}

-
-
-
- -
- - -
-
+ + + diff --git a/src/pages/generators/wheel.astro b/src/pages/generators/wheel.astro index 5dd9b3c..921d2eb 100644 --- a/src/pages/generators/wheel.astro +++ b/src/pages/generators/wheel.astro @@ -1,77 +1,11 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import WheelSpinner from '../../components/generators/WheelSpinner.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import SeoBlock from '../../components/SeoBlock.astro'; -import { generators } from '../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import WheelSpinner from "@/components/generators/WheelSpinner.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'wheel')!; +const generator = generators.find((g) => g.slug === "wheel")!; --- - -
- - -
-
- - randify -
-

- {generator.title} -

-

{generator.description}

-
- -
- -
- -
- -
- - -
-
+ + + diff --git a/src/pages/index.astro b/src/pages/index.astro index 71ef7cf..7ce753a 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,46 +1,53 @@ --- -import BaseLayout from '../layouts/BaseLayout.astro'; -import GeneratorCard from '../components/GeneratorCard.astro'; -import AdBanner from '../components/AdBanner.astro'; -import { generators } from '../data/generators'; -import { useT } from '../i18n/translations'; +import BaseLayout from "@/layouts/BaseLayout.astro"; +import GeneratorCard from "@/components/GeneratorCard.astro"; +import AdBanner from "@/components/AdBanner.astro"; +import { generators } from "@/data/generators"; +import { useT } from "@/i18n/translations"; +import type { Lang } from "@/i18n/translations"; -const T = useT('en'); +const lang = (Astro.currentLocale as Lang) || "en"; +const T = useT(lang); --- - -
-
-
- - randify -
-

- {T.homeTitle} -

-

- {T.homeSubtitle} -

-
+ +
+
+
+ + {T.brandLabel} +
+

+ {T.homeTitle} +

+

+ {T.homeSubtitle} +

+
- + -
-
- {generators.map((generator) => ( -
- -
- ))} -
-
-
+
+
+ { + generators.map((generator) => ( +
+ +
+ )) + } +
+
+
diff --git a/src/pages/ru/generators/cards.astro b/src/pages/ru/generators/cards.astro index 3f5d976..601a1ff 100644 --- a/src/pages/ru/generators/cards.astro +++ b/src/pages/ru/generators/cards.astro @@ -1,40 +1,11 @@ --- -import BaseLayout from '../../../layouts/BaseLayout.astro'; -import CardGenerator from '../../../components/generators/CardGenerator.astro'; -import AdBanner from '../../../components/AdBanner.astro'; -import SeoBlock from '../../../components/SeoBlock.astro'; -import { generators } from '../../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import CardGenerator from "@/components/generators/CardGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'cards')!; +const generator = generators.find((g) => g.slug === "cards")!; --- - -
- -
-
- - randify -
-

Генератор карт

-

{generator.ruDescription}

-
-
-
- -
-
+ + + diff --git a/src/pages/ru/generators/coin.astro b/src/pages/ru/generators/coin.astro index 9399ebc..3affc26 100644 --- a/src/pages/ru/generators/coin.astro +++ b/src/pages/ru/generators/coin.astro @@ -1,40 +1,11 @@ --- -import BaseLayout from '../../../layouts/BaseLayout.astro'; -import CoinGenerator from '../../../components/generators/CoinGenerator.astro'; -import AdBanner from '../../../components/AdBanner.astro'; -import SeoBlock from '../../../components/SeoBlock.astro'; -import { generators } from '../../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import CoinGenerator from "@/components/generators/CoinGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'coin')!; +const generator = generators.find((g) => g.slug === "coin")!; --- - -
- -
-
- - randify -
-

Подбросить монетку

-

{generator.ruDescription}

-
-
-
- -
-
+ + + diff --git a/src/pages/ru/generators/colors.astro b/src/pages/ru/generators/colors.astro index 039f4ca..5ab6aac 100644 --- a/src/pages/ru/generators/colors.astro +++ b/src/pages/ru/generators/colors.astro @@ -1,39 +1,11 @@ --- -import BaseLayout from '../../../layouts/BaseLayout.astro'; -import ColorGenerator from '../../../components/generators/ColorGenerator.astro'; -import AdBanner from '../../../components/AdBanner.astro'; -import SeoBlock from '../../../components/SeoBlock.astro'; -import { generators } from '../../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import ColorGenerator from "@/components/generators/ColorGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'colors')!; +const generator = generators.find((g) => g.slug === "colors")!; --- - -
- -
-
- - randify -
-

Генератор цветов

-

{generator.ruDescription}

-
-
-
- -
-
+ + + diff --git a/src/pages/ru/generators/dice.astro b/src/pages/ru/generators/dice.astro index 9787670..96e2fd5 100644 --- a/src/pages/ru/generators/dice.astro +++ b/src/pages/ru/generators/dice.astro @@ -1,43 +1,11 @@ --- -import BaseLayout from '../../../layouts/BaseLayout.astro'; -import DiceGenerator from '../../../components/generators/DiceGenerator.astro'; -import AdBanner from '../../../components/AdBanner.astro'; -import SeoBlock from '../../../components/SeoBlock.astro'; -import { generators } from '../../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import DiceGenerator from "@/components/generators/DiceGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'dice')!; +const generator = generators.find((g) => g.slug === "dice")!; --- - -
- -
-
- - randify -
-

Бросить кубик онлайн

-

{generator.ruDescription}

-
-
-
- -
-
+ + + diff --git a/src/pages/ru/generators/list.astro b/src/pages/ru/generators/list.astro index 00ef839..031a112 100644 --- a/src/pages/ru/generators/list.astro +++ b/src/pages/ru/generators/list.astro @@ -1,40 +1,11 @@ --- -import BaseLayout from '../../../layouts/BaseLayout.astro'; -import ListGenerator from '../../../components/generators/ListGenerator.astro'; -import AdBanner from '../../../components/AdBanner.astro'; -import SeoBlock from '../../../components/SeoBlock.astro'; -import { generators } from '../../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import ListGenerator from "@/components/generators/ListGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'list')!; +const generator = generators.find((g) => g.slug === "list")!; --- - -
- -
-
- - randify -
-

Случайный выбор из списка

-

{generator.ruDescription}

-
-
-
- -
-
+ + + diff --git a/src/pages/ru/generators/lottery.astro b/src/pages/ru/generators/lottery.astro index 6419aa5..47c78e5 100644 --- a/src/pages/ru/generators/lottery.astro +++ b/src/pages/ru/generators/lottery.astro @@ -1,40 +1,11 @@ --- -import BaseLayout from '../../../layouts/BaseLayout.astro'; -import LotteryGenerator from '../../../components/generators/LotteryGenerator.astro'; -import AdBanner from '../../../components/AdBanner.astro'; -import SeoBlock from '../../../components/SeoBlock.astro'; -import { generators } from '../../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import LotteryGenerator from "@/components/generators/LotteryGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'lottery')!; +const generator = generators.find((g) => g.slug === "lottery")!; --- - -
- -
-
- - randify -
-

Генератор лотереи

-

{generator.ruDescription}

-
-
-
- -
-
+ + + diff --git a/src/pages/ru/generators/numbers.astro b/src/pages/ru/generators/numbers.astro index 3a1871f..d1c9fd2 100644 --- a/src/pages/ru/generators/numbers.astro +++ b/src/pages/ru/generators/numbers.astro @@ -1,41 +1,11 @@ --- -import BaseLayout from '../../../layouts/BaseLayout.astro'; -import NumberGenerator from '../../../components/generators/NumberGenerator.astro'; -import AdBanner from '../../../components/AdBanner.astro'; -import SeoBlock from '../../../components/SeoBlock.astro'; -import { generators } from '../../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import NumberGenerator from "@/components/generators/NumberGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'numbers')!; +const generator = generators.find((g) => g.slug === "numbers")!; --- - -
- -
-
- - randify -
-

Генератор чисел

-

{generator.ruDescription}

-
-
-
- -
-
+ + + diff --git a/src/pages/ru/generators/password.astro b/src/pages/ru/generators/password.astro index e41ff34..43233ad 100644 --- a/src/pages/ru/generators/password.astro +++ b/src/pages/ru/generators/password.astro @@ -1,40 +1,11 @@ --- -import BaseLayout from '../../../layouts/BaseLayout.astro'; -import PasswordGenerator from '../../../components/generators/PasswordGenerator.astro'; -import AdBanner from '../../../components/AdBanner.astro'; -import SeoBlock from '../../../components/SeoBlock.astro'; -import { generators } from '../../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import PasswordGenerator from "@/components/generators/PasswordGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'password')!; +const generator = generators.find((g) => g.slug === "password")!; --- - -
- -
-
- - randify -
-

Генератор паролей

-

{generator.ruDescription}

-
-
-
- -
-
+ + + diff --git a/src/pages/ru/generators/uuid.astro b/src/pages/ru/generators/uuid.astro index d5c61b3..b28faaa 100644 --- a/src/pages/ru/generators/uuid.astro +++ b/src/pages/ru/generators/uuid.astro @@ -1,40 +1,11 @@ --- -import BaseLayout from '../../../layouts/BaseLayout.astro'; -import UuidGenerator from '../../../components/generators/UuidGenerator.astro'; -import AdBanner from '../../../components/AdBanner.astro'; -import SeoBlock from '../../../components/SeoBlock.astro'; -import { generators } from '../../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import UuidGenerator from "@/components/generators/UuidGenerator.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'uuid')!; +const generator = generators.find((g) => g.slug === "uuid")!; --- - -
- -
-
- - randify -
-

Генератор UUID и токенов

-

{generator.ruDescription}

-
-
-
- -
-
+ + + diff --git a/src/pages/ru/generators/wheel.astro b/src/pages/ru/generators/wheel.astro index bafb5f3..921d2eb 100644 --- a/src/pages/ru/generators/wheel.astro +++ b/src/pages/ru/generators/wheel.astro @@ -1,40 +1,11 @@ --- -import BaseLayout from '../../../layouts/BaseLayout.astro'; -import WheelSpinner from '../../../components/generators/WheelSpinner.astro'; -import AdBanner from '../../../components/AdBanner.astro'; -import SeoBlock from '../../../components/SeoBlock.astro'; -import { generators } from '../../../data/generators'; +import GeneratorLayout from "@/layouts/GeneratorLayout.astro"; +import WheelSpinner from "@/components/generators/WheelSpinner.astro"; +import { generators } from "@/data/generators"; -const generator = generators.find((g) => g.slug === 'wheel')!; +const generator = generators.find((g) => g.slug === "wheel")!; --- - -
- -
-
- - randify -
-

Колесо фортуны онлайн

-

{generator.ruDescription}

-
-
-
- -
-
+ + + diff --git a/src/pages/ru/index.astro b/src/pages/ru/index.astro index 8b79dfc..7ce753a 100644 --- a/src/pages/ru/index.astro +++ b/src/pages/ru/index.astro @@ -1,50 +1,53 @@ --- -import BaseLayout from '../../layouts/BaseLayout.astro'; -import GeneratorCard from '../../components/GeneratorCard.astro'; -import AdBanner from '../../components/AdBanner.astro'; -import { generators } from '../../data/generators'; -import { useT } from '../../i18n/translations'; +import BaseLayout from "@/layouts/BaseLayout.astro"; +import GeneratorCard from "@/components/GeneratorCard.astro"; +import AdBanner from "@/components/AdBanner.astro"; +import { generators } from "@/data/generators"; +import { useT } from "@/i18n/translations"; +import type { Lang } from "@/i18n/translations"; -const T = useT('ru'); +const lang = (Astro.currentLocale as Lang) || "en"; +const T = useT(lang); --- - -
-
-
- - {T.brandLabel} -
-

- {T.homeTitle} -

-

- {T.homeSubtitle} -

-
+ +
+
+
+ + {T.brandLabel} +
+

+ {T.homeTitle} +

+

+ {T.homeSubtitle} +

+
- + -
-
- {generators.map((generator) => ( -
- -
- ))} -
-
-
+
+
+ { + generators.map((generator) => ( +
+ +
+ )) + } +
+
+