Skip to content

danbileee/furigana

Repository files navigation

AI Japanese Reading Assistant

A small Next.js app that helps you read Japanese text by adding furigana (readings) on top of kanji using OpenAI. It supports:

  • Ruby-based furigana rendering (HTML <ruby><rt> tags)
  • Two display modes: always show vs. hover to see furigana
  • Local history sidebar with editable titles (saved in localStorage)

1. Running the app locally

1.1. Prerequisites

  • Node.js 20+ recommended
  • pnpm (preferred) or npm/yarn
  • An OpenAI API key

1.2. Install dependencies

From the project root:

pnpm install
# or
npm install

1.3. Configure environment variables

Create .env.local in the project root:

OPENAI_API_KEY=sk-...

# Optional: choose which OpenAI model to use for furigana generation
# Recommended:
#   - gpt-5.2 or gpt-5.2-pro (best accuracy)
#   - gpt-5-mini (cheaper GPT‑5)
#   - gpt-4o (default, good balance)
# Budget:
#   - gpt-4o-mini (cheapest, may skip some kanji)
OPENAI_MODEL=gpt-4o

Note: Do not commit .env.local to git.

1.4. Start the dev server

pnpm dev
# or
npm run dev

Open http://localhost:3000 in your browser.


2. UI structure & how to use the furigana reader

The main layout is split into:

  • Left sidebar – history of previous requests
  • Right panel – either the input form or the furigana display

2.1. First visit / new input

When you first open the app (or click New in the sidebar header):

  • The right panel shows an input form:
    • A large textarea labeled (visually hidden) “Japanese paragraph”
    • A Get furigana button
  • Type or paste Japanese text (sentences, paragraphs) and click Get furigana.

Behind the scenes:

  • The app calls POST /api/furigana with your text.
  • The API uses OpenAI to generate HTML like:
    • <ruby>今日<rt>きょう</rt></ruby>は<ruby>天気<rt>てんき</rt></ruby>がいい。
  • The HTML is sanitized and stored in local history.

After a successful request:

  • A new history item appears in the sidebar.
  • The right panel switches to show the furigana display for that item.

2.2. History sidebar

The sidebar:

  • Stays fixed to the viewport height and does not scroll with the page.
  • Shows a header:
    • Title: History
    • A New button to start a new input session.
  • Below the header:
    • A scrollable list of history entries (stored in localStorage).

History entry behavior:

  • Each entry shows:
    • Either a custom name (if you set one), or
    • A truncated version of the original input text.
  • You can:
    • Click an entry to show its furigana on the right.
    • Double‑click the text (or click the pencil icon) to rename it.
    • Use the trash icon to delete the entry.

2.3. Furigana display panel

When a history item is selected (or after submission), the right panel shows:

  • A mode selector (tabs) at the top:
    • Always show furigana – readings (rt) are always visible above kanji.
    • Hover to see furigana – readings are hidden until you hover over kanji.
  • Below the tabs:
    • The Japanese paragraph rendered using <ruby> / <rt> tags generated by OpenAI.

Implementation details:

  • HTML from the API is sanitized to keep only <ruby> and <rt> and escape everything else.
  • CSS controls the two modes:
    • Always show: rt is visible by default.
    • Hover: rt is hidden until you hover over the corresponding ruby.

At any time you can:

  • Click a history item to switch to its reading.
  • Click New in the sidebar to return to the input form and create a new history entry.

3. Future plans

The current app is an MVP: history is stored only in localStorage and there is no authentication. The implementation is intentionally migration‑friendly so we can move to a production setup later.

Based on the plan in .cursor/plans/ai_japanese_reading_assistant_d4573b6c.plan.md, future work is organized into three main areas:

3.1. Live streaming of OpenAI responses

Goal: improve perceived performance and UX by streaming furigana HTML as it is generated, instead of waiting for the full response.

Planned steps:

  • Update POST /api/furigana (or add a new route) to use streaming responses from the OpenAI Chat Completions API.
  • On the client, replace the single fetch/res.json() call with a stream reader (e.g. ReadableStream + TextDecoder), progressively building the HTML string.
  • Render a streaming furigana display:
    • Show partial <ruby><rt> output as it arrives.
    • Keep the same sanitizer (sanitize-furigana-html) in the loop so only safe <ruby>/<rt> tags are rendered.
  • Optionally show a small “Streaming…” indicator while the response is not complete.

This keeps the UI responsive for long paragraphs and gives immediate feedback while the model is still generating.

3.2. Server‑side history persistence (Upstash Redis)

Goal: move history from localStorage to a server‑side store so users can keep their reading history across devices.

Planned steps:

  • Add Upstash Redis via Vercel Marketplace.
  • Expose history via API routes:
    • GET /api/history – fetch HistoryItem[] for the current user.
    • POST /api/history (or integrate into POST /api/furigana) – append a new history item.
    • Optional DELETE /api/history – clear or delete specific history items.
  • Use a user id key (e.g. user:{userId}) in Redis; value is a JSON array of history items.
  • Swap the history client (lib/history-client.ts) to call these APIs instead of using localStorage:
    • getHistory()fetch('/api/history')
    • appendHistoryItem() → POST to /api/history
    • deleteHistoryItem() → DELETE to /api/history

The UI (useHistory hook + sidebar) does not need major changes; it keeps calling the history client.

3.3. User management (Google login)

Goal: restrict persistent history to logged‑in users, while keeping the reader usable without an account.

Planned steps:

  • Add authentication (e.g. Google OAuth via NextAuth/Auth.js or similar).
  • When logged in:
    • Show the history sidebar.
    • Use session.user.id as the userId for Redis keys.
    • Read/write history via the new history API routes.
  • When logged out:
    • Keep the input form + furigana display fully usable.
    • Hide or disable the history sidebar (e.g. show “Sign in to save history”).
    • Do not persist history server‑side.

3.4. Logout behavior & migration order

Behavior:

  • On logout, users can still:
    • Paste Japanese text.
    • Click Get furigana.
    • See the furigana display for the current input.
  • But they won’t:
    • See or save persistent history.

Migration path (high‑level):

  1. Introduce Upstash Redis + /api/history, while still using a placeholder user id (e.g. anonymous cookie) – history client switches to server APIs.
  2. Add Google login, and start using the real authenticated userId for Redis keys.
  3. Enforce “no history when logged out”:
    • Hide or empty the sidebar when unauthenticated.
    • Only use history APIs when the user is logged in.

This allows the current MVP to evolve into a production‑ready Japanese reading assistant with persistent, user‑scoped history and optional authentication, without rewriting the core UI.

About

AI Japanese reading assistant

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published