javascript

Как я создал платформу для изучения иврита: от идеи до работающего сервиса

  • пятница, 7 ноября 2025 г. в 00:00:07
https://habr.com/ru/articles/963834/

Я построил полноценную образовательную платформу для изучения иврита — с интерактивными тренажерами, умным словарем на 4000+ слов и системой подписок. В статье рассказываю о нетривиальных технических решениях, архитектурных выборах и ошибках, которые пришлось исправлять по ходу.

Демо: hebrewglot.com
Стек: Next.js 15, TypeScript, PostgreSQL + SQLite, Stripe, NextAuth


🎯 Предыстория: почему вообще это началось

Я начал учить иврит и быстро столкнулся с проблемой: хороших онлайн-ресурсов на русском языке почти нет.

Что есть:

  • Duolingo — поверхностный, не объясняет грамматику

  • Pealim — отличный сайт для спряжений, но только для спряжений

  • Ульпаны — дорого ($300-500/месяц) и нужно ездить

Чего не хватало:

  • Интерактивных упражнений с мгновенной обратной связью

  • Нормального объяснения системы биньянов (это как виды глаголов, но сложнее)

  • Словаря с примерами использования в контексте

  • Всего этого на русском языке без необходимости учить через английский

Я подумал: "А что если сделать самому?" И понеслось...

Прежде чем рассказывать про код, нужно понять специфику языка. Иврит — это не английский. Там все устроено по-другому, и это создает интересные технические челленджи.

Корневая система

В иврите слова строятся из корней (обычно 3 буквы). Представьте, что у вас есть конструктор LEGO, где 3 базовых кубика порождают сотни вариантов.

Корень כ-ת-ב (к-т-б) = "писание"

От него образуются:

  • כָּתַב (katav) — "писал"

  • כּוֹתֵב (kotev) — "пишет"

  • מִכְתָּב (michtav) — "письмо" (объект)

  • כְּתוֹבֶת (ktovet) — "адрес"

  • כְּתִיבָה (ktiva) — "письмо" (процесс)

Видите паттерн? К-Т-Б везде, но слова разные. Для программиста это звучит как "идеальная задача для шаблонизации".

Система биньянов

Это породы глаголов — всего их 7 штук. Как спряжения в русском, но намного строже и системнее.

Один и тот же корень в разных биньянах дает разные значения:

Корень ש-ב-ר (ш-б-р) = "ломать"

  • ПААЛ: שָׁבַר (shabar) — "сломал" (просто действие)

  • НИФЪАЛЬ: נִשְׁבַּר (nishbar) — "сломался" (пассив/рефлексив)

  • ПИЭЛЬ: שִׁבֵּר (shiber) — "разбил на куски" (интенсив)

  • hИФЪИЛЬ: הִשְׁבִּיר (hishbir) — "заставил сломать" (каузатив)

Задача для программиста: Как это всё структурировать и генерировать автоматически? Именно здесь начинается самое интересное.


💡 Идея и MVP

Что должно было быть в продукте

Минимум (MVP):

  1. ✅ Словарь с поиском по русскому/ивриту

  2. ✅ Тренажёр для спряжений

  3. ✅ Объяснение биньянов с примерами

Хорошо бы добавить:
4. ✅ Уроки для начинающих (от алфавита)
5. ✅ Статистика прогресса
6. ✅ Блог с полезными статьями (для SEO)
7. ✅ Монетизация (чтобы окупить хостинг)

Выбор технологий

Тут я исходил из простого принципа: "Используй то, что знаешь, но не бойся учить новое".

Next.js 15 — потому что:

  • Server-side rendering для SEO (важно для образовательного контента)

  • API routes (не нужен отдельный бэкенд)

  • App Router с Server Components (новая фишка, хотел попробовать в деле)

  • Большое комьюнити и быстрые решения проблем

TypeScript — потому что:

  • Иврит имеет жесткую грамматическую структуру

  • Типобезопасность = меньше багов в морфологии

  • Автодополнение = быстрее писать сложный код

  • Рефакторинг становится безопасным

PostgreSQL + SQLite — об этом подробнее ниже, это самое интересное архитектурное решение

