PWA English Practice App
Build a Progressive Web App for English language learning that uses browser Text-to-Speech (TTS) for practicing listening comprehension with English-Korean sentence pairs.
What This Skill Does
Creates a single-page PWA with playlist management, CSV/text import, adjustable playback controls, voice selection, and offline capabilities using vanilla JavaScript, Web Speech API, and Service Workers.
Implementation Steps
1. Project Structure Setup
Create the following file structure:
```
/
├── index.html # Main UI with upload and player screens
├── app.js # State management and application logic
├── sw.js # Service Worker for offline support
├── manifest.json # PWA manifest
├── vercel.json # Deployment configuration (if using Vercel)
└── icon-*.png # PWA icons (multiple sizes)
```
2. Core HTML Structure (index.html)
Build a single-page application with two main screens:
**Upload/Playlist Screen (`uploadCard`)**:
CSV file upload inputText paste textarea (alternating English/Korean lines)Clipboard paste buttonSample data buttonSaved playlists list (from localStorage)**Player Screen (`playerCard`)**:
Track title displayEnglish text (large, prominent)Korean translation (smaller, below)Progress bar with real-time character position trackingPlayback controls: Previous, Play/Pause/Resume, NextSpeed control (0.5x - 1.5x, step 0.1)Repeat mode toggle (none/one/all)Voice selection dropdown (populated from available system voices)Back to playlists buttonInclude these CDN dependencies:
PapaParse for CSV parsingMaterial Icons or similar for UI icons3. State Management (app.js)
Implement global state variables:
```javascript
let tracks = [] // Array of {english, korean} objects
let currentTrack = 0 // Current track index
let isPlaying = false // Playing state
let isPaused = false // Paused state
let playbackSpeed = 1.0 // TTS rate (0.5-1.5)
let repeatMode = 'none' // 'none', 'one', or 'all'
let selectedVoice = null // User-selected voice or null for auto
let currentProgress = 0 // Character position for progress bar
```
4. TTS Playback Implementation
**Create `playCurrentTrack()` function**:
Instantiate `SpeechSynthesisUtterance` with current English textAuto-select best English voice (prefer local service over network)Apply `selectedVoice` if user has chosen oneSet `utterance.rate` from `playbackSpeed`Attach event handlers: - `onboundary`: Update progress bar using `charIndex / totalLength`
- `onend`: Handle repeat logic (replay track, advance, or stop)
- `onerror`: Reset state and log error
Call `window.speechSynthesis.speak(utterance)`**Create `pauseCurrentTrack()` function**:
Attempt `window.speechSynthesis.pause()`If unsupported, fall back to `cancel()` and set flag for restart**Create `resumeCurrentTrack()` function**:
If paused via `pause()`, call `resume()`If cancelled, restart from beginning with `playCurrentTrack()`**Important Web Speech API Considerations**:
No native pause/resume in all browsers → implement fallbackCannot seek or resume from arbitrary positionsProgress tracking via `onboundary` is character-based, not time-basedAvailable voices vary by browser and OS5. Voice Selection Feature
**Populate voice dropdown**:
Use `window.speechSynthesis.getVoices()` to fetch available voicesFilter for English-language voices (check `voice.lang` starts with 'en')Mark local voices with 🔸 indicator (`voice.localService === true`)Populate `<select>` element with voice options**Handle voice selection**:
On change, update `selectedVoice` variableIf currently playing, restart playback with new voice**Voice loading timing**:
Voices may load asynchronously; listen to `voiceschanged` eventPopulate dropdown on page load and after `voiceschanged`6. Playlist Management
**Import methods**:
**CSV Upload**: - Use PapaParse to parse uploaded file
- Validate required columns: `english`, `korean`
- Display error if columns missing
**Text Paste**: - Split by newlines
- Alternate lines: even = English, odd = Korean
- Skip empty lines
**Clipboard Paste**: - Use `navigator.clipboard.readText()`
- Process like text paste
**Sample Data**: - Provide hardcoded example tracks for demo
**Storage**:
Save playlists to localStorage with key format: `playlist_${timestamp}`Each playlist: `{name, tracks, created}`On app load, populate saved playlists listImplement delete playlist functionality7. Playback Controls
**Speed Control**:
Range input (0.5 to 1.5, step 0.1)Display current speed (e.g., "1.0x")Note: Changing speed during playback requires restart (Web Speech API limitation)**Repeat Modes**:
None: Stop after last trackOne: Repeat current track indefinitelyAll: Loop playlist**Navigation**:
Previous: Go to previous track (or restart if < 3 seconds in)Next: Advance to next trackAuto-advance on track end (unless repeat-one)8. Service Worker (sw.js)
**Caching Strategy**:
Cache name: `english-player-v1` (increment on updates)Cache on install: - index.html
- app.js
- manifest.json
- Icons
- CDN resources (PapaParse, icons)
**Fetch Strategy**:
Cache-first with network fallback: 1. Try to serve from cache
2. If miss, fetch from network
3. Cache successful network responses
**Cleanup**:
On `activate` event, delete old caches that don't match current `CACHE_NAME`**Registration**:
Register Service Worker in app.js with: ```javascript
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
}
```
9. PWA Manifest (manifest.json)
Include required PWA fields:
`name`, `short_name``start_url`: "/"`display`: "standalone"`theme_color`, `background_color``icons`: Array of icons (192x192, 512x512 minimum)Link in `<head>`:
```html
<link rel="manifest" href="/manifest.json">
```
10. Deployment Configuration
**For Vercel (vercel.json)**:
```json
{
"headers": [
{
"source": "/sw.js",
"headers": [
{"key": "Service-Worker-Allowed", "value": "/"}
]
}
]
}
```
**Alternative deployment**:
Any static host (Netlify, GitHub Pages, Cloudflare Pages)Requires HTTPS for Service Worker and Web Speech API11. Local Development
Requires HTTP server (not `file://` protocol):
```bash
Option 1: Python
python -m http.server 8000
Option 2: Node.js
npx serve
```
Access at `http://localhost:8000`
12. Testing TTS Functionality
**Browser Testing**:
Test on Chrome, Safari, Firefox, EdgeVoice quality varies by browser and OSCheck browser console for speech synthesis errors**Voice Availability**:
Test on different operating systemsVerify voice dropdown populates correctlyEnsure local voices work offline**Debugging Tools**:
Browser DevTools → Console for TTS eventsCheck `speechSynthesis.speaking`, `.pending`, `.paused` statesMonitor `onboundary` events for progress trackingKey Technical Considerations
Web Speech API Limitations
**No true pause/resume**: Implement fallback using cancel+restart**No seeking**: Must always restart from beginning**Speed changes require restart**: Cannot adjust rate during playback**Progress estimation**: `onboundary` provides character position, not time**Voice loading**: May be asynchronous; handle `voiceschanged` eventState Management
Pure vanilla JavaScript, no frameworksImperative DOM updatesGlobal state variables in app.jsCSV Format
Expected import format:
```csv
english,korean
"English sentence here","한국어 번역"
```
Both columns required and validated in upload handler.
Constraints
Use vanilla JavaScript (no frameworks)Browser must support Web Speech APIService Workers require HTTPS (except localhost)TTS voice quality depends on OS/browserlocalStorage limits apply (typically 5-10MB)Example Usage
1. User visits app
2. Uploads CSV with English-Korean pairs or pastes text
3. App saves playlist to localStorage
4. User opens player screen
5. Selects preferred voice (optional)
6. Adjusts speed and repeat mode
7. Plays through tracks with progress tracking
8. App works offline after first visit (PWA)