Skip to content

Commit 838866a

Browse files
committed
Add i18next internationalization
- Install i18next and react-i18next with full SSR support - Configure client-side i18n with browser language detection - Configure server-side i18n with filesystem backend - Add improved German translations (idiomatic business German) - Integrate with Vite via i18next-loader plugin - Add i18next middleware to Express server - Update server-side entry to serialize translations for hydration - Update client-side entry to hydrate with server state - Translate all Welcome page content with defaultValue pattern - Document i18n usage and architecture in CLAUDE.md English text stays inline in components as defaultValue. Other languages defined in src/locales/*.json files. Browser automatically detects user's preferred language. No language flicker due to proper SSR hydration.
1 parent deea9da commit 838866a

File tree

11 files changed

+1302
-94
lines changed

11 files changed

+1302
-94
lines changed

CLAUDE.md

Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
This is a **Carbon Design System** React starter template with server-side rendering (SSR). The project uses Vite, React 19, React Router 7, and Express to provide a full-stack SSR application scaffold. It's designed to be a starting point for teams building applications with IBM's Carbon Design System.
8+
9+
**Key requirement:** Node 24 is required for development.
10+
11+
## Common Commands
12+
13+
### Development
14+
```bash
15+
npm run dev # Start development server with hot reload (runs src/server.js)
16+
npm run preview # Preview production build locally (NODE_ENV=local)
17+
npm run preview:prod # Preview production build (NODE_ENV=production)
18+
```
19+
20+
### Building
21+
```bash
22+
npm run build # Build both client and server for production
23+
npm run build:client # Build client-side bundle only
24+
npm run build:server # Build server-side bundle only
25+
```
26+
27+
### Testing
28+
```bash
29+
npm run test # Run all tests with coverage (Vitest)
30+
npm run test:watch # Run tests in watch mode
31+
```
32+
33+
### Linting
34+
```bash
35+
npm run lint # Run all linters (ESLint, Stylelint, Prettier, CSpell)
36+
npm run lint-fix # Auto-fix ESLint, Stylelint, and Prettier issues
37+
npm run lint:es # ESLint only
38+
npm run lint:style # Stylelint only (for SCSS files)
39+
npm run lint:format # Prettier only
40+
npm run lint:spell # CSpell (spell checker) only
41+
```
42+
43+
## Architecture
44+
45+
### Server-Side Rendering (SSR)
46+
47+
The application is split into **client-side** and **server-side** code:
48+
49+
- **Entry points:**
50+
- `src/entry-client.jsx` - Client-side hydration entry point (uses `hydrateRoot` with `BrowserRouter`)
51+
- `src/entry-server.jsx` - Server-side rendering entry point (uses `renderToPipeableStream` with `StaticRouter`)
52+
- `src/server.js` - Express server that handles SSR and serves the application
53+
54+
- **Server flow:**
55+
- In development: Vite dev server with middleware mode handles HMR and SSR
56+
- In production: Express serves static assets from `dist/client` and renders HTML via `dist/server`
57+
- The server splits `index.html` at `<!--app-html-->` marker and streams React output in between
58+
59+
### Routing Architecture
60+
61+
Routes are defined in a **flat configuration** (`src/routes/config.js`) and then organized hierarchically:
62+
63+
- Each route has a `path` and optional `element` (React component)
64+
- Routes include a `carbon` property with metadata for Carbon UI components:
65+
- `label` - Display name in navigation
66+
- `inHeader` - Show in header navigation
67+
- `inSideNav` - Show in side navigation
68+
- `icon` - Carbon icon component
69+
- `href` - External links (for routes without elements)
70+
- `virtualPath` - Path for routes without actual pages (like external links)
71+
- `subMenu` - Auto-generated from child routes matching parent path pattern
72+
73+
- **Route processing:**
74+
- Flat routes are processed to create a hierarchy for Carbon header/sidenav
75+
- `routesInHeader` and `routesInSideNav` are exported for nav components
76+
- Parent-child relationships are detected via path patterns (`/parent/child`)
77+
78+
- **Status codes:**
79+
- Routes can have a `status` property (e.g., 404 for NotFound)
80+
- `src/routes/utils.js` exports `getStatusCodeForPath()` for SSR
81+
82+
### API Routes
83+
84+
API routes are registered via `src/routes/routes.js`:
85+
- Uses `getRoutes(app, handlers)` pattern for testability
86+
- Handlers can be swapped for mocks in tests
87+
- Example: `app.get('/api/message', handlers.getMessage)`
88+
- Service layer is in `src/service/`, API layer in `src/api/`
89+
90+
### Theme Management
91+
92+
The app uses a sophisticated theme system via `ThemeContext`:
93+
94+
- **Theme settings:**
95+
- `themeSetting`: 'system' | 'light' | 'dark'
96+
- `themeMenuCompliment`: boolean (inverts header/menu theme)
97+
- Persisted to localStorage via `src/utils/local-storage.js`
98+
99+
- **Theme values:**
100+
- `theme`: 'g10' (light) or 'g100' (dark) - main page theme
101+
- `themeMenu`: 'g10' or 'g100' - header/menu theme (can differ if compliment enabled)
102+
- Uses Carbon's `usePrefersDarkScheme()` for system preference detection
103+
104+
- **Implementation:**
105+
- `ThemeProvider` wraps the app in `entry-client.jsx` (client-only, not server)
106+
- `ThemeLayout` wraps routes and applies `GlobalTheme` and `Theme` components
107+
- Updates `document.documentElement` with `cs--theme` attribute
108+
109+
### CSS/SCSS
110+
111+
- **Carbon Design System** styles are imported via SCSS
112+
- Styles are loaded from `index.html` (not React components) to avoid FOUC and layout shifts
113+
- Uses `sass-embedded` for SCSS compilation
114+
- Stylelint enforces:
115+
- Carbon tokens/mixins/functions usage (`stylelint-plugin-carbon-tokens`)
116+
- Logical properties (e.g., `block-size` instead of `height`)
117+
- Accessibility rules (`@double-great/stylelint-a11y`)
118+
119+
### Testing
120+
121+
Testing uses **Vitest**, **React Testing Library**, and **MSW** (Mock Service Worker):
122+
123+
- **Setup:** `src/test/setup.js` configures mocks for JSDOM (matchMedia, ResizeObserver, etc.)
124+
- **MSW integration:**
125+
- `src/test/server.js` wraps MSW's setupServer with networking utils
126+
- `src/test/networking.js` tracks outgoing requests for debugging
127+
- `src/test/router.js` provides router for registering mock handlers
128+
- API routes are registered using the same `getRoutes()` function as production
129+
- **Test files:** Place tests in `src/__tests__/*.test.jsx` or `src/__tests__/*.test.js`
130+
- **Coverage:** Vitest automatically generates coverage reports using V8 provider
131+
132+
## Project Structure
133+
134+
```
135+
src/
136+
├── entry-client.jsx # Client hydration entry
137+
├── entry-server.jsx # SSR render entry
138+
├── server.js # Express server
139+
├── context/ # React contexts (ThemeContext)
140+
├── routes/
141+
│ ├── config.js # Route definitions with Carbon metadata
142+
│ ├── index.jsx # Router component
143+
│ ├── routes.js # API route registration
144+
│ └── utils.js # Route utilities (status codes, etc.)
145+
├── layouts/ # Layout wrappers (theme-layout, page-layout)
146+
├── pages/ # Page components
147+
├── components/ # Shared components (Nav, Footer, etc.)
148+
├── test/ # Test setup and utilities
149+
│ ├── setup.js # Vitest setup, JSDOM mocks
150+
│ ├── server.js # MSW server wrapper
151+
│ ├── networking.js # Network request tracking
152+
│ └── test-utils.jsx # Custom render utilities
153+
├── __tests__/ # Test files
154+
├── api/ # API layer (data fetching)
155+
├── service/ # Service layer (business logic)
156+
└── utils/ # Utility functions
157+
158+
dist/
159+
├── client/ # Client build output
160+
└── server/ # Server build output
161+
```
162+
163+
## Important Development Notes
164+
165+
### Adding New Routes
166+
167+
1. Add route config to `src/routes/config.js`
168+
2. Include `carbon` metadata for navigation (`label`, `inHeader`, `inSideNav`, etc.)
169+
3. The route will automatically be organized into parent/child hierarchy
170+
4. For external links, use `href` and `virtualPath` instead of `path` and `element`
171+
172+
### Stylelint Rules
173+
174+
When disabling Stylelint rules, ALWAYS provide a description:
175+
```scss
176+
/* stylelint-disable-next-line rule-name -- Reason for disabling */
177+
```
178+
Never disable rules for entire files. See `docs/Stylelint.md` for details.
179+
180+
### Server-Side Rendering Considerations
181+
182+
- `ThemeProvider` is only in `entry-client.jsx` (not server-side) because it depends on `window.localStorage`
183+
- Server uses `StaticRouter`, client uses `BrowserRouter`
184+
- Template has `<!--app-html-->` placeholder where React output is injected
185+
- Server aborts rendering after 10 seconds (`ABORT_DELAY`)
186+
187+
### Testing API Routes
188+
189+
Tests can use the same `getRoutes()` function as production by passing mock handlers:
190+
```javascript
191+
import { getRoutes } from '../routes/routes';
192+
import { getRouter } from './router';
193+
194+
const mockHandlers = { getMessage: vi.fn() };
195+
getRoutes(getRouter(mocks, networking), mockHandlers);
196+
```
197+
198+
## Internationalization (i18n)
199+
200+
This project uses **i18next** with **react-i18next** for internationalization, fully integrated with SSR.
201+
202+
### Translation Pattern
203+
204+
English text stays **inline** with components using the `defaultValue` pattern. Other languages are stored in separate JSON files:
205+
206+
```jsx
207+
import { useTranslation } from 'react-i18next';
208+
209+
const MyComponent = () => {
210+
const { t } = useTranslation();
211+
212+
return (
213+
<div>
214+
<h1>{t('myComponent.title', 'Default English Title')}</h1>
215+
<p>{t('myComponent.description', 'Default English description text.')}</p>
216+
</div>
217+
);
218+
};
219+
```
220+
221+
- **First parameter**: Translation key (e.g., `'myComponent.title'`)
222+
- **Second parameter**: English text (default value)
223+
- Only non-English translations need to be added to language files
224+
225+
### File Structure
226+
227+
```
228+
src/
229+
├── i18n.client.js # Client-side i18n config
230+
├── i18n.server.js # Server-side i18n config
231+
└── locales/
232+
├── de.json # German translations
233+
└── [lang].json # Add more languages here
234+
```
235+
236+
### Adding New Languages
237+
238+
1. Create a new JSON file in `src/locales/` (e.g., `fr.json` for French)
239+
2. Add the language to `supportedLngs` in both `i18n.client.js` and `i18n.server.js`:
240+
```js
241+
supportedLngs: ['en', 'de', 'fr'],
242+
```
243+
3. Add translations matching the keys used in components:
244+
```json
245+
{
246+
"myComponent": {
247+
"title": "Titre en français",
248+
"description": "Description en français."
249+
}
250+
}
251+
```
252+
253+
### Translation File Format
254+
255+
Organize translations by component/page using nested objects:
256+
257+
```json
258+
{
259+
"welcome": {
260+
"title": "Willkommen",
261+
"description": "Beschreibung",
262+
"features": {
263+
"title": "Funktionen",
264+
"subtitle": "Untertitel"
265+
}
266+
},
267+
"dashboard": {
268+
"title": "Dashboard"
269+
}
270+
}
271+
```
272+
273+
### Language Detection
274+
275+
The app automatically detects the user's preferred language from:
276+
1. Browser settings (`navigator.language`)
277+
2. HTML `lang` attribute
278+
3. Falls back to English if language not supported
279+
280+
### SSR Considerations
281+
282+
- Server-side config (`i18n.server.js`) uses filesystem backend to load translations
283+
- Client-side config (`i18n.client.js`) uses dynamic imports with browser language detection
284+
- Translations are serialized on server and hydrated on client to prevent language flicker
285+
- Both configs have `useSuspense: false` for SSR compatibility
286+
287+
### Packages Used
288+
289+
- `i18next` - Core i18n engine
290+
- `react-i18next` - React bindings (provides `useTranslation` hook)
291+
- `i18next-http-middleware` - Express middleware for SSR language detection
292+
- `i18next-fs-backend` - Server-side filesystem loader
293+
- `i18next-browser-languagedetector` - Client-side language detection
294+
- `i18next-resources-to-backend` - Client-side dynamic translation loading
295+
- `vite-plugin-i18next-loader` - Vite plugin for HMR support
296+
297+
## Husky Pre-commit Hooks
298+
299+
The project uses Husky with lint-staged to run linters on staged files before commit. Specifically:
300+
- ESLint runs on staged JS/TS/JSX/TSX files
301+
- Special check prevents accidental commit of `.npmrc`

0 commit comments

Comments
 (0)