A production-ready React frontend template with multi-locale support (zh-Hans / en-US), dark mode, PWA, locale-prefixed routing, and legal pages.
The core/ layer is fully portable — zero business-domain coupling, copy it to any new project.
| Layer | Technology |
|---|---|
| UI | React 19 + TypeScript |
| Build | Vite 7 |
| Styling | Tailwind CSS 3 (custom ink/paper/jade palette) |
| i18n | i18next + react-i18next + static messages map |
| Routing | react-router-dom 7 (locale-prefixed URLs /en/..., /zh/...) |
| Testing | Vitest + @testing-library/react + happy-dom |
| PWA | Service worker + Web App Manifest |
src/
├── core/ # Shared infrastructure — zero business coupling
│ ├── components/
│ │ ├── layout/ # Header, Footer, RootLayout, ErrorBoundary
│ │ ├── ui/ # LoadingSpinner, PageBackground, ContentCard, Markdown
│ │ └── legal/ # LegalPage component + placeholder markdown files
│ ├── context/ # ThemeContext (dark mode)
│ ├── hooks/ # useTheme (localStorage persistence)
│ ├── i18n/ # MessageSchema, en-US and zh-Hans translations, i18next init
│ ├── lib/ # locale.ts, locale-registry.ts, date.ts
│ └── types/ # Locale, OutputLocale
├── features/ # Add your feature components here
├── pages/ # Thin route wrappers
│ ├── HomePage.tsx # Replace with your own content
│ ├── LegalPageWrapper.tsx # Connects ThemeContext to LegalPage
│ └── NotFoundPage.tsx # 404 fallback
├── router.tsx # Route definitions
├── main.tsx # SPA entry point
└── index.css # Global styles + dark-mode CSS variables
npm install
npm run dev # Dev server at http://localhost:5173 (VITE_ENV=dev)
npm test # Run unit tests
npm run build:dev # Build with development env (VITE_ENV=dev) → dist/
npm run build:prod # Build with production env (VITE_ENV=prod) → dist/
npm run preview # Preview production build locally- Add an entry to
src/core/lib/locale-registry.ts:"ja-JP": { urlPrefix: "ja", bcpPrefix: "ja", label: "日本語" }
- Create
src/core/i18n/ja-jp.tsimplementingMessageSchema - Register in
src/core/i18n/index.ts: add tomessages,resources, and ensureLOCALE_LISTpicks it up
- Create
src/features/your-feature/with your components - Add a route in
src/router.tsx - Create a thin page wrapper in
src/pages/YourFeaturePage.tsx - Feature components should accept
isDarkModeandlocaleas props (not context) for micro-frontend portability
index.html—<title>andapple-mobile-web-app-titlepublic/manifest.json—nameandshort_namepublic/logo192.png,public/logo512.png— app iconssrc/core/i18n/en-us.tsandzh-hans.ts—header.title
Edit the four markdown files in src/core/components/legal/. They are imported at build time via Vite ?raw, so no runtime fetch is needed.
Edit src/core/components/layout/Footer.tsx. The Links and Community sections are marked with TODO comments for placeholder URLs.
| Decision | Rationale |
|---|---|
core → features → pages one-way dependency |
Enforced layering; core/ can be dropped into any project without modification |
Feature components use props for isDarkMode/locale |
Portability across micro-frontend shells without host context |
Static messages map alongside i18next |
messages[locale] is zero-overhead and SSR-safe; i18next enables runtime useTranslation hooks |
| Cookie + navigator browser language detection | Cookie persists user preference; navigator provides a sensible first-visit default |
Inline <head> script reads app-theme-mode |
Runs before first paint — prevents theme flash without server involvement |
app_locale / app-theme-mode storage keys |
Generic names avoid collisions when projects share a domain |
npm test # Run all tests once
npm run test:watch # Watch mode
# Run individual test suites:
npx vitest run src/core/lib/__tests__/
npx vitest run src/core/i18n/__tests__/
npx vitest run src/core/hooks/__tests__/