Aplikacja mobilna w pojedynkę: Linkoglot od jednej bazy kodu do dwóch sklepów
Jak sam zbudowałem i wydałem Linkoglot, aplikację AI do nauki języków: Expo, Supabase, RevenueCat, Lingui w 30 językach i wymagania obu sklepów.
Linkoglot, aplikację AI do nauki języków, zbudowałem sam w kilka miesięcy i wydałem w Google Play 10 lipca 2026, a w App Store 21 lipca 2026. Jedna osoba udźwignie dwie platformy, bo za każdą ciężką pracą stoi narzędzie: Expo, Supabase, RevenueCat i Lingui. Na Twojej głowie zostaje zakres, reguły sklepów i decyzja, w czym nie ufać telefonowi.
W skrócie
- W pojedynkę znaczy: każda warstwa. Aplikacja, baza danych, prompty AI, płatności, wpisy w sklepach, strona produktu, dokumenty prawne i usuwanie konta.
- Expo buduje obie natywne aplikacje z jednej bazy kodu, RevenueCat daje jedno SDK nad płatnościami obu sklepów, a Postgres pilnuje reguł, które klient mógłby podrobić.
- Oba sklepy wymagają usuwania konta, informacji o prywatności i własnego systemu płatności za funkcje cyfrowe. Zaplanuj to jako funkcje w pierwszym tygodniu, a nie jako papierologię w ostatnim.
Co obejmowało „w pojedynkę”?
Linkoglot zamienia to, co masz wokół siebie, w materiał do nauki. Kierujesz aparat na widok, stronę z tekstem albo kilka przedmiotów, a AI robi ze zdjęcia fiszki, quizy i wyzwania. Potem ćwiczysz: czat tekstowy z tutorem AI, wymowę i rozmowy głosowe w czasie rzeczywistym.
Praca w pojedynkę oznaczała każdą warstwę, nie tylko samą aplikację:
- produkt i jego projekt graficzny;
- aplikację w Expo na iPhone'a i Androida;
- schemat bazy w Supabase, row-level security i triggery;
- prompty AI;
- płatności: darmowy okres próbny, potem subskrypcja miesięczna albo roczna;
- wpisy w obu sklepach, z przetłumaczonym opisem i zrzutami ekranu dla każdego języka;
- stronę produktu w Next.js w 30 wersjach językowych, politykę prywatności, regulamin i stronę do usuwania konta;
- projekt poboczny: narzędzie CLI
lingui-translate-aiw npm.
Na początku dużo pisałem ręcznie, później dużo z pomocą AI. Tak czy inaczej, każda decyzja opisana niżej była moja, tak samo jak naprawianie jej skutków.
Jakie zadania bierze na siebie stack?
| Zadanie | Narzędzie | Co robi |
|---|---|---|
| Natywne projekty iOS i Android | Expo z development buildem i prebuildem | Generuje foldery ios i android z konfiguracji aplikacji i config pluginów |
| Budowanie i podpisywanie | EAS Build | Hostowana usługa buildów; buildy iOS powstają na maszynach z macOS w chmurze Expo, a usługa potrafi też zarządzać keystore'ami, profilami provisioningowymi i certyfikatami |
| Konta, dane, zdjęcia, aktualizacje na żywo | Supabase | Logowanie przez Google i Apple, Postgres, Storage, Realtime |
| Dane z serwera w telefonie | TanStack Query | Cache po stronie klienta, który aplikacja zachowuje między uruchomieniami |
| Subskrypcje w obu sklepach | RevenueCat | Jedno SDK nad StoreKit i Google Play Billing |
| 30 języków | Lingui | Teksty w kodzie, katalog .po dla każdego języka |
Interfejs to NativeWind z React Native Reusables.
Expo nazywa to podejście Continuous Native Generation: krótko żyjące projekty natywne powstają tylko wtedy, gdy są potrzebne. Ręczne zmiany w tych folderach mogą przepaść przy następnym npx expo prebuild --clean, więc zmiany wpisuje się w config pluginy. Nawet jedyny natywny dodatek Linkoglota, rozszerzenie iOS Notification Service Extension do powiadomień push, jest generowany: @bacons/apple-targets tworzy go podczas prebuildu z folderu targets/.
Development build (aplikacja skompilowana z expo-dev-client) ma znaczenie także dla płatności. Poradnik RevenueCat dla Expo ostrzega, że w Expo Go SDK podmienia wywołania natywne na atrapy (mock API), a prawdziwe zakupy w tym trybie nie działają.
O czym ma decydować baza, a nie telefon?
Aplikacja na telefonie to klient, którego nie kontrolujesz, więc dwie części Linkoglota nigdy nie zależą od tego, czy telefon mówi prawdę.
Osiągnięcia. Warstwa gry to XP, osiągnięcia i serie dni. Osiągnięcia przyznaje Postgres: triggery AFTER INSERT i AFTER UPDATE wywołują funkcję w schemacie private. Tabela osiągnięć ma włączone row-level security i wyłączone wszystkie operacje dla klienta. Dokumentacja Supabase opisuje skutek wprost: po włączeniu RLS żadne dane nie są dostępne przez API z kluczem publicznym (publishable key), dopóki nie utworzysz polityk. Aplikacja nie przyzna więc sobie osiągnięcia sama, a reguła jest zapisana raz dla obu platform.
Zdjęcia. Telefon opuszcza tylko skompresowana kopia:
- 01 W telefonie Zdjęcie zmniejsza się do 1280 px wysokości i zapisuje jako JPEG z jakością 0,25.
- 02 Do Storage Plik trafia do folderu użytkownika w Supabase Storage.
- 03 Analiza na serwerze Analiza odbywa się po stronie serwera, a aplikacja odczytuje jej wynik.
Czego sklepy wymagają przed wydaniem?
Reguły sklepów zamieniają się w ekrany, formularze i kod, więc należą do planu. Te cztery ukształtowały Linkoglot:
| Wymaganie | Reguła | W Linkoglocie |
|---|---|---|
| Usunięcie konta | Apple 5.1.1(v): aplikacja, w której można założyć konto, musi pozwalać je usunąć w aplikacji. Google Play: ścieżka w aplikacji plus link w sieci do zgłoszenia usunięcia | Usuwanie w aplikacji i strona Delete Account w serwisie produktu |
| Prywatna opcja logowania | Apple 4.8: aplikacja z Google Sign-In albo innym logowaniem społecznościowym musi też oferować równoważne logowanie, które ogranicza dane do imienia i e-maila, pozwala ukryć adres e-mail i bez zgody nie zbiera interakcji z aplikacją do celów reklamowych | Logowanie przez Google i przez Apple |
| Informacje o prywatności | Szczegóły prywatności w App Store, łącznie z danymi zbieranymi przez zewnętrzne SDK. Formularz Data safety w Google Play, wymagany nawet na ścieżkach testów zamkniętych i otwartych | App Store wymienia sześć kategorii danych powiązanych z Tobą: zakupy, dane kontaktowe, treści użytkownika, identyfikatory, dane o użyciu i diagnostykę. Play podaje, że dane są szyfrowane podczas przesyłania i że możesz poprosić o ich usunięcie |
| Płatności sklepu za funkcje cyfrowe | Apple 3.1.1: płatne funkcje w aplikacji sprzedaje się przez in-app purchase. Google Play: funkcje i treści cyfrowe w aplikacji przechodzą przez system płatności Google Play, z wymienionymi wyjątkami | RevenueCat nad oboma systemami płatności |
Co naprawiła pierwsza aktualizacja?
Wersja 1.0.1 trafiła do Google Play 22 lipca, a do App Store 29 lipca. Notka do wydania w moim tłumaczeniu: „Naprawiono błąd, przez który zestawy do nauki generowane przez AI w tle zapisywały się poprawnie, ale nie odświeżały się na ekranie”.
Dane były dobre, a ekran nieaktualny. Przy cache po stronie klienta, takim jak TanStack Query, ekran pokazuje dane z cache, dopóki coś nie oznaczy ich jako nieaktualnych. Dokumentacja TanStack opisuje invalidateQueries właśnie na sytuację, w której wiesz na pewno, że dane zapytania są nieaktualne: metoda oznacza zapytania jako przestarzałe i pobiera ponownie te, które są na ekranie. Zadanie w tle, które zapisuje dane, ma więc drugie zadanie: dać znać ekranowi.
Jak działają subskrypcje w dwóch sklepach?
Model: darmowe pobranie, darmowy okres próbny, potem subskrypcja miesięczna albo roczna. RevenueCat opisuje swój produkt jako backend i SDK, które opakowują StoreKit, Google Play Billing i RevenueCat Billing. W aplikacji jego SDK react-native-purchases jest schowane za moimi hookami do inicjalizacji, zakupu, anulowania i przeliczania walut. Sprawdzenie dostępu, które pokazuje dokumentacja RevenueCat, jest krótkie:
const customerInfo = await Purchases.getCustomerInfo();
// "pro" to przykładowy identyfikator uprawnienia (entitlement)
const hasAccess = typeof customerInfo.entitlements.active["pro"] !== "undefined";
Entitlement to według RevenueCat poziom dostępu, funkcji albo treści, do którego użytkownik ma prawo. SDK trzyma te informacje w cache i odświeża go, gdy jest starszy niż 5 minut, ale tylko przy getCustomerInfo(), zakupie albo przywróceniu zakupów. Na zmiany pomiędzy tymi momentami jest addCustomerInfoUpdateListener, który uruchamia się przy każdej zmianie tych informacji na urządzeniu.
Kredyty na dokładkę do subskrypcji. Funkcje AI rozlicza się w kredytach. Plan daje miesięczną pulę, a dodatkowe kredyty nie wygasają. Opublikowana tabela kosztów (z 7 czerwca 2026) pokazuje, jak bardzo różnią się funkcje:
| Funkcja | Model zalecany | Model Max |
|---|---|---|
| Fiszki, generowanie bazowe | 0,88 | 2,4 |
| Czat, za wiadomość | 1,7 | 5,52 |
| Rozmowa głosowa, za minutę | 100 | 100 |
Na zalecanym modelu minuta rozmowy głosowej kosztuje tyle kredytów, co mniej więcej 59 wiadomości na czacie.
Jak jedna osoba wydaje aplikację w 30 językach?
App Store podaje 30 języków Linkoglota, a strona produktu ma te same 30 wersji językowych, każdą z alternatywą hreflang. Tekst mieszka w czterech miejscach i każde ma własną drogę:
- Teksty aplikacji. Lingui z katalogiem
.podla każdego języka, ta sama biblioteka, której używa ta strona (jak powstała ta strona). Obie biblioteki porównuję w tekście Lingui czy i18next. - Tłumaczenie na dużą skalę. Skrypt w repozytorium wysyłał katalogi do LLM partiami. 17 czerwca 2026 stał się narzędziem CLI
lingui-translate-aiw npm, a wersja 1.1.0 z obsługą liczby mnogiej ICU wyszła 10 lipca 2026, tego samego dnia, w którym aplikacja pojawiła się w Google Play. Jak działa to narzędzie i jak sprawdzam jego szkice. - Treści w bazie. Nazwy i opisy osiągnięć są zapisane jako jsonb, a tłumaczenia uzupełnia trigger
translate_column. - Wpisy w sklepach. Przetłumaczony opis i zrzuty ekranu dla każdego języka.
Jedna rzecz do sprawdzenia, jeśli robisz to samo: poradnik Lingui dla React Native zaznacza, że obsługa Intl może się różnić między wersjami React Native i systemów, i dodaje polyfille dla Intl.Locale oraz Intl.PluralRules. Linkoglot działa od iOS 16.4 i Androida 7.0 wzwyż, więc liczbę mnogą testuj na najstarszych wspieranych wersjach.
Ograniczenia
- Żadnych liczb o użytkownikach: aplikacja działa, ale jest jeszcze bez trakcji, a ten tekst dotyczy budowania i wydania, nie wzrostu.
- Pomijam rundy weryfikacji w sklepach, to, jak w produkcji działają funkcje AI i rozmowy głosowe, oraz powody wyboru każdego narzędzia zamiast alternatyw. Każdy z tych tematów zasługuje na osobny tekst.
- Opisy ze sklepów i tabelę kredytów cytuję w wersji z podanych dat. Ceny i koszty mogą się potem zmienić.
Linkoglot ma własny rozdział na mojej stronie głównej, obok reszty moich projektów na stronie z projektami.
Źródła
- Continuous Native Generation (Expo docs) (otwiera się w nowej karcie) (dostęp: )
- Introduction to development builds (Expo docs) (otwiera się w nowej karcie) (dostęp: )
- EAS Build (Expo docs) (otwiera się w nowej karcie) (dostęp: )
- expo-apple-targets, @bacons/apple-targets (GitHub) (otwiera się w nowej karcie) (dostęp: )
- Using RevenueCat with Expo (RevenueCat docs) (otwiera się w nowej karcie) (dostęp: )
- Entitlements (RevenueCat docs) (otwiera się w nowej karcie) (dostęp: )
- Getting subscription status (RevenueCat docs) (otwiera się w nowej karcie) (dostęp: )
- App Review Guidelines (Apple Developer) (otwiera się w nowej karcie) (dostęp: )
- App privacy details on the App Store (Apple Developer) (otwiera się w nowej karcie) (dostęp: )
- App account deletion requirements (Play Console Help) (otwiera się w nowej karcie) (dostęp: )
- Data safety section (Play Console Help) (otwiera się w nowej karcie) (dostęp: )
- Payments policy (Play Console Help) (otwiera się w nowej karcie) (dostęp: )
- Row Level Security (Supabase docs) (otwiera się w nowej karcie) (dostęp: )
- Query Invalidation (TanStack Query docs) (otwiera się w nowej karcie) (dostęp: )
- React Native internationalization (Lingui docs) (otwiera się w nowej karcie) (dostęp: )
- Linkoglot on the App Store (otwiera się w nowej karcie) (dostęp: )
- Linkoglot: AI Credits (otwiera się w nowej karcie) (dostęp: )