Stripe — для подписок:

  • Простая интеграция

  • Надежный

  • Автоматическое управление подписками


🏗️ Архитектура: необычное решение с двумя базами

Проблема

У меня было два типа данных с совершенно разными характеристиками:

1. Пользовательские данные (меняются часто):

  • Учетные записи

  • Статистика прогресса

  • История ответов

  • Подписки

  • ~100 записей на пользователя

2. Словарь иврита (статичные, read-only):

  • 4000+ слов

  • Все формы спряжений (~200 форм на глагол)

  • Переводы на русский и английский

  • Примеры использования

  • Размер: 44 МБ

Проблема: Держать 44 МБ статичных данных в облачной PostgreSQL — это:

  • 💸 Дорого (облачные БД берут за объем)

  • 🐌 Медленно (network latency при каждом запросе)

  • 🤷 Бессмысленно (данные не меняются)

  • 🔄 Сложно обновлять (нужны миграции для контента)

Решение: гибридная схема

Архитектура двух баз данных
Архитектура двух баз данных
┌─────────────────────────────────────────┐
│         Next.js приложение              │
├─────────────────────────────────────────┤
│                                         │
│  ┌──────────────┐   ┌───────────────┐  │
│  │ PostgreSQL   │   │    SQLite     │  │
│  │   (Render)   │   │   (локально)  │  │
│  ├──────────────┤   ├───────────────┤  │
│  │ Users        │   │ Words (4000+) │  │
│  │ Sessions     │   │ Roots         │  │
│  │ Stats        │   │ Conjugations  │  │
│  │ Subscriptions│   │ Examples      │  │
│  └──────────────┘   └───────────────┘  │
│         ↕                    ↕          │
│      Prisma          better-sqlite3     │
└─────────────────────────────────────────┘
Postgree и SQLite
Postgree и SQLite

PostgreSQL (облако) — для всего, что меняется
SQLite (файл в проекте) — для словаря


Создал менеджер баз данных, который прозрачно работает с обеими:

// src/lib/database.ts
export class DatabaseManager {
  private sqlite: Database
  private prisma: PrismaClient
  
  // Словарь → SQLite (быстро, локально)
  async searchWords(query: string, locale: string) {
    const stmt = this.sqlite.prepare(`
      SELECT * FROM words 
      WHERE form LIKE ? OR translation LIKE ?
      LIMIT 50
    `)
    return stmt.all(`%${query}%`, `%${query}%`)
  }
  
  // Статистика → PostgreSQL (облако)
  async saveUserStats(userId: string, stats: any) {
    return this.prisma.userStats.upsert({
      where: { userId },
      update: stats,
      create: { userId, ...stats }
    })
  }
}

Плюсы этого подхода:

  • ⚡ SQLite очень быстрый (нет сети, всё локально, < 1ms)

  • 💰 Экономия на хостинге (~$20/месяц)

  • 📦 Деплой проще (SQLite файл в репозитории)

  • 🔍 Prepared statements работают мгновенно

Минусы:

  • 🔄 Нельзя обновлять словарь на лету (нужен редеплой)

  • Но для образовательного контента это не проблема (обновления редки)


🧠 Морфологический движок: самая сложная часть

Это сердце всей системы. Нужно было научить программу генерировать формы глаголов по правилам.

Задача

Пользователь вводит (или система генерирует):

  • Корень: כ-ת-ב

  • Биньян: ПААЛ

  • Время: прошедшее

  • Лицо: я (1-е лицо, единственное число)

Программа должна выдать: כָּתַבְתִּי (katavti — "я писал")

И так для всех комбинаций: 7 биньянов × 4 времени × 9 лиц = 252 формы для каждого корня!

Как это работает

Шаг 1: Шаблоны с плейсхолдерами

Для каждого биньяна создал шаблоны:

// src/lib/morph/verbRules.ts
const PAAL_TEMPLATE = {
  past: {
    '1s': {  // 1-е лицо единственное число
      template: '{r1}ַ{r2}ְ{r3}ְתִּי',
      transcription: '{r1}a{r2}{r3}ti'
    },
    '3ms': {  // 3-е лицо муж.род ед.число
      template: '{r1}ַ{r2}ַ{r3}',
      transcription: '{r1}a{r2}a{r3}'
    }
    // ... всего 9 форм для каждого времени
  },
  present: {
    'ms': { 
      template: '{r1}וֹ{r2}ֵ{r3}',
      transcription: '{r1}o{r2}e{r3}'
    }
    // ...
  },
  future: { /* ... */ },
  imperative: { /* ... */ }
}

