<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Yavuz Utku Varol</title>
    <description>The latest articles on Forem by Yavuz Utku Varol (@yavuz_utkuvarol_e213e4bd).</description>
    <link>https://forem.com/yavuz_utkuvarol_e213e4bd</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3819189%2F47a96dd4-c9f0-447c-ba0b-6b0bb80a0941.png</url>
      <title>Forem: Yavuz Utku Varol</title>
      <link>https://forem.com/yavuz_utkuvarol_e213e4bd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/yavuz_utkuvarol_e213e4bd"/>
    <language>en</language>
    <item>
      <title>How I Built a Free German Learning App with Firebase + Vanilla JS (No Framework)</title>
      <dc:creator>Yavuz Utku Varol</dc:creator>
      <pubDate>Thu, 12 Mar 2026 00:02:14 +0000</pubDate>
      <link>https://forem.com/yavuz_utkuvarol_e213e4bd/how-i-built-a-free-german-learning-app-with-firebase-vanilla-js-no-framework-31c3</link>
      <guid>https://forem.com/yavuz_utkuvarol_e213e4bd/how-i-built-a-free-german-learning-app-with-firebase-vanilla-js-no-framework-31c3</guid>
      <description>&lt;p&gt;I've been learning German for a while and couldn't find a tool that fit my workflow — most apps are either too gamified or locked behind paywalls. So I built my own: AlmancaPratik, a free web app for Turkish speakers learning German.&lt;br&gt;
Here's the stack I used and what I learned along the way.&lt;/p&gt;

&lt;p&gt;Stack&lt;/p&gt;

&lt;p&gt;Vanilla JS (ES Modules, no React/Vue)&lt;br&gt;
Firebase Auth (Google Sign-In)&lt;br&gt;
Firestore (user data — words, texts)&lt;br&gt;
GitHub Pages (hosting — completely free)&lt;/p&gt;

&lt;p&gt;Zero server costs. Zero monthly fees.&lt;/p&gt;

&lt;p&gt;Architecture&lt;br&gt;
Every user's data lives under their own Firestore path:&lt;br&gt;
users/{userId}/words/{wordId}&lt;br&gt;
users/{userId}/texts/{textId}&lt;br&gt;
Firestore Security Rules handle authorization — only the authenticated user can read/write their own data:&lt;br&gt;
jsmatch /users/{userId}/{document=**} {&lt;br&gt;
  allow read, write: if request.auth.uid == userId;&lt;br&gt;
}&lt;br&gt;
No backend needed. The rules ARE the backend.&lt;/p&gt;

&lt;p&gt;The Interesting Part — Example Sentences&lt;br&gt;
When a user saves a German word, they can click it to see real usage examples. I pull these from two public APIs in sequence:&lt;/p&gt;

&lt;p&gt;Wiktionary API — parses the "Beispiele" (examples) section from the German Wiktionary entry&lt;br&gt;
Tatoeba API — fallback if Wiktionary returns nothing&lt;/p&gt;

&lt;p&gt;jsasync function fetchExampleSentences(word) {&lt;br&gt;
  let sentences = await fetchFromWiktionary(word);&lt;br&gt;
  if (sentences.length &amp;lt; 2) {&lt;br&gt;
    const tatoeba = await fetchFromTatoeba(word);&lt;br&gt;
    sentences = [...sentences, ...tatoeba.slice(0, 2 - sentences.length)];&lt;br&gt;
  }&lt;br&gt;
  return sentences;&lt;br&gt;
}&lt;br&gt;
Both APIs are free, no auth required. The results are cached in a Map() so the same word is never fetched twice in a session.&lt;/p&gt;

&lt;p&gt;What the App Does&lt;/p&gt;

&lt;p&gt;Text Analysis — paste German text, click any word to look it up&lt;br&gt;
 Quiz — multiple choice + typing mode from your saved words&lt;br&gt;
 Artikel Finder — der / die / das lookup&lt;br&gt;
 Sentence Examples — Wiktionary + Tatoeba&lt;br&gt;
 Word List — save, tag, filter, edit your vocabulary&lt;/p&gt;

&lt;p&gt;Lighthouse Scores&lt;/p&gt;

&lt;p&gt;Performance: 99&lt;br&gt;
Accessibility: 100&lt;br&gt;
Best Practices: 100&lt;br&gt;
SEO: 100&lt;/p&gt;

&lt;p&gt;Getting accessibility to 100 required fixing contrast ratios, adding  landmarks, and making sure all buttons had proper aria-label attributes.&lt;/p&gt;

&lt;p&gt;What I'd Do Differently&lt;br&gt;
Vanilla JS was a deliberate choice — I wanted zero build tooling. But as the app grew, managing state across pages without a framework got messy. If I started today I'd probably use a lightweight option like Preact.&lt;/p&gt;

&lt;p&gt;Try it: almancapratik.com&lt;br&gt;
GitHub feedback welcome in the comments.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
