A modern, offline-first vocabulary learning application built with Tauri 2, React, TypeScript, and SQLite. Features spaced repetition learning, multiple practice modes, audio playback, daily reminders, and optional Google Drive sync. Supports both desktop and Android platforms.
- Features
- Tech Stack
- Installation
- Usage
- Project Structure
- Configuration
- Development Commands
- Mobile/Android Support
- Recent Updates
- Troubleshooting
- Comprehensive Word Information
- Word definitions with translations
- IPA pronunciation guide
- Audio pronunciation (supports MP3, WAV, OGG, M4A, AAC formats)
- Example sentences
- Word types (noun, verb, adjective, etc.)
- Language-specific proficiency levels (CEFR for European languages, Basic/Intermediate/Advanced for Asian languages)
- Topic categorization
- Related words (synonyms, antonyms, derivatives)
- Optional concept field for alternative learning prompts
- Audio Playback: Integrated audio player with support for online audio URLs
- Pagination: Efficient lazy loading for large vocabulary lists
- Collections: Organize vocabularies into language-specific collections
- Word Count Tracking: Automatic word count per collection
- Import/Export: CSV and plain text CSV support
- Soft Deletion: Collections and words use soft delete for data recovery
Three comprehensive practice modes with spaced repetition:
- Flashcard Practice: See definition/concept, recall the word
- Fill Word Practice: Fill in missing word from example sentence
- Bidirectional Support: Practice definition→word or word→definition
- Hint System: Toggle-able hints for extra support
- Multiple Choice Practice: Choose correct definition from options
Special Features:
- Concept Mode: Toggle between Definition Mode and Concept Mode for alternative learning prompts
- Study Mode vs Practice Mode: Choose to practice with or without progress tracking
- Multi-Mode Completion: Words must complete all three modes in a review cycle to advance boxes
- Auto-Advance: Configurable auto-advance timeout (default: 2 seconds)
-
Three Algorithm Options:
- SM-2 (SuperMemo 2) with dynamic easiness factor
- Modified SM-2 with fixed intervals per Leitner box
- Simple Doubling (interval doubles on each success)
-
Leitner Box System: Configurable 3, 5, or 7 boxes with progressive review intervals
-
Smart Word Selection: Prioritizes due words, limits new words per session
-
Progress Tracking:
- Per-word statistics including streak, interval, easiness factor
- Per-language progress segregation
- Box distribution visualization
- Session statistics with duration and accuracy
-
Advanced Settings:
- New words per day limit (default: 20)
- Daily review limit (default: 100)
- Consecutive correct threshold per box
- Auto-advance timeout (default: 2 seconds)
- Hint display preferences
- Optional Cloud Sync: Backup entire database to Google Drive
- Conflict Detection: Version tracking prevents data loss
- Easy Restore: One-click restore from cloud backup
- Privacy: Works completely offline if you prefer
- Chameleon Theme: Colorful, adaptive design with glassmorphism effects
- Smooth Animations: Floating background elements and transitions
- Mobile-First Design: Responsive layout optimized for all screen sizes
- Bottom Navigation: Easy thumb-accessible navigation
- Interface Languages: English and Vietnamese (extensible i18n system)
- Learning Languages: Supports English, Vietnamese, Spanish, French, German, Korean, Japanese, Chinese
- Language-Specific Levels:
- CEFR (A1-C2) for European languages
- Basic/Intermediate/Advanced for Asian languages
Daily Reminder (New in v0.0.15):
- Schedule Study Reminders: Set a daily reminder at a specific time to practice vocabulary
- Persistent Reminders: Notifications persist through app closure and device reboots
- Auto-Rescheduling: Automatically reschedules for the next day after the reminder fires
- Cross-Platform: Works on Desktop (Windows, macOS, Linux) and Android
- Easy Configuration: Enable/disable and set time from Learning Settings page
Scheduled Notifications:
- Schedule one-time or recurring notifications
- Works on both Desktop and Android
- Runtime permission handling on Android 13+
- Test notification features for debugging
Implementation:
-
Frontend (ProfilePage.tsx)
import { isPermissionGranted, requestPermission } from "@tauri-apps/plugin-notification"; // Check/request permission let permissionGranted = await isPermissionGranted(); if (!permissionGranted) { const permission = await requestPermission(); permissionGranted = permission === "granted"; } // Schedule notification await invoke("schedule_test_notification_one_minute");
-
Backend (notification_commands.rs)
use tauri_plugin_schedule_task::{ScheduleTaskRequest, ScheduleTime, ScheduleTaskExt}; #[tauri::command] pub async fn schedule_notification(app: AppHandle, title: String, body: String, delay_seconds: u64) { let mut parameters = HashMap::new(); parameters.insert("title".to_string(), title); parameters.insert("body".to_string(), body); let task_request = ScheduleTaskRequest { task_name: format!("notification_{}", chrono::Utc::now().timestamp()), schedule_time: ScheduleTime::Duration(delay_seconds), parameters: Some(parameters), }; app.schedule_task().schedule_task(task_request).await?; }
-
Task Handler (scheduled_task_handler.rs)
impl<R: Runtime> ScheduledTaskHandler<R> for NotificationTaskHandler { fn handle_scheduled_task(&self, task_name: &str, parameters: HashMap<String, String>, app: &AppHandle<R>) { // Desktop: Use Tauri's notification API // Android: Handled by MainActivity (see below) } }
-
Android Native (NotificationHelper.kt)
- Purpose: Send notifications using Android's native NotificationManager
- Why needed: Tauri's notification API requires active app context, unavailable in background workers
- Usage: Called by MainActivity when scheduled task triggers
-
Android Native (MainActivity.kt)
- Intercepts scheduled task launches via
onNewIntent()/onCreate() - Extracts notification parameters from intent extras
- Calls
NotificationHelper.sendNotification()to display notification
- Intercepts scheduled task launches via
-
NotificationWorker.kt (not currently used)
- Alternative approach to send notifications directly from WorkManager
- Can be used for more efficient notification delivery without launching MainActivity
Required Dependencies:
# Cargo.toml
tauri-plugin-notification = "2"
tauri-plugin-schedule-task = "0.1"// package.json
"@tauri-apps/plugin-notification": "^2.3.3"Android Configuration:
// build.gradle.kts
android {
defaultConfig {
minSdk = 26 // Required by schedule-task plugin
}
}
dependencies {
implementation("androidx.work:work-runtime-ktx:2.9.0")
}<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>Plugin Initialization (lib.rs):
// IMPORTANT: schedule-task must be initialized FIRST
tauri::Builder::default()
.plugin(tauri_plugin_schedule_task::init_with_handler(NotificationTaskHandler))
.plugin(tauri_plugin_notification::init())
// ... other pluginsHow It Works:
- Desktop: Plugin uses Tauri's async runtime to delay task execution (like setTimeout)
- Android: Plugin uses WorkManager to schedule background workers that run even when app is closed
- When time arrives, WorkManager launches MainActivity with intent extras
- MainActivity extracts notification parameters and displays notification using native Android API
-
Atomic Design Pattern:
- Atoms: Button, Input, TextArea, Select, Badge, Card, Modal, AudioPlayer
- Molecules: SearchBar, VocabularyCard, TopBar, BottomNavigation, StatsCard
- Organisms: VocabularyList, VocabularyForm, CollectionList, PracticeModeSelector
- Templates: MainLayout
- Pages: Home, Collections, AddVocabulary, VocabularyDetail, Practice modes (Flashcard, Fill Word, Multiple Choice), Settings, Learning Settings, Profile, Statistics
-
Offline-First: All data stored locally in SQLite
-
Service Layer: Clean separation between frontend and Tauri backend
-
Type Safety: Full TypeScript coverage with shared types
- SQLite Backend: Lightweight, embedded database with bundled support
- Cross-Platform: Works on desktop and Android
- Platform-Specific Storage: Automatic app data directory selection
- Schema Versioning: Migration system for database updates
- Comprehensive Schema:
- Core tables: users, collections, vocabularies, definitions, example sentences
- Practice tables: practice_sessions, practice_results, word_progress, word_progress_completed_modes
- Organization: topics, tags, related words (many-to-many relationships)
- Settings: learning_settings with algorithm, box count, and notification preferences
- Sync: database_metadata for Google Drive version tracking
- React 19 - UI library
- TypeScript - Type safety
- Tailwind CSS 4 - Styling
- React Router DOM 7 - Navigation
- React i18next - Internationalization
- Lucide React - Icon library
- Framer Motion - Animations
- React Hook Form - Form management
- Tauri 2 - Desktop/mobile app framework
- Rust - Backend language
- rusqlite7 - Embedded SQLite database with bundled support
- Tauri Plugins:
tauri-plugin-google-auth 0.3- Google OAuth authenticationtauri-plugin-notification 2- Native notificationstauri-plugin-schedule-task- Background task schedulingtauri-plugin-dialog 2- File dialogstauri-plugin-opener 2- Open files/URLs
- serde + serde_json - Serialization/deserialization
- chrono - Date/time handling
- uuid - ID generation
- reqwest - HTTP client for Google Drive API
- csv - CSV parsing for import/export
- Node.js 18+ and pnpm
- Rust 1.70+
- For Android: Android SDK and NDK
-
Clone the repository
git clone <repository-url> cd cham-lang
-
Install dependencies
pnpm install
-
Run in development mode
# Desktop pnpm tauri dev # Android (requires Android SDK) pnpm tauri android dev
- Launch the application
- The app creates a local SQLite database automatically
- Create your first collection from the Collections page
- Start adding vocabulary words
- Navigate to Collections page
- Click "Add Collection"
- Enter collection name and select language
- Start adding words to your collection
- Select a collection
- Click "Add Word" button
- Fill in word details:
- Word text
- Word type
- Proficiency level
- IPA pronunciation
- Definitions (with translations)
- Optional concept (alternative learning prompt)
- Example sentences
- Topics
- Related words
- Click "Save"
- Navigate to Practice page
- Select a collection
- Choose practice mode (Flashcard, Fill Word, or Multiple Choice)
- Toggle between Concept Mode and Definition Mode (if concept exists)
- Complete the session
- Review your results and progress
- Go to Settings page
- Choose spaced repetition algorithm (SM-2, Modified SM-2, or Simple Doubling)
- Configure Leitner box count (3, 5, or 7 boxes)
- Set consecutive correct threshold
- Adjust review intervals per box
- Set daily new word limit (default: 20)
- Set daily review limit (default: 100)
- Configure auto-advance timeout
- Toggle hint display in fill word mode
- Go to Learning Settings page
- Enable "Daily Reminder"
- Set your preferred reminder time (e.g., 09:00 for 9 AM)
- Save settings
- Grant notification permissions when prompted (Android 13+)
- Reminder will automatically reschedule for the next day after each notification
- Go to Profile page
- Click "Backup to Google Drive"
- Authenticate with Google account
- Database is uploaded to your Google Drive
To restore:
- Click "Restore from Google Drive"
- System checks for version conflicts
- Confirm restore operation
- Export Collection: Export vocabulary to CSV format
- Import from CSV: Import words from CSV file (with or without headers)
- Plain Text Import: Import simple word lists
cham-lang/
├── src/
│ ├── components/
│ │ ├── atoms/ # Basic UI components
│ │ ├── molecules/ # Composite components
│ │ ├── organisms/ # Complex components
│ │ ├── templates/ # Page layouts
│ │ └── pages/ # Full pages
│ ├── i18n/
│ │ ├── config.ts # i18n configuration
│ │ └── locales/ # Translation files
│ │ ├── en/
│ │ └── vi/
│ ├── types/ # TypeScript types
│ ├── services/ # Tauri command wrappers
│ └── utils/ # Utility functions
│ └── spacedRepetition/ # SR algorithms
├── src-tauri/
│ ├── gen/android/app/src/main/java/com/loidinh/cham_lang/
│ │ ├── MainActivity.kt # Handles scheduled task intents
│ │ ├── NotificationHelper.kt # Native Android notification sender
│ │ └── NotificationWorker.kt # Alternative worker implementation
│ └── src/
│ ├── models.rs # Data models
│ ├── local_db.rs # SQLite operations
│ ├── commands.rs # Vocabulary commands
│ ├── collection_commands.rs # Collection commands
│ ├── gdrive.rs # Google Drive sync
│ ├── notification_commands.rs # Notification scheduling commands
│ ├── scheduled_task_handler.rs # Background task handler
│ └── lib.rs # Main entry point
└── README.md
The project uses Tailwind CSS 4 with custom chameleon theme colors:
- Primary: Teal/Cyan gradient
- Secondary: Amber/Orange gradient
- Accent colors: Emerald, Orange, Pink
Add new interface languages by:
- Creating a new locale folder in
src/i18n/locales/ - Adding translation JSON files
- Importing in
src/i18n/config.ts - Adding to language selector
Add new learning languages by:
- Update
get_level_config()insrc-tauri/src/models.rs - Add language-specific level options
- Update TypeScript types if needed
# Frontend
pnpm tsc --noEmit
# Backend
cargo check --manifest-path=src-tauri/Cargo.toml# Production build
pnpm build
# Android APK
pnpm tauri android build --apk true# Initialize Android (first time only)
pnpm tauri android init
# Run on Android
pnpm tauri android dev
# View Android logs
timeout 10 /home/loidinh/Android/Sdk/platform-tools/adb logcatThe app fully supports Android with:
- SQLite database (platform-specific storage)
- Google Drive sync (OAuth authentication)
- Touch-optimized UI
- Bottom navigation for easy thumb access
- Ensure Android SDK is installed
- Run
pnpm tauri android init(first time only) - Connect device or start emulator
- Run
pnpm tauri android devfor development - Run
pnpm tauri android build --apk truefor production
- Database is created automatically in app data directory
- Check logs for SQLite errors
- Use Google Drive backup/restore for data recovery
pnpm tsc --noEmitcargo check --manifest-path=src-tauri/Cargo.toml# Monitor Android logs
adb logcat
# Filter for app logs
adb logcat | grep -i chameleon- Don't use
invoke()directly in components - use service layer - Don't modify database from frontend - use Tauri commands
- Don't create global state unnecessarily
- Don't bypass SessionManager in practice pages
- Don't hard-code language levels - use backend configuration