Шаг 2: Подстановка корня

function realizeVerb(
  root: [string, string, string], 
  template: string
): string {
  const [r1, r2, r3] = root
  
  return template
    .replace('{r1}', r1)
    .replace('{r2}', r2)
    .replace('{r3}', r3)
}

// Пример использования
realizeVerb(['כ', 'ת', 'ב'], '{r1}ַ{r2}ְ{r3}ְתִּי')
// → 'כַתְבְתִּי'

Шаг 3: Обработка особых случаев

Некоторые буквы ведут себя странно (гортанные буквы א, ה, ח, ע):

// Если первая буква корня — א (алеф), 
// она "проглатывает" некоторые гласные
if (r1 === 'א' &amp;&amp; binyan === 'PAAL') {
  // Специальная обработка для слабых корней
  template = adjustForWeakRoot(template, 'first')
}

// Если последняя буква — ה (хей), 
// она меняется в некоторых формах
if (r3 === 'ה') {
  template = adjustForWeakRoot(template, 'last')
}

Результат:

Теперь из одного корня можно сгенерировать все 200+ форм автоматически!

const forms = generateAllForms('כתב', 'PAAL')
// → {
//   past: { 
//     '1s': 'כתבתי', 
//     '2ms': 'כתבת',
//     '3ms': 'כתב',
//     // ... всего 9 форм
//   },
//   present: { 
//     'ms': 'כותב', 
//     'fs': 'כותבת', 
//     'mp': 'כותבים',
//     'fp': 'כותבות'
//   },
//   future: { /* ... */ },
//   imperative: { /* ... */ }
// }

Зачем это нужно

  1. Тренажёр: генерирую вопросы на лету (не нужно хранить все формы)

  2. Словарь: показываю все формы слова динамически

  3. Проверка ответов: сравниваю с правильной формой

  4. Масштабируемость: новый корень = автоматически 200+ слов


🌍 Интернационализация: русский И английский

Здесь я потратил слишком много времени, но узнал важный урок о приоритизации.

Проблема

Хотел поддержать два языка:

  • 🇷🇺 Русский (основная аудитория)

  • 🇬🇧 Английский (для масштабирования и международной аудитории)

Простая часть: UI переводы (кнопки, меню)

// src/hooks/useI18n.ts
const translations = {
  ru: {
    'search': 'Поиск',
    'login': 'Войти',
    'dictionary': 'Словарь'
  },
  en: {
    'search': 'Search',
    'login': 'Login',
    'dictionary': 'Dictionary'
  }
}

Сложная часть: Контент из базы данных

Слова в БД на иврите и русском. Как их показать англоязычным пользователям?

Решение: многоуровневая система переводов

// src/lib/translationService.ts

// Уровень 1: Кеш (500+ предзаполненных переводов)
const translationCache = new Map([
  ['учить', 'to learn'],
  ['писать', 'to write'],
  ['говорить', 'to speak'],
  // ... еще 500+ самых частых слов
])

// Уровень 2: Паттерн-анализ
function translateText(text: string, targetLocale: string): string {
  // Проверяем кеш
  if (translationCache.has(text)) {
    return translationCache.get(text)!
  }
  
  // Анализируем паттерны (например, глагольные формы)
  if (text.endsWith('ть')) {  // русский инфинитив
    const englishForm = convertToEnglishInfinitive(text)
    return 'to ' + englishForm
  }
  
  // Паттерны существительных
  if (text.endsWith('ие') || text.endsWith('ость')) {
    return handleAbstractNoun(text)
  }
  
  // Резервный вариант: показываем как есть
  return text
}

Результат: ~100% покрытие переводов без внешних API

Что я узнал (важный урок!)

Ошибка: Потратил месяц на полную локализацию, хотя 70% пользователей русскоязычные.

