Logo
Jak zbudowałam własne „Duolingo light” dopasowane do moich potrzeb

Jak zbudowałam własne „Duolingo light” dopasowane do moich potrzeb

Zawsze chciałam mieć aplikację do nauki słówek, która nie uczy mnie o „zielonym jabłku” czy „cioci Ali”, ale o architekturze systemów, Code Review i business English.

Jako programistka szybko przestałam odnajdywać się w typowych aplikacjach językowych. Duolingo jest świetnym narzędziem do zbudowania solidnych fundamentów — szczególnie do poziomu B2. Później jednak zaczyna brakować specjalistycznego słownictwa i realnych kontekstów zawodowych. A ja używam angielskiego codziennie i chcę go poszerzać, nie tylko utrzymywać.

Eksperymentowałam z Gemami skonfigurowanymi tak, aby codziennie coś mi podrzucały. To jednak nie było efektywne — brakowało struktury fiszek i systemu powtórek. Dodatkowo wymagało to aktywnego wchodzenia do Gemini.

Postanowiłam więc napisać własne rozwiązanie. Tym razem zamiast Claude’a zaprzągłam duet: Gemini (specyfikacja) + Antigravity.

W godzinę powstało AI English PRO — spersonalizowana aplikacja PWA, która uczy mnie dokładnie tego, co przydaje mi się w pracy.

Proces: od pomysłu do działającej aplikacji

Zrobiłam to zgodnie ze „sztuką”.

Najpierw kilkukrotnie przeiterowałam z Gemini (rozbudowany model do rozwiązywania zadań programistycznych), jak aplikacja powinna wyglądać:

  • UI
  • Architektura
  • Model danych
  • Hosting
  • Sposób generowania i przechowywania lekcji

Dopiero potem przygotowałam precyzyjny prompt do asystenta kodującego i uruchomiłam Antigravity.

Schemat wyglądał następująco:

Specyfikacja → prompt → generacja → poprawki → gotowa aplikacja → weryfikacja przez inny LLM → testy użytkownika

To nie był chaos. To był świadomy proces projektowy.

I tu pojawia się element, który w pracy z AI jest absolutnie kluczowy: planowanie.

Im lepiej zdefiniowany problem na wejściu, tym mniej halucynacji, mniej przypadkowych decyzji architektonicznych i mniej „magicznego” kodu, którego nikt nie rozumie.

Asystent kodujący nie zastępuje etapu projektowego. On go bezlitośnie weryfikuje. Jeśli specyfikacja jest nieprecyzyjna, dostajemy nieprecyzyjne rozwiązanie — tylko szybciej.

Planowanie pozwoliło mi:

  • utrzymać kontrolę nad strukturą projektu,
  • ograniczyć refaktoryzację,
  • świadomie podjąć decyzje technologiczne,
  • skrócić czas implementacji do minimum bez utraty jakości.

W erze AI szybkość generowania kodu nie jest przewagą.
Przewagą jest jakość myślenia przed jego wygenerowaniem.

Stack technologiczny

Oto stack, którego użyłam do stworzenia aplikacji. Nie bawiłam się w szukanie najbardziej hype frameworka, postawiłam na sprawdzone rozwiązania, co do których miałam pewność, że LLM ma dużo danych treningowych a i ja mam w nich doświadczenie, więc mogłam na bieżąco kontrolować postęp.

1. Vite – szybkość i PWA bez komplikacji

Wybrałam Vite, bo "life is too short for slow builds". Hot Module Replacement w Vite działa błyskawicznie. Przy pracy z React + Tailwind każda sekunda opóźnienia rozbija koncentrację. Tutaj zmiany są widoczne natychmiast.

Progressive Web App

Chciałam, żeby aplikacja działała jak natywna apka na moim telefonie – z ikonką na ekranie głównym i działaniem offline. Dzięki vite-plugin-pwa konfiguracja była trywialna. W vite.config.ts dodałam tylko plugin, który generuje manifest.webmanifest i obsługuje Service Workery:

// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa';

export default defineConfig({
  plugins: [
    react(),
    VitePWA({
      registerType: 'autoUpdate',
      manifest: {
        name: 'AI English PRO',
        short_name: 'AIEnglish',
        theme_color: '#000000',
        icons: [ ... ] // ikony dla iOS i Androida
      }
    })
  ]
});

Efekt? Apka instaluje się na iPhone i Androidzie, działa na pełnym ekranie i cache'uje zasoby.

2. Firebase – backend bez infrastruktury

Skupiłam się na logice biznesowej, a nie na stawianiu serwerów. Firebase ogarnął całą "ciężką" robotę serwerową:

  • Authentication: Logowanie użytkowników (Google/Email) "out of the box".
  • Firestore: Baza danych NoSQL do trzymania postępów, talii kart i historii nauki.
  • Offline Persistence: To kluczowy feature. Włączyłam enableIndexedDbPersistence, dzięki czemu aplikacja działa w metrze czy samolocie. Dane synchronizują się same, gdy złapię zasięg.
// services/firebase.ts
import { getFirestore, enableIndexedDbPersistence } from "firebase/firestore";

export const db = getFirestore(app);

// To jedna linijka, która robi magię offline:
enableIndexedDbPersistence(db).catch((err) => {
    console.warn('Firestore persistence error', err);
});

Ciekawostka: Trzymam nawet klucze API do AI w zabezpieczonej kolekcji w Firestore, którą aplikacja pobiera przy starcie, zamiast zaszywać je w kodzie frontendu. Do wypychania zbudowanej appki nie używam żadnych pipelineów tylko narzędzi konsolowych Firebase, dzieki czemu mój agent kodujący nie ma dostępu ani do kluczy API AI ani do moich danych dostępowych do appki. Jedyne do czego ma dostęp do danych .env, w których zaszyte są informacje, które i tak są dostepne na stronie.

