agent-ecosystem/landing/docs/LANDING_PLAN.md
iliya e6e89d4ebc fix(tests): improve messageId generation for legacy inbox rows
- Enhanced tests to ensure consistent messageId generation for legacy inbox rows lacking a messageId.
- Updated test descriptions for better clarity regarding the new messageId handling.
- Adjusted test expectations to align with the updated behavior of relaying legacy inbox rows with generated messageIds.
2026-03-23 16:31:37 +02:00

1027 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# План разработки лендинга для Voice-to-Text
## Обзор проекта
**Voice-to-Text** — десктопное приложение для преобразования голоса в текст с фокусом на приватность и офлайн-поддержку. Построено на Tauri, Rust и Vue 3.
### Ключевые особенности приложения:
- 🎤 **Real-time транскрипция** с поддержкой нескольких провайдеров (Deepgram, AssemblyAI, Whisper)
- 🔒 **Privacy-focused** — API ключи хранятся локально, нет облачного хранилища
- 🌍 **Кроссплатформенность** — macOS, Windows, Linux
-**Глобальные хоткеи** — быстрый доступ через горячие клавиши
- 📋 **Автоматическое копирование** в буфер обмена
- 🎨 **Современный UI** с glass morphism эффектами
- 🌐 **Мультиязычность** — поддержка 6 языков (en, ru, es, fr, de, uk)
---
## Технологический стек
### Основные технологии:
- **Nuxt 3** — фреймворк для Vue.js с SSR/SSG
- **Vuetify 3** — Material Design компоненты
- **Vue I18n** — локализация (совместимо с текущим `vue-i18n`)
- **TypeScript** — типизация
- **Vite** — сборщик (встроен в Nuxt)
- **Pinia** — управление общим состоянием (через `@pinia/nuxt`)
### Дополнительные библиотеки:
- **@nuxtjs/i18n** — интеграция i18n с Nuxt
- **@nuxtjs/seo** — SEO оптимизация
- **@vueuse/nuxt** — композаблы для Vue (например `usePreferredDark`)
- **nuxt-icon** — иконки
- **@nuxtjs/ipx** — обработка изображений (опционально)
- **swiper** — карусель скриншотов (Vue интеграция)
- **@nuxt/eslint** + **Prettier** — линтинг и форматирование кода
Примечания по зависимостям:
- Google Fonts лучше не подключать как внешнюю зависимость. Либо системный шрифт, либо self-host (проще с приватностью и стабильностью).
---
## Архитектура проекта
```
landing/
├── data/ # статические данные/конфиги секций (без Nuxt контекста)
├── types/ # TS типы (без Nuxt контекста)
├── utils/ # чистые функции (без Nuxt контекста, легко тестировать)
├── nuxt.config.ts # Конфигурация Nuxt
├── package.json
├── tsconfig.json
├── .env # Переменные окружения
├── locales/ # Файлы локализации
│ ├── en.json
│ ├── ru.json
│ ├── es.json
│ ├── fr.json
│ ├── de.json
│ └── uk.json
├── assets/ # Статические ресурсы
│ ├── images/
│ │ ├── hero-bg.jpg
│ │ ├── screenshot-dark.png
│ │ ├── screenshot-light.png
│ │ ├── features/
│ │ └── platforms/
│ ├── videos/ # Демо-видео (опционально)
│ └── styles/
│ └── main.scss # Глобальные стили
├── components/ # Vue компоненты
│ ├── layout/
│ │ ├── AppHeader.vue # Шапка с навигацией
│ │ ├── AppFooter.vue # Подвал
│ │ └── LanguageSwitcher.vue # Переключатель языков
│ │
│ ├── sections/
│ │ ├── HeroSection.vue # Главный экран
│ │ ├── FeaturesSection.vue # Особенности
│ │ ├── ProvidersSection.vue # STT провайдеры
│ │ ├── ScreenshotsSection.vue # Скриншоты
│ │ ├── DownloadSection.vue # Секция загрузки
│ │ ├── PrivacySection.vue # Приватность
│ │ └── FAQSection.vue # Частые вопросы
│ │
│ ├── ui/
│ │ ├── DownloadButton.vue # Кнопка загрузки
│ │ ├── FeatureCard.vue # Карточка фичи
│ │ ├── PlatformBadge.vue # Бейдж платформы
│ │ └── ScreenshotCarousel.vue # Карусель скриншотов
│ │
│ └── common/
│ ├── AppLogo.vue
│ └── ThemeToggle.vue # Переключатель темы (если нужен)
├── composables/ # Композаблы
│ ├── useDownload.ts # Логика загрузки
│ ├── useAnalytics.ts # Аналитика (опционально)
│ ├── usePlatform.ts # Определение платформы
│ ├── useBrowserTheme.ts # Определение темы браузера
│ └── useLocation.ts # Определение локации пользователя
├── layouts/
│ └── default.vue # Основной layout
├── plugins/ # Плагины Nuxt
│ ├── vuetify.ts # Инициализация Vuetify
│ └── init-theme-locale.client.ts # Автоинициализация темы и локали
├── pages/
│ ├── index.vue # Главная страница
│ ├── download.vue # Страница загрузки
│ └── privacy.vue # Политика приватности (опционально)
├── public/ # Публичные файлы
│ ├── favicon.ico
│ ├── robots.txt
│ └── sitemap.xml
└── server/ # Server API (если нужен)
```
---
## Правила разделения логики
- **composables/**: завязаны на Nuxt/Vue (реактивность, `useCookie`, `navigateTo`, `useRoute`, `useFetch`, `useHead` и т.д.)
- **utils/**: чистые функции без Nuxt контекста (легко тестировать, переиспользовать)
## Структура страниц
### 1. Главная страница (`/`)
#### Hero Section
- **Заголовок**: "Voice to Text — Privacy-Focused Transcription"
- **Подзаголовок**: Краткое описание приложения
- **CTA кнопки**:
- "Download for [Platform]" (определяется автоматически)
- "View Features" (скролл к секции)
- **Фоновое изображение/видео**: Демонстрация приложения
#### Features Section
**6 основных фич в виде карточек:**
1. **Real-time Transcription**
- Иконка: 🎤
- Описание: Мгновенная транскрипция с частичными результатами
2. **Privacy-Focused**
- Иконка: 🔒
- Описание: API ключи хранятся локально, нет облачного хранилища
3. **Multiple Providers**
- Иконка: 🌐
- Описание: Deepgram, AssemblyAI, Whisper (офлайн)
4. **Cross-Platform**
- Иконка: 💻
- Описание: macOS, Windows, Linux
5. **Global Hotkeys**
- Иконка: ⌨️
- Описание: Быстрый доступ через горячие клавиши
6. **Auto-Copy**
- Иконка: 📋
- Описание: Автоматическое копирование в буфер обмена
#### Providers Section
**Детальное описание STT провайдеров:**
- **Deepgram** (Nova-2/3)
- Низкая задержка
- Высокое качество
- Автоматический выбор модели (Nova-3 для английского, Nova-2 для русского)
- **AssemblyAI** (Universal-Streaming v3)
- Высокое качество
- Облачный сервис
- **Whisper Local** (офлайн)
- Полностью офлайн
- Требует cmake и загрузки модели
#### Screenshots Section
**Карусель скриншотов (Swiper):**
- Используем Swiper для Vue: `https://swiperjs.com/vue`
- На **десктопе** должно быть видно **сразу несколько** скриншотов на экране (не один с обязательным “далее”)
- Пример: `slidesPerView: 2-4` на широких экранах с `breakpoints`
- Допускается режим с “частичным” превью следующего слайда
- На мобильных: 1 скрин, свайп, пагинация
- Набор скринов:
- Темная тема
- Светлая тема
- Настройки
- Процесс записи
#### Download Section
**Платформо-специфичные кнопки загрузки с автоопределением ОС:**
- Определяем текущую ОС пользователя и показываем **приоритетно** релевантную загрузку
- Если ОС определить не удалось — показываем **все** ОС
- Для macOS учитывать архитектуру (Apple Silicon vs Intel):
- Если получилось определить — выбираем нужную сборку по умолчанию
- Если нет — показываем оба варианта и даём выбрать вручную
**Дополнительно:**
- Версия приложения
- Размер файла
- Системные требования
- Changelog ссылка
#### Privacy Section
**Ключевые моменты приватности:**
- Локальное хранение API ключей
- Нет облачного хранилища транскрипций
- Опциональное использование собственных API ключей
**Open Source:**
- Да: часть компонентов/модулей планируем сделать open-source (для маркетинга и доверия)
#### FAQ Section
**Частые вопросы:**
1. Какие платформы поддерживаются?
2. Нужен ли интернет для работы?
3. Как настроить API ключи?
4. Можно ли использовать офлайн?
5. Как изменить горячие клавиши?
6. Безопасны ли мои данные?
### 2. Страница загрузки (`/download`)
- **Определение платформы** автоматически
- **Кнопки загрузки** для всех платформ
- **Инструкции по установке** для каждой ОС
- **Системные требования**
- **Changelog** (последние версии)
---
## Локализация (i18n)
### Поддерживаемые языки:
- 🇺🇸 English (`en`)
- 🇷🇺 Русский (`ru`)
- 🇪🇸 Español (`es`)
- 🇫🇷 Français (`fr`)
- 🇩🇪 Deutsch (`de`)
- 🇺🇦 Українська (`uk`)
### Структура файлов локализации:
```json
// locales/en.json
{
"meta": {
"title": "Voice to Text - Privacy-Focused Transcription",
"description": "..."
},
"nav": {
"features": "Features",
"download": "Download",
"privacy": "Privacy"
},
"hero": {
"title": "Voice to Text",
"subtitle": "Privacy-focused voice-to-text application with offline support",
"download": "Download for {platform}",
"viewFeatures": "View Features"
},
"features": {
"realtime": {
"title": "Real-time Transcription",
"description": "..."
},
"privacy": {
"title": "Privacy-Focused",
"description": "..."
}
// ... остальные фичи
},
"providers": {
"title": "Multiple STT Providers",
"deepgram": {
"name": "Deepgram",
"description": "..."
}
// ... остальные провайдеры
},
"download": {
"title": "Download",
"forPlatform": "Download for {platform}",
"systemRequirements": "System Requirements",
"version": "Version {version}"
},
"faq": {
"title": "Frequently Asked Questions",
"items": [
{
"question": "...",
"answer": "..."
}
]
}
}
```
### Модель контента (чтобы i18n не превращалась в ад)
Проблема классическая: если хранить “структуру секций” в `data/*`, а весь контент раскидать по ключам i18n, то любая правка превращается в квест “найди 20 ключей в 6 языках”.
Решение — разделить два слоя:
- **Микрокопирайт** (кнопки, лейблы, мелкие подписи) — остаётся в `landing/locales/*`.
- **Контент секций** (FAQ, список фич, провайдеры, тексты блоков) — лежит в **локализованных контент-файлах** с одинаковой структурой по всем языкам.
Рекомендуемая схема:
- `landing/content/en.ts`, `landing/content/ru.ts`, ... (или `.json`, если удобнее).
- Внутри — один типизированный объект `LandingContent`, где:
- элементы имеют **стабильные `id`** (например `faq.items[].id`, `features.items[].id`)
- порядок можно менять без рефакторинга компонентов
- длинные тексты редактируются “в одном месте” для каждой локали
Минимальные правила дисциплины:
- `id` неизменяемы (меняем текст, но не идентификатор).
- Если добавили/удалили элемент — правим **все локали** (это легко проверяется автоматикой).
- Для контента не используем “вложенные ключи на 10 уровней”, держим структуру простой и читаемой.
Проверка качества:
- Добавляем маленькую проверку (скрипт/тест), которая сравнивает структуру контента между локалями и падает, если где-то не хватает ключей/элементов.
Минимальный “контракт” этой проверки:
- сравниваем структуру (ключи/массивы по `id`), а не тексты
- ошибка должна показывать, **какой `id`/ключ отсутствует** и **в какой локали**
- проверка запускается локально и в CI (чтобы не ловить это уже на проде)
### Настройка i18n в Nuxt:
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxtjs/i18n',
'vuetify/nuxt'
],
i18n: {
locales: [
{ code: 'en', iso: 'en-US', file: 'en.json', name: 'English' },
{ code: 'ru', iso: 'ru-RU', file: 'ru.json', name: 'Русский' },
{ code: 'es', iso: 'es-ES', file: 'es.json', name: 'Español' },
{ code: 'fr', iso: 'fr-FR', file: 'fr.json', name: 'Français' },
{ code: 'de', iso: 'de-DE', file: 'de.json', name: 'Deutsch' },
{ code: 'uk', iso: 'uk-UA', file: 'uk.json', name: 'Українська' }
],
defaultLocale: 'en',
strategy: 'prefix_except_default',
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'i18n_redirected',
redirectOn: 'root',
// Определение языка по локации (через composable)
alwaysRedirect: false,
fallbackLocale: 'en'
}
}
})
```
### Автоматическое определение языка по локации:
Логика определения языка:
1. **Проверка cookie** — если пользователь уже выбирал язык, использовать его
2. **Определение по браузеру**`navigator.language` или `navigator.languages`
3. **Fallback** — английский язык по умолчанию
Важно:
- Лендинг планируется как **SSG (статический)**. Значит IP-геолокация на сервере здесь неуместна: негде “серверу” исполняться на каждый запрос.
- Если когда-то понадобится geo-редирект — это отдельная задача (edge/runtime), не часть текущего лендинга.
**Маппинг стран к языкам:**
- 🇷🇺 Россия, Беларусь, Казахстан → `ru`
- 🇺🇦 Украина → `uk`
- 🇪🇸 Испания, Латинская Америка → `es`
- 🇫🇷 Франция, Бельгия, Швейцария (французский) → `fr`
- 🇩🇪 Германия, Австрия, Швейцария (немецкий) → `de`
- 🇺🇸 Остальные → `en`
---
## Компоненты Vuetify
### Используемые компоненты:
1. **v-app-bar** — шапка сайта
2. **v-container**, **v-row**, **v-col** — сетка
3. **v-card** — карточки фич
4. **v-btn** — кнопки
5. **v-carousel** — карусель скриншотов
6. **v-expansion-panels** — FAQ аккордеон
7. **v-chip** — бейджи платформ
8. **v-select** — выбор языка
9. **v-icon** — иконки
10. **v-divider** — разделители
### Кастомизация темы:
```typescript
// plugins/vuetify.ts
import { createVuetify } from 'vuetify'
export default defineNuxtPlugin((nuxtApp) => {
// Определение темы браузера при инициализации
const getInitialTheme = (): 'dark' | 'light' => {
if (process.client) {
// Проверка сохраненной темы в localStorage
const savedTheme = localStorage.getItem('vuetify-theme')
if (savedTheme === 'dark' || savedTheme === 'light') {
return savedTheme
}
// Определение по системным настройкам браузера
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark'
}
}
return 'light' // Fallback для SSR
}
const vuetify = createVuetify({
theme: {
defaultTheme: getInitialTheme(),
themes: {
dark: {
colors: {
primary: '#6366f1', // indigo
secondary: '#8b5cf6', // purple
accent: '#ec4899', // pink
background: '#0f172a', // slate-900
surface: '#1e293b', // slate-800
}
},
light: {
colors: {
primary: '#6366f1',
secondary: '#8b5cf6',
accent: '#ec4899',
background: '#ffffff',
surface: '#f8fafc', // slate-50
}
}
}
}
})
// Слушатель изменений системной темы
if (process.client) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const handleThemeChange = (e: MediaQueryListEvent) => {
const savedTheme = localStorage.getItem('vuetify-theme')
// Автоматически менять только если пользователь не выбирал тему вручную
if (!savedTheme) {
vuetify.theme.global.name.value = e.matches ? 'dark' : 'light'
}
}
mediaQuery.addEventListener('change', handleThemeChange)
}
nuxtApp.vueApp.use(vuetify)
})
```
---
## SEO оптимизация
### Meta теги:
```vue
<!-- layouts/default.vue -->
<template>
<Html :lang="$i18n.locale">
<Head>
<Title>{{ $t('meta.title') }}</Title>
<Meta name="description" :content="$t('meta.description')" />
<Meta property="og:title" :content="$t('meta.title')" />
<Meta property="og:description" :content="$t('meta.description')" />
<Meta property="og:image" content="/og-image.png" />
<Meta name="twitter:card" content="summary_large_image" />
</Head>
<!-- ... -->
</Html>
</template>
```
### Структурированные данные (JSON-LD):
```typescript
// composables/useStructuredData.ts
export const useStructuredData = () => {
const { $i18n } = useNuxtApp()
const softwareApplication = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Voice to Text",
"applicationCategory": "UtilityApplication",
"operatingSystem": ["macOS", "Windows", "Linux"],
"offers": {
"@type": "Offer",
"price": "0",
"priceCurrency": "USD"
}
}
return { softwareApplication }
}
```
---
## Функциональность
### 1. Определение платформы
```typescript
// composables/usePlatform.ts
export const usePlatform = () => {
const platform = ref<'macos' | 'windows' | 'linux' | 'unknown'>('unknown')
if (process.client) {
const userAgent = navigator.userAgent.toLowerCase()
if (userAgent.includes('mac')) platform.value = 'macos'
else if (userAgent.includes('win')) platform.value = 'windows'
else if (userAgent.includes('linux')) platform.value = 'linux'
}
const downloadUrl = computed(() => {
// Логика формирования URL для загрузки
const baseUrl = 'https://github.com/777genius/voice-to-text/releases'
// ...
})
return { platform, downloadUrl }
}
```
### 2. Определение темы браузера
```typescript
// composables/useBrowserTheme.ts
import { usePreferredDark } from '@vueuse/core'
import { useTheme } from 'vuetify'
export const useBrowserTheme = () => {
const { $vuetify } = useNuxtApp()
const preferredDark = usePreferredDark()
const theme = useTheme()
// Инициализация темы при первом посещении
const initTheme = () => {
if (process.client) {
const savedTheme = localStorage.getItem('vuetify-theme')
if (savedTheme) {
// Использовать сохраненную тему
theme.global.name.value = savedTheme as 'dark' | 'light'
} else {
// Использовать системную тему браузера
theme.global.name.value = preferredDark.value ? 'dark' : 'light'
localStorage.setItem('vuetify-theme', theme.global.name.value)
}
}
}
// Переключение темы
const toggleTheme = () => {
const newTheme = theme.global.name.value === 'dark' ? 'light' : 'dark'
theme.global.name.value = newTheme
localStorage.setItem('vuetify-theme', newTheme)
}
// Установка конкретной темы
const setTheme = (themeName: 'dark' | 'light') => {
theme.global.name.value = themeName
localStorage.setItem('vuetify-theme', themeName)
}
// Слушатель изменений системной темы (только если пользователь не выбирал вручную)
if (process.client) {
watch(preferredDark, (isDark) => {
const savedTheme = localStorage.getItem('vuetify-theme')
if (!savedTheme) {
theme.global.name.value = isDark ? 'dark' : 'light'
}
})
}
return {
currentTheme: computed(() => theme.global.name.value),
isDark: computed(() => theme.global.name.value === 'dark'),
initTheme,
toggleTheme,
setTheme
}
}
```
### 3. Определение локации пользователя
```typescript
// composables/useLocation.ts
export const useLocation = () => {
const { $i18n } = useNuxtApp()
const locale = useCookie('i18n_redirected', { default: () => 'en' })
// Маппинг стран к языкам
const countryToLocale: Record<string, string> = {
'RU': 'ru', // Россия
'BY': 'ru', // Беларусь
'KZ': 'ru', // Казахстан
'UA': 'uk', // Украина
'ES': 'es', // Испания
'MX': 'es', // Мексика
'AR': 'es', // Аргентина
'CO': 'es', // Колумбия
'CL': 'es', // Чили
'PE': 'es', // Перу
'FR': 'fr', // Франция
'BE': 'fr', // Бельгия (французский)
'CH': 'de', // Швейцария (по умолчанию немецкий, можно улучшить)
'DE': 'de', // Германия
'AT': 'de', // Австрия
}
// Определение языка по браузеру
const getBrowserLocale = (): string => {
if (process.client) {
const browserLang = navigator.language || (navigator as any).userLanguage
const langCode = browserLang.split('-')[0].toLowerCase()
// Проверка поддерживаемых языков
const supportedLocales = ['en', 'ru', 'es', 'fr', 'de', 'uk']
if (supportedLocales.includes(langCode)) {
return langCode
}
// Проверка полного кода (например, ru-RU)
const fullCode = browserLang.toLowerCase()
if (fullCode.startsWith('ru')) return 'ru'
if (fullCode.startsWith('uk')) return 'uk'
if (fullCode.startsWith('es')) return 'es'
if (fullCode.startsWith('fr')) return 'fr'
if (fullCode.startsWith('de')) return 'de'
}
return 'en'
}
// Важно: лендинг статический (SSG), поэтому IP-геолокацию не используем.
// Инициализация языка при первом посещении
const initLocale = async () => {
// Если язык уже выбран пользователем, не менять
if (locale.value && locale.value !== 'en') {
return
}
// Приоритет: cookie > браузер > fallback
let detectedLocale = locale.value || 'en'
// На клиенте определяем по браузеру
detectedLocale = getBrowserLocale()
// Устанавливаем язык только если он отличается от текущего
if (detectedLocale !== $i18n.locale.value) {
const { switchLocalePath } = useI18n()
await navigateTo(switchLocalePath(detectedLocale))
}
}
return {
currentLocale: computed(() => $i18n.locale.value),
initLocale,
getBrowserLocale
}
}
```
### 4. Плагин для автоматической инициализации
```typescript
// plugins/init-theme-locale.client.ts
export default defineNuxtPlugin(async () => {
const { initTheme } = useBrowserTheme()
const { initLocale } = useLocation()
// Инициализация темы
initTheme()
// Инициализация локали (только при первом посещении)
const hasVisited = useCookie('has_visited', { default: () => false })
if (!hasVisited.value) {
await initLocale()
hasVisited.value = true
}
})
```
### 5. Статистика загрузок (опционально)
Не делаем.
### 6. Аналитика (опционально)
```typescript
// composables/useAnalytics.ts
export const useAnalytics = () => {
const trackDownload = (platform: string) => {
if (process.client) {
// Google Analytics, Plausible, или другая аналитика
gtag('event', 'download', { platform })
}
}
return { trackDownload }
}
```
**Решение по аналитике:**
- Делаем **GA4** (Google Analytics) + события:
- `download_click` (platform, arch, version, locale)
- `download_page_view`
- `language_change`
- `theme_change`
- `faq_open`
- `cta_view_features_click`
- Для подключения нужен `GA4 Measurement ID` (например, `G-XXXXXXXXXX`) и/или доступы к аккаунту. В коде предусматриваем конфиг через env, а фактическое подключение выполняется владельцем аккаунта.
---
## Дизайн и стилизация
### Цветовая схема:
**Темная тема:**
- Фон: `#0f172a` (slate-900)
- Поверхности: `#1e293b` (slate-800)
- Акцент: `#6366f1` (indigo-500)
- Текст: `#f1f5f9` (slate-100)
**Светлая тема:**
- Фон: `#ffffff`
- Поверхности: `#f8fafc` (slate-50)
- Акцент: `#6366f1` (indigo-500)
- Текст: `#0f172a` (slate-900)
### Типографика:
- **Заголовки**: Inter или System Font Stack
- **Текст**: Inter или System Font Stack
- **Моноширинный**: JetBrains Mono (для кода)
### Анимации:
- Плавные переходы при скролле
- Hover эффекты на карточках
- Параллакс для hero секции (опционально)
---
## Деплой
### Рекомендуемые платформы:
1. **Vercel** (рекомендуется)
- Автоматический деплой из Git
- SSR/SSG поддержка
- CDN по умолчанию
2. **Netlify**
- Аналогично Vercel
- Хорошая поддержка Nuxt
3. **GitHub Pages** (только SSG)
- Бесплатный хостинг
- Требует `nuxt generate`
### Target деплой:
- **Render**
- Режим: **Static Site Generation (SSG)** (статическая генерация)
### Конфигурация для деплоя:
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
// Для SSG (пререндер страниц, а не SPA):
ssr: true,
nitro: { preset: 'static' },
routeRules: {
'/': { prerender: true },
'/download': { prerender: true },
},
})
```
### Важно про i18n + SSG (критично для SEO)
`routeRules` выше — это только базовый пример. Для i18n нужно гарантировать, что **пререндерятся все локали**.
Правило:
- Источник правды — список локалей + список страниц.
- На их основе формируем список путей для пререндера и для sitemap.
Пример логики (идея, не финальный код):
- Локали: `['en', 'ru', 'es', 'fr', 'de', 'uk']`, default = `en`
- Страницы: `['/', '/download']`
- Генерация путей:
- для `en`: `['/', '/download']`
- для остальных: `['/ru', '/ru/download']`, `['/es', '/es/download']`, ...
Это решает сразу две проблемы:
- не теряем локали при деплое (все страницы реально существуют как статик)
- sitemap/alternate можно генерировать из той же таблицы
См. также: `landing/docs/ARCHITECTURE_GUARDRAILS.md` (раздел про “источник правды по URL”).
---
## Чеклист разработки
### Фаза 1: Настройка проекта
- [ ] Инициализация Nuxt 3 проекта
- [ ] Установка Vuetify 3
- [ ] Настройка TypeScript
- [ ] Настройка Pinia (`@pinia/nuxt`) и базовых stores (общие состояния не держим “размазанными” по компонентам)
- [ ] Настройка i18n с определением локации
- [ ] Настройка Vuetify с определением темы браузера
- [ ] Установка @vueuse/nuxt для usePreferredDark
- [ ] ESLint (`@nuxt/eslint`) + Prettier (единый стиль форматирования)
- [ ] Базовая структура папок
### Фаза 2: Layout и навигация
- [ ] Создание `default.vue` layout
- [ ] Компонент `AppHeader.vue` с навигацией
- [ ] Компонент `AppFooter.vue`
- [ ] Компонент `LanguageSwitcher.vue`
- [ ] Адаптивная навигация (мобильное меню)
### Фаза 3: Главная страница
- [ ] Hero Section
- [ ] Features Section (6 карточек)
- [ ] Providers Section
- [ ] Screenshots Section (карусель)
- [ ] Download Section
- [ ] Privacy Section
- [ ] FAQ Section
### Фаза 4: Локализация и тема
- [ ] Переводы для всех 6 языков
- [ ] Композабл `useBrowserTheme.ts` для определения темы
- [ ] Композабл `useLocation.ts` для определения локации
- [ ] Плагин `init-theme-locale.client.ts` для автоинициализации
- [ ] Маппинг стран к языкам
- [ ] Определение языка по браузеру (client-side)
- [ ] Определение темы по системным настройкам
- [ ] Сохранение выбора пользователя в cookies/localStorage
- [ ] Тестирование переключения языков и темы
- [ ] SEO мета-теги для каждого языка
- [ ] Правильные URL для каждого языка
### Фаза 5: Функциональность
- [ ] Определение платформы пользователя
- [ ] Кнопки загрузки с правильными ссылками
- [ ] Страница `/download` с инструкциями
- [ ] Аналитика (GA4) + события (скачивание, смена языка/темы, FAQ, CTA)
### Фаза 6: Оптимизация
- [ ] Оптимизация изображений
- [ ] Lazy loading для секций
- [ ] SEO оптимизация
- [ ] Структурированные данные
- [ ] Sitemap и robots.txt
### Фаза 7: Тестирование
- [ ] Тестирование на разных устройствах
- [ ] Тестирование всех языков
- [ ] Тестирование автоматического определения языка по браузеру
- [ ] Тестирование автоматического определения темы
- [ ] Тестирование переключения темы вручную
- [ ] Тестирование сохранения выбора пользователя
- [ ] Проверка производительности
- [ ] Проверка доступности (a11y)
### Фаза 8: Деплой
- [ ] Настройка CI/CD
- [ ] Деплой на выбранную платформу
- [ ] Настройка домена
- [ ] SSL сертификат
---
## Дополнительные рекомендации
### Определение темы и локации:
- **Приоритет определения языка:**
1. Cookie (если пользователь уже выбирал)
2. Браузерные настройки (`navigator.language`)
3. Fallback на английский
- **Приоритет определения темы:**
1. localStorage (если пользователь выбирал вручную)
2. Системные настройки браузера (`prefers-color-scheme`)
3. Fallback на светлую тему
- **Альтернативные API для геолокации:**
- Не используем в рамках текущего статического лендинга (SSG).
- **Обработка ошибок:**
- Таймаут для API запросов (3 секунды)
- Graceful fallback на браузерные настройки
- Логирование ошибок для отладки
### Производительность:
- Использовать `nuxt/image` для оптимизации изображений
- Lazy loading для компонентов ниже fold
- Code splitting для больших компонентов
- Никаких внешних geo-запросов: меньше точек отказа и проще с приватностью.
### Доступность:
- Семантический HTML
- ARIA атрибуты
- Keyboard navigation
- Контрастность цветов (WCAG AA)
### Аналитика:
- Google Analytics 4
- Plausible (privacy-focused)
- Yandex Metrika (для русскоязычной аудитории)
### Мониторинг:
- Sentry для отслеживания ошибок
- Uptime monitoring
---
## Примеры реализации компонентов
### Hero Section
```vue
<template>
<v-container class="hero-section">
<v-row align="center" justify="center" class="fill-height">
<v-col cols="12" md="8" class="text-center">
<h1 class="text-h2 font-weight-bold mb-4">
{{ $t('hero.title') }}
</h1>
<p class="text-h6 text-medium-emphasis mb-8">
{{ $t('hero.subtitle') }}
</p>
<div class="d-flex gap-4 justify-center flex-wrap">
<DownloadButton :platform="platform" />
<v-btn
size="large"
variant="outlined"
@click="scrollToFeatures"
>
{{ $t('hero.viewFeatures') }}
</v-btn>
</div>
</v-col>
</v-row>
</v-container>
</template>
```
### Feature Card
```vue
<template>
<v-card class="feature-card" elevation="2">
<v-card-text class="text-center pa-6">
<v-icon
:icon="icon"
size="64"
color="primary"
class="mb-4"
/>
<h3 class="text-h6 mb-2">{{ title }}</h3>
<p class="text-body-2 text-medium-emphasis">
{{ description }}
</p>
</v-card-text>
</v-card>
</template>
```
---
## Контакты и ресурсы
- **GitHub**: https://github.com/777genius/voice-to-text
- **Документация**: (если есть)
- **Поддержка**: (если есть)
---
**Версия плана**: 1.0
**Дата создания**: 2025-01-17
**Статус**: Готов к реализации
---
## План итераций (сначала планируем, затем перепроверяем, потом реализуем)
Процесс:
1) Сначала готовим максимально подробные планы итераций.
2) Затем **несколько раз** перепроверяем планы (полнота, несостыковки, риски, критерии готовности).
3) Только после этого начинаем реализацию пошагово.
4) После реализации — сверяемся с планами и ещё раз перепроверяем соответствие.
Файлы итераций:
- `landing/docs/iterations/ITERATION_00_REQUIREMENTS.md`
- `landing/docs/iterations/ITERATION_01_SCAFFOLDING.md`
- `landing/docs/iterations/ITERATION_02_UI_SECTIONS.md`
- `landing/docs/iterations/ITERATION_03_SWIPER_AND_DOWNLOAD.md`
- `landing/docs/iterations/ITERATION_04_ANALYTICS_SEO_SSG_RENDER.md`