Правильно было бы:

  1. Запустить только на русском

  2. Собрать аудиторию

  3. Посмотреть метрики

  4. Локализовать по требованию

Урок: Делай интернационализацию, когда она реально нужна, а не "на будущее". YAGNI принцип работает.


🎯 Интерактивные тренажёры

Это то, ради чего всё затевалось. Пассивное чтение учебников не работает — нужна практика.

Типы тренажёров

1. Лёгкий режим: выбор из вариантов (Multiple Choice)

Вопрос: Как будет "я писал" на иврите?

A) כותב
B) כתבתי  ← правильный ответ
C) אכתוב
D) כתב

2. Сложный режим: ввод формы (Free Input)

Введите форму глагола כתב (писать)
Время: прошедшее
Лицо: мы

Ваш ответ: [_______]
Правильно: כתבנו

3. Спряжения: полная таблица

Настоящее время (הווה)
он пишет → כותב
она пишет → כותבת
они писали → כתבו

Генерация вопросов

Вопросы генерируются на лету:

// Упрощенный пример генерации вопросов
export async function generateQuestions(locale: string, difficulty: string) {
  // Берем случайные слова из БД
  const words = await getRandomWords(10)
  
  // Для каждого слова генерируем формы
  const questions = words.map(word =&gt; {
    const forms = generateAllForms(word.root, word.binyan)
    
    // Случайное время и лицо
    const tense = randomChoice(['past', 'present', 'future'])
    const person = randomChoice(['1s', '2ms', '3fs', '1p'])
    
    const correctAnswer = forms[tense][person]
    
    // Генерируем неправильные варианты
    const wrongAnswers = generateDistractors(word, tense, person)
    
    // Формируем вопрос на нужном языке
    const question = formatQuestion(word, person, tense, locale)
    
    return {
      question,
      correctAnswer,
      wrongAnswers,
      metadata: { tense, person, binyan: word.binyan }
    }
  })
  
  return questions
}

Статистика и геймификация

После каждого ответа сохраняю результат в двух местах:

Локально (localStorage) — для быстрого доступа:

const stats = {
  totalAnswers: 142,
  correctAnswers: 98,
  wrongAnswers: 44,
  accuracy: 69.0,
  streak: 5,  // дней подряд
  lastActiveDate: '2025-11-04'
}

В облаке (PostgreSQL) — для кросс-девайсности:

await prisma.userStats.update({
  where: { userId },
  data: {
    totalWords: { increment: 1 },
    correctAnswers: { increment: isCorrect ? 1 : 0 },
    wrongAnswers: { increment: isCorrect ? 0 : 1 },
    lastActiveDate: new Date(),
    streak: calculateStreak(user.lastActiveDate)
  }
})

Эффект: Пользователи видят прогресс → мотивация растет → retention улучшается


💰 Монетизация: Stripe и Premium контент

Мне нужно было как-то окупать хостинг (~$15/мес) и мотивировать себя развивать проект.

Модель: Freemium

Бесплатно (Forever Free):

  • ✅ Базовый словарь (4000 самых частых слов)

  • ✅ Первые 2 урока (алфавит, чтение, базовая грамматика)

  • ✅ Ограниченный тренажёр (10 слов в день)

  • ✅ Объяснение биньянов

Premium ($10/мес):

  • ✨ Полный словарь (4000+ слов)

  • ✨ Все 12 уроков

  • ✨ Неограниченные тренажёры

  • ✨ Статистика и прогресс

  • ✨ Без рекламы

  • ✨ Поддержка проекта

Почему $10? Это дешевле, чем:

  • Один урок с репетитором ($15-30)

  • Месяц Duolingo Plus ($13)

  • Подписка на любой учебник ($20+)

Интеграция Stripe

Шаг 1: Создание Checkout Session

// Пример создания сессии оплаты
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)

export async function POST(req: Request) {
  const { priceId } = await req.json()
  const session = await getCurrentSession()
  
  if (!session?.user) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 })
  }
  
  // Создаём сессию оплаты
  const checkoutSession = await stripe.checkout.sessions.create({
    customer_email: session.user.email,
    line_items: [{ price: priceId, quantity: 1 }],
    mode: 'subscription',
    success_url: `${YOUR_DOMAIN}/success`,
    cancel_url: `${YOUR_DOMAIN}/pricing`,
    metadata: { userId: session.user.id }
  })
  
  return Response.json({ url: checkoutSession.url })
}

