Repository navigation
Product-grade UI, light/dark themes and Bahasa Melayu + English - #21
Merged
Merged
Conversation
All UI text now lives in src/i18n/messages/{en,ms}.ts. The `Messages`
type and a test both fail when a key is missing in either language, when
placeholders differ, or when the BM file uses common Indonesian words.
The first visit follows the browser language (ms -> BM). The choice is
stored in localStorage (wrapped in try/catch, in-memory fallback) and
mirrored to a cookie so the server renders the first paint in the same
language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every API call sends `language: "en" | "ms"`. Each route appends a language instruction to its system prompt and a short reminder to the user message. The BM instruction asks for standard Malaysian spelling and names Indonesian words to avoid. Unknown values fall back to English. Tests cover every route (quiz, flashcards, roadmap, chat, analyze) and the browser client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Colour tokens in globals.css for light and dark (prefers-color-scheme plus a toggle that applies before first paint); Tailwind's default palette is switched off so every colour goes through a token. A test computes WCAG AA contrast for every text/background pair in both themes. - Fixes the roadmap contrast bug: upcoming step numbers were white on light grey (1.2:1, Tailwind gray-200); they are now muted ink on the surface (7.7:1 light, 8.2:1 dark). - lucide-react icons instead of emoji, Inter + Fraunces via next/font, desktop nav plus a mobile bottom tab bar, BM | EN switch. - Shared topic form with example topics, skeleton loading states, localised error notices with retry, empty states, focus styles, skip link, focus trap in the settings dialog (now portalled to body). - Quiz: difficulty and length options, per-question feedback with the explanation, results with a review list. Flashcards: card grows with its text, end-of-deck summary. Roadmap: "Mark as done" moves the current step forward. - A test fails on hard-coded words in JSX text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
LearnPath is shown as proof work and will be presented to Malaysian users and judges. It looked like a template (emoji icons, plain cards), had a contrast bug on
/roadmap, and was English only.Before → after
ms→ BM); choice saved inlocalStorage(try/catch) and mirrored to a cookie so the server renders the first paint in the same languagelanguageand tells the model to answer in it; BM prompt asks for standard Malaysian spelling and lists Indonesian words to avoidprefers-color-scheme, toggle applied before first paint (no flash)All routes and features are kept. The Concept Map is still sample data and says so.
Contrast (computed, not eyeballed)
tests/design/contrast.test.tsparsesglobals.cssand checks every text/background token pair for WCAG AA (4.5:1 text, 3:1 UI) in both themes, and that the system-dark and toggle-dark token sets are identical. Lowest text pairs:ink-subtleonsurface-24.95:1 (light) and 5.91:1 (dark). Input borders 3.00:1 (light) / 3.85:1 (dark).Tests added
tests/i18n/messages.test.ts: every key in both languages (fails on a missing key), no empty strings, same{placeholders}, no common Indonesian words in BM, BM not copied from English, agreed terms (Kuiz, Kad imbas, Peta laluan pembelajaran, Tetapan, Kunci API).tests/i18n/locale.test.ts: browser-language default, Accept-Language parsing, persistence, blocked storage.tests/i18n/no-hardcoded-text.test.ts: fails on hard-coded words in JSX text.tests/api/routes.test.ts: for all five routes,language: "ms"puts the Malaysian Malay instruction in the system prompt,"en"/unknown → English.tests/lib/api-client.test.ts: the browser client sends the selected language.I sabotaged the key-parity and Indonesian-word tests (deleted one BM key, inserted "harus") and the hard-coded-text test; each failed as expected.
How it was verified
npm run typecheck,npm run lint,npm test(244 tests),npm run build, all green locally on Node 22.next startin server-key mode against a real OpenAI-compatible model and drove it with Playwright WebKit usingdevices['iPhone 13']and a 1440×900 desktop, in BM and EN, light and dark:/api/llm-configmocked; missing-key error opens settings with the BM reason;<html lang="ms">, persists across reload, page title follows;Screenshots are not added to the README in this PR.
Notes
Accept-Language) so the first paint is in the right language.🤖 Generated with Claude Code