All data access is routed through src/services/. Components and hooks never call fetch() or Dexie directly. This abstraction enables a future migration to a REST or Firebase backend without touching UI code.
Loads static JSON files from public/data/. All methods return Promises and are cached in memory.
// Returns:
{
languages: ['spanish', 'chinese', 'swedish'],
defaultLanguage: 'spanish',
zipfLevels: [1, 2, 3, 4, 5],
audioPath: '/audio',
modelsPath: '/models',
sentencesPerLevel: 50
}Returns all sentences for a language. Cached after first fetch.
// Sentence shape:
{
id: 'es_001', // unique identifier
text: '¿Dónde está el baño?',
translation: 'Where is the bathroom?',
pronunciation: 'DOHN-deh eh-STAH el BAH-nyoh',
audioUrl: '/audio/es_001.mp3',
zipfLevel: 1 // 1 (easiest) to 5 (hardest)
}Filters sentences where zipfLevel <= level.
Returns config.languages.
Future API migration: replace load(url) with apiFetch(endpoint) — interface unchanged.
Wraps Dexie (IndexedDB). All methods return Promises.
// CardRecord shape:
{
id: 'es_001',
language: 'spanish',
zipfLevel: 1,
interval: 6, // days until next review
repetitions: 2, // successful review streak
easeFactor: 2.5, // SM-2 ease factor
nextReview: '2026-05-25T10:00:00.000Z'
}Returns cards whose nextReview is in the past (or null) filtered by language and max level.
// ProgressRecord shape:
{
language: 'spanish',
totalCorrect: 42,
totalReviewed: 60
}Returns a JSON string containing all cards and progress records.
Bulk-inserts exported data. Safe to call multiple times (uses bulkPut).
Manages offline speech recognition.
Loads the Vosk WASM model from modelPath. Falls back silently if Vosk is unavailable.
Call once on language change.
Transcribes an audio Blob. Uses Vosk if initialized, otherwise delegates to Web Speech API.
Returns 0–100 pronunciation score based on word-level overlap.
VoskService.score('donde esta el banyo', '¿Dónde está el baño?') // → 100
VoskService.score('donde esta', '¿Dónde está el baño?') // → 50Pure functions — no state, no side effects.
Returns default card state: { interval: 1, repetitions: 0, easeFactor: 2.5, nextReview: null }.
quality is 0–5:
- 5: Perfect response
- 4: Correct with slight hesitation
- 3: Correct with difficulty
- 2: Incorrect — easy to recall
- 1: Incorrect — hard to recall
- 0: Complete blackout
Returns updated { interval, repetitions, easeFactor, nextReview }.
Returns true if card.nextReview is in the past or null.
Returns current unlocked level (1–5) based on total correct answers.
Returns sentences where zipfLevel <= level.
Returns 0–100 percentage progress toward unlocking the next level.
| Field | Type | Description |
|---|---|---|
languages |
string[] |
Available language codes |
defaultLanguage |
string |
Language loaded on first visit |
zipfLevels |
number[] |
Level numbers (e.g. [1,2,3,4,5]) |
audioPath |
string |
Base path for audio files |
modelsPath |
string |
Base path for Vosk models |
sentencesPerLevel |
number |
Correct answers needed to unlock next level |
| Field | Type | Description |
|---|---|---|
id |
string |
Unique ID, format <lang>_<NNN> |
text |
string |
Native language sentence |
translation |
string |
English translation |
pronunciation |
string |
Phonetic guide (IPA or respelling) |
audioUrl |
string |
Path to MP3 relative to public root |
zipfLevel |
number |
Difficulty level 1–5 |