Шаг 2: Обработка Webhook (критично!)

// Упрощенный пример обработки webhook
export async function POST(req: Request) {
  // Проверяем подпись Stripe
  const event = await verifyWebhookSignature(req)
  
  if (!event) {
    return Response.json({ error: 'Invalid signature' }, { status: 400 })
  }
  
  // Обрабатываем события
  switch (event.type) {
    case 'checkout.session.completed':
      await handleSuccessfulPayment(event.data)
      break
    
    case 'customer.subscription.updated':
      await updateSubscriptionStatus(event.data)
      break
      
    case 'customer.subscription.deleted':
      await handleCancellation(event.data)
      break
  }
  
  return Response.json({ received: true })
}

Важно: Всегда проверяйте подписи webhook для защиты от подделки запросов.

Шаг 3: Защита контента (PremiumGate)

// components/PremiumGate.tsx
export async function PremiumGate({ 
  children 
}: { 
  children: ReactNode 
}) {
  const session = await getServerSession(authOptions)
  
  if (!session) {
    return 
  }
  
  // Проверяем подписку
  const subscription = await prisma.subscription.findUnique({
    where: { userId: session.user.id }
  })
  
  const hasPremium = 
    subscription?.status === 'active' &amp;&amp; 
    subscription.currentPeriodEnd &gt; new Date()
  
  if (!hasPremium) {
    return (
      <div>
        <h3>🔒 Premium контент</h3>
        <p>Этот урок доступен только в Premium подписке</p>
        
          <button>Получить Premium за $10/мес</button>
        
      </div>
    )
  }
  
  return &lt;&gt;{children}
}

Использование:

// app/[locale]/lessons/[id]/page.tsx
export default async function LessonPage({ params }) {
  const lesson = await getLesson(params.id)
  
  if (lesson.isPremium) {
    return (
      
        
      
    )
  }
  
  return 
}

📝 Блог для SEO: контент-маркетинг

Понял, что просто приложение без контента никто не найдет в поиске. Нужен органический трафик.

Что сделал

Написал 22 статьи на темы, которые люди ищут:

  • "10 причин начать учить иврит"

  • "Как выучить иврит с нуля онлайн"

  • "100 разговорных фраз на каждый день"

  • "Ошибки начинающих при изучении иврита"

  • "Как читать на иврите за неделю"

  • И т.д.

Технология: MDX

MDX = Markdown + React компоненты. Можно вставлять интерактивные элементы прямо в текст статьи.

---
title: "Ошибки начинающих при изучении иврита"
date: "2025-10-28"
description: "10 типичных ошибок и как их избежать"
keywords: ["ошибки иврит", "как учить иврит"]
---

## Ошибка 1: Игнорировать биньяны

**Не делайте так!** Биньяны — это основа всей системы.



**Попробуйте сами прямо сейчас:** 

SEO оптимизация

Что сделал для поисковиков:

  • Semantic HTML (<article>, <header>, <section>)

  • Meta tags (title, description, keywords)

  • OpenGraph изображения (автогенерация через DeepAI API)

  • Structured data (JSON-LD для Google)

  • Sitemap.xml (автоматический)

  • robots.txt (правильная индексация)

  • Быстрая загрузка (Server Components + Image optimization)

Результат:

// Lighthouse Score
{
  performance: 94,
  accessibility: 98,
  bestPractices: 100,
  seo: 100
}

🚀 Деплой и продакшен

Для хостинга выбрал облачную платформу с:

  • Недорогим PostgreSQL tier

  • Простым деплоем (git push = deploy)

  • Поддержкой Next.js из коробки

  • Автоматическими SSL сертификатами

  • Европейским регионом (ближе к целевой аудитории)

Конфигурация