W obecnych realiach to nie jest opcja — to konieczność. Trzymanie haseł i kluczy API lokalnie lub w repozytorium to zaproszenie do problemów: wycieku danych, nadużyć API, niekontrolowanych kosztów. Jeżeli budujemy coś z użyciem AI, bezpieczeństwo musi być elementem architektury, a nie dodatkiem.

3. Gemini AI – serce projektu

To jest "serce" projektu. Zamiast ręcznie wpisywać słówka do bazy, podłączyłam Google Gemini API (model gemini-2.0-flash), który generuje dla mnie lekcje "on demand". Dzięki temu aplikacja jest nieskończona i kontekstowa.

Jak to działa?

W serwisie aiService.ts mam funkcję, która buduje prompt dla AI. To nie jest zwykłe "daj mi słówka", to coś więcej:

  1. Rola: Mówię AI, że jest "Native Speakerem i mentorem business english".
  2. Kontekst ucznia: "Uczeń to Software Engineer na poziomie C1". Dzięki temu dostaję zdania o deploymentach, stakeholderach i deadlinach, a nie o pogodzie.
  3. Pamięć: Wysyłam do AI listę słów, które już umiem (learnedWords), z instrukcją: "DO NOT include any of these words". Dzięki temu materiał się nie powtarza.
  4. Struktura: Wymuszam format JSON, żeby łatwo wyświetlić to w UI.

Oto fragment promptu, który robi robotę:

const prompt = `
    You are a native speaker and an elite English mentor. 
    Your student is a software engineer. Current level: ADVANCED.
    
    Prepare 15 new words. Focus on:
    1. Professional Tech English: Architectural terms, code reviews.
    2. Daily Native Life: Casual idioms used in Silicon Valley.
    
    Return ONLY a JSON array:
[
      {
        "en": "word",
        "pl": "translation",
        "example": "sentence in tech context",
        "category": "Tech/Business"
      }
    ]
`;

Wynik? Dostaję fiszki typu:

  • "Bottleneck" – z przykładem o wydajności bazy danych.
  • "Low-hanging fruit" – w kontekście prioretyzacji tasków w JIRA.
  • "To mitigate" – przy omawianiu ryzyka w projekcie.

4. TTS – każde słowo można odsłuchać

Każda fiszka ma opcję odsłuchania wymowy. Mogłam podpiąć zewnętrzne API TTS albo wykorzystać lokalne modele (ostatnio testowałam takie rozwiązania), ale w tym projekcie byłoby to wyciąganie armaty na muchę. Tutaj liczy się prostota, szybkość i niezawodność.

Zastosowałam natywne rozwiązanie przeglądarkowe: window.speechSynthesis i SpeechSynthesisUtterance. To standardowe interfejsy JavaScript dostępne w nowoczesnych przeglądarkach.

Strategia wyboru głosu

Zależało mi na brytyjskim akcencie. Algorytm wyboru głosu działa w kolejności:

  1. Google UK English Female
  2. Google UK English Male
  3. Daniel (macOS/iOS)
  4. Serena (macOS/iOS)
  5. Dowolny głos en-GB
  6. Dowolny głos angielski jako fallback

Dzięki temu aplikacja adaptuje się do systemu użytkownika, ale zachowuje priorytet jakości i akcentu.

Parametry odtwarzania

  • Rate: 0.9 — minimalnie wolniej niż naturalna rozmowa
  • Pitch: 1.0 — naturalna wysokość
  • Volume: 1.0 — pełna głośność

To nie są przypadkowe wartości. Tempo ma wspierać przetwarzanie poznawcze, a nie je utrudniać.

Dlaczego takie podejście?

  • Brak kosztów i brak latencji — działa offline.
  • Naturalna jakość — korzysta z silników systemowych.
  • Prywatność — tekst przetwarzany lokalnie, bez wysyłania do chmury.

W dodatku głos lektora jest jak “pudełko czekoladek, you never know what you’re gonna get” jak mawiał Forrest Gump. I to też robi fajną robotę.

Skalowanie: poziomy Beginner i Intermediate

Początkowo aplikacja była tylko dla mnie. Potem rodzina zaczęła pytać, czy też mogą korzystać. Dodałam więc poziomy Beginner i Intermediate.

Wymagało to:

  • zmiany promptu,
  • wprowadzenia poziomów trudności,
  • uproszczenia przykładów,
  • ograniczenia specjalistycznych idiomów,
  • dodania języków do UI.

Ta sama aplikacja, inne profile użytkownika, różne ścieżki nauki. To już nie jest eksperyment. To system.

AI English PRO Interface

Podsumowanie

Ten projekt nie jest o fiszkach. To przykład, jak dziś buduje się wyspecjalizowane narzędzia z użyciem AI. Specyfikacja powstaje w dialogu z modelem. Kod generuje asystent. Infrastruktura działa jako usługa. Bariera wejścia jest niska.

Kluczowe nie jest już samo pisanie kodu. Kluczowe jest:

  • rozumienie słabych punktów systemu,
  • znajomość ograniczeń modeli i technologii,
  • świadome projektowanie architektury,
  • precyzyjne zdefiniowanie potrzeby biznesowej.

AI przyspiesza implementację, ale nie rozwiązuje problemu za nas. Jeśli nie rozumiemy, gdzie system może zawieść i jaką realną wartość ma dostarczyć, narzędzie będzie tylko demonstracją technologii.

Zawód programisty nie zniknie. Zmieni się. Od nas zależy, czy będziemy operatorami generatorów kodu, czy inżynierami, którzy rozumieją system — w całości, wraz z jego ograniczeniami i celem biznesowym.