# config.yaml
services:
  - type: web
    name: hebrewglot
    env: node
    region: frankfurt  # ближе к целевой аудитории
    plan: starter  # $7/мес
    buildCommand: |
      npx prisma generate &amp;&amp; 
      npx prisma migrate deploy &amp;&amp; 
      next build
    startCommand: npm start
    envVars:
      - key: DATABASE_URL
        fromDatabase:
          name: hebrewglot-db
          property: connectionString
      - key: NEXTAUTH_SECRET
        generateValue: true
      - key: NEXTAUTH_URL
        value: https://hebrewglot.com

databases:
  - name: hebrewglot-db
    databaseName: hebrewglot
    plan: starter  # бесплатно (1GB)

CI/CD Pipeline

Процесс деплоя (автоматический):

# 1. Push в репозиторий
git add .
git commit -m "Add new feature"
git push origin main

# 2. Платформа автоматически:
#    → Скачивает код
#    → Устанавливает зависимости
#    → Запускает миграции БД
#    → Собирает проект
#    → Деплоит новую версию
#    → Делает zero-downtime restart

# Время деплоя: ~4-5 минут

📊 Метрики и результаты

Производительность

Web Vitals:

  • LCP (Largest Contentful Paint): 1.2s ✅

  • FID (First Input Delay): 45ms ✅

  • CLS (Cumulative Layout Shift): 0.02 ✅

Размеры бандлов:

  • JavaScript: 180KB (gzip) — основной бандл

  • CSS: 12KB (gzip) — Tailwind

  • Первая загрузка: < 2s на 3G

Lighthouse Score:

{
  "performance": 94,
  "accessibility": 98,
  "bestPractices": 100,
  "seo": 100
}

Технические метрики

  • Слов в словаре: 4,000+

  • Корней глаголов: 1,266

  • Статей в блоге: 22 (RU), 2 (EN)

  • Уроков: 16

  • Строк кода: ~15,000

  • Тестов: 47 (unit + integration)

  • Типов TypeScript: 150+


🤦 Ошибки, которые я совершил

Честно о том, что пошло не так (и как исправлял).

1. Переусложнил интернационализацию

Что сделал: Потратил месяц на полную поддержку английского с первого дня
Реальность: 70% пользователей русскоязычные, английской версией никто не пользовался первые 3 месяца
Урок: Делай локализацию по требованию, а не "на будущее". Принцип YAGNI (You Aren't Gonna Need It) работает.

Как исправил: Сделал русскую версию основной, английскую — второстепенной. Сэкономил бы месяц работы.

2. Игнорировал мобильную версию в начале

Что сделал: Сначала десктоп во всей красе, потом "адаптируем"
Правильно: Mobile-first подход с самого начала
Почему: 60% пользователей с телефонов, они видели плохой UX

Как исправил: Переделал весь UI с фокусом на мобильные экраны. Заняло еще 2 недели.

3. Не валидировал цену подписки

Что сделал: Установил $10/мес "на глаз", потому что "так у всех"
Правильно: Опросить потенциальных пользователей ДО запуска
Результат: Первые пользователи сказали "дорого для РФ", пришлось делать региональные цены

Как исправил: Добавил опрос, сделал $9 для всего мира.

4. Написал весь код сам (включая морфологию)

Что сделал: Всё с нуля, включая правила спряжений
Правильно: Использовать существующие API или библиотеки (например, Pealim API)
Почему: "Изобретение колеса" заняло 3 недели

Но: Зато теперь у меня 100% контроль, никакой зависимости от внешних сервисов, и работает офлайн. Спорное решение.

5. Недооценил важность контента

Что сделал: Сделал крутую техническую платформу, написал 3 статьи для блога
Правильно: Content is king. Писать статьи параллельно с разработкой
Результат: Первые 2 месяца — 0 органического трафика

Как исправил: Нанял копирайтера, написали 20 статей за месяц. Трафик вырос в 10 раз.


💡 Что я узнал (главные инсайты)

Технические инсайты

1. SQLite в продакшене — это нормально

  • Для read-only данных — идеальное решение

  • Быстрее PostgreSQL для статичного контента (нет network latency)

  • Проще деплоить (файл в репе)

  • Меньше точек отказа

2. Next.js App Router готов к продакшену

  • Server Components реально ускоряют (меньше JS в браузере)

  • Но документация местами сырая (много trial & error)

  • Community большое, ответы находятся быстро

3. TypeScript окупается на сложной логике

  • Морфология без типов = ад и боль

  • Рефакторинг с типами = безопасно и легко

  • Обучение команды занимает +2 недели, но оно того стоит

4. Монорепозиторий удобен для малых проектов

  • Всё в одном месте

  • Нет проблем с версионированием между фронтом и бэком

  • Но становится медленным на >50k LOC

5. Prepared statements в SQLite — must have

  • Защита от SQL injection

  • В разы быстрее обычных запросов

  • Кэширование плана выполнения

Бизнес-инсайты

1. SEO > платная реклама (для образовательных проектов)

  • Одна хорошая статья = 100+ визитов в месяц

  • Контент живет годами

  • Google Ads для ниши "иврит" стоит $3+ за клик

2. Freemium работает, если бесплатная часть реально полезна

  • Дай попробовать продукт

  • Покажи ценность

  • 5-10% конвертируются в платящих

3. Community > маркетинг

  • Сообщества в соцсетях дали больше пользователей, чем реклама

  • Форумы и тематические площадки — бесплатный охват

  • Главное — не спамить, а помогать

4. Качество > количество

  • Лучше 12 крутых уроков, чем 50 средненьких

  • Пользователи ценят глубину

  • Retention выше у качественного контента


🎯 Что дальше: планы на будущее

Ближайшие месяцы (Q1 2025)

  • [ ] Мобильное приложение (React Native + Expo)

  • [ ] AI помощник для составления предложений (OpenAI API)

  • [ ] Голосовой тренажер (Web Speech API)

  • [ ] Flashcards система (Spaced Repetition Algorithm)

  • [ ] Геймификация (достижения, рейтинги, соревнования)

Мечты на будущее (2025-2026)

  • Расширение на другие семитские языки (арабский, фарси — та же корневая система)

  • B2B версия для школ и ульпанов

  • Интеграция с существующими ульпанами (дополнительная домашка)

  • API для разработчиков (морфология как сервис)

  • Community features (форум, обмен опытом, языковые партнеры)


🔗 Попробуйте сами

🌐 Демо: hebrewglot.com

Что можно попробовать бесплатно:

Для разработчиков:

  • Морфологический движок работает прямо в браузере

  • TypeScript для типобезопасности

  • Server Components для оптимизации


🤝 Выводы и takeaways

Что сработало отлично:

  • ✅ Гибридная схема БД (PostgreSQL + SQLite) — быстро и дешево

  • ✅ Морфологический движок с шаблонами — генерация форм в runtime

  • ✅ SEO через блог — основной источник трафика

  • ✅ Freemium модель — люди пробуют, потом платят

  • ✅ TypeScript для сложной логики — меньше багов, проще рефакторинг

Что не сработало или было ошибкой:

  • ❌ Слишком ранняя интернационализация — потратил месяц впустую

  • ❌ Desktop-first подход — большинство с мобильных

  • ❌ Игнорирование маркетинга на старте — долго не было пользователей

  • ❌ Изобретение колеса везде — где-то можно было использовать готовые решения

Главный урок для других разработчиков:

> "Делай, ошибайся, учись, итерируй. Идеальный код в вакууме никому не нужен — важен работающий продукт в руках реальных пользователей. Ship early, ship often."

Второй важный урок:

> "Сложная техническая часть (морфология) — это круто и интересно, но без контента, маркетинга и понимания аудитории это просто хобби-проект. Нужен баланс между технологиями и бизнесом."


📚 Полезные ресурсы

Если хотите повторить похожий проект:

Технологии:

EdTech:

Если изучаете иврит:

  • HebrewGlot — моя платформа (можете попробовать)

  • Pealim — отличный справочник по спряжениям

  • Reverso Context — примеры использования слов


💬 Вопросы и обратная связь

Буду рад ответить на вопросы в комментариях!

Интересные темы для обсуждения:

  • Как вы решаете проблему хранения больших статичных данных?

  • Кто-нибудь еще использует SQLite в продакшене?

  • Делали ли вы морфологические движки для других языков?

  • Какие у вас опыт с монетизацией EdTech проектов?

Контакты:


Статья написана: 4 ноября 2025