|
1 | 1 | # Migrations |
2 | 2 |
|
3 | | -## [Unreleased]() |
| 3 | +## [10.0.0 (2026-04-16)](https://github.com/Frachtwerk/essencium-frontend/releases/tag/essencium-app-v10.0.0) |
4 | 4 |
|
5 | | -### Add API token management (https://github.com/Frachtwerk/essencium-frontend/pull/929) |
| 5 | +### Migrate from i18next to next-intl (https://github.com/Frachtwerk/essencium-frontend/pull/967) |
6 | 6 |
|
7 | | -#### `packages/types/src/right.ts` |
| 7 | +#### `packages/app/package.json` |
8 | 8 |
|
9 | | -- add `API_TOKEN`, `API_TOKEN_ADMIN`, and `SESSION_TOKEN_ADMIN` to the `RIGHTS` enum |
| 9 | +- remove `i18next`, `react-i18next`, `next-i18n-router`, `i18next-resources-to-backend` |
| 10 | +- add `next-intl` |
10 | 11 |
|
11 | | -#### `packages/types/src/apiToken.ts` |
| 12 | +#### `packages/app/next.config.mjs` (renamed from `next.config.js`) |
12 | 13 |
|
13 | | -- add file with `ApiTokenOutput`, `ApiTokenInput`, `ApiTokenStatus`, `FilterObjectApiToken` types and Zod schemas |
| 14 | +- convert from CJS (`module.exports`) to ESM (`export default`) |
| 15 | +- wrap config with `withNextIntl()` from `next-intl/plugin` |
| 16 | +- add `turbopack: {}` option |
14 | 17 |
|
15 | | -#### `packages/app/src/api/apiToken.ts` |
| 18 | +#### `packages/app/src/i18n/routing.ts` |
16 | 19 |
|
17 | | -- add file with API hooks: `useGetApiTokens`, `useGetApiToken`, `useCreateApiToken`, `useDeleteApiToken`, `useRevokeApiToken`, `useGetAllApiTokens`, `useGetAllApiTokensGrouped`, `useGetTokenExpirationInfo` |
| 20 | +- add file: defines locale routing via `defineRouting()` from `next-intl/routing` |
| 21 | +- exports `routing` (locales, defaultLocale) — used throughout the app |
18 | 22 |
|
19 | | -#### `packages/app/src/api/me.ts` |
| 23 | +#### `packages/app/src/i18n/request.ts` |
20 | 24 |
|
21 | | -- rewrite `useGetMe` hook with explicit `useQuery` implementation to gate on auth token availability |
| 25 | +- add file: server-side `getRequestConfig()` from `next-intl/server` |
| 26 | +- loads static JSON translation files and deep-merges backend translation overrides |
| 27 | +- replaces the old `initTranslations()` function from `src/config/i18n.ts` |
22 | 28 |
|
23 | | -#### `packages/app/src/utils/dayjs.ts` |
| 29 | +#### `packages/app/src/i18n/navigation.ts` |
24 | 30 |
|
25 | | -- add file with dayjs locale configuration (de/en) |
| 31 | +- add file: locale-aware navigation exports via `createNavigation(routing)` |
| 32 | +- provides `Link`, `redirect`, `usePathname`, `useRouter`, `getPathname` |
| 33 | +- use these instead of `next/navigation` for locale-aware routing |
26 | 34 |
|
27 | | -#### `packages/app/src/app/[locale]/providers.tsx` |
| 35 | +#### `packages/app/src/middleware.ts` |
28 | 36 |
|
29 | | -- wrap app with Mantine `DatesProvider` for date picker locale support |
30 | | -- accept `locale` prop from layout |
| 37 | +- add file: `createMiddleware(routing)` from `next-intl/middleware` |
| 38 | +- replaces `src/proxy.ts` (which used `next-i18n-router`) |
| 39 | + |
| 40 | +#### `packages/app/src/proxy.ts` |
| 41 | + |
| 42 | +- remove file: replaced by `src/middleware.ts` |
| 43 | + |
| 44 | +#### `packages/app/src/config/i18n.ts` |
| 45 | + |
| 46 | +- `initTranslations()` removed — replaced by next-intl's `getRequestConfig` |
| 47 | +- file now only re-exports `routing as i18nConfig` for backwards compatibility |
| 48 | + |
| 49 | +#### `packages/app/src/components/provider/translationProvider.tsx` |
| 50 | + |
| 51 | +- `TranslationProvider` component gutted and deprecated |
| 52 | +- root layout now wraps children directly with `NextIntlClientProvider` from `next-intl` |
| 53 | + |
| 54 | +#### `packages/app/src/hooks/useAddTranslation.ts` |
| 55 | + |
| 56 | +- hook gutted and deprecated |
| 57 | +- backend translation overrides are now loaded server-side in `src/i18n/request.ts` |
| 58 | + |
| 59 | +#### `packages/app/src/hooks/index.ts` |
| 60 | + |
| 61 | +- remove export of `useAddTranslation` |
31 | 62 |
|
32 | 63 | #### `packages/app/src/app/[locale]/layout.tsx` |
33 | 64 |
|
| 65 | +- replace `TranslationProvider` with `NextIntlClientProvider` from `next-intl` |
| 66 | +- replace `initTranslations(locale)` with `getMessages()` from `next-intl/server` |
| 67 | +- `generateStaticParams` uses `routing.locales` instead of `i18nConfig.locales` |
34 | 68 | - pass `locale` prop to `Providers` |
35 | 69 |
|
36 | | -#### `packages/app/src/globals.css` |
| 70 | +#### `packages/app/src/app/[locale]/providers.tsx` |
| 71 | + |
| 72 | +- add `DatesProvider` from `@mantine/dates` wrapping children for date picker locale support |
| 73 | +- accept `locale` prop from layout |
| 74 | +- add side-effect `import '@/utils/dayjs'` for locale registration |
| 75 | + |
| 76 | +#### `packages/app/src/app/[locale]/(public)/layout.tsx` |
| 77 | + |
| 78 | +- migrate from i18next to next-intl patterns |
| 79 | + |
| 80 | +#### `packages/app/src/components/layouts/AuthLayout.tsx` |
| 81 | + |
| 82 | +- replace `useTranslation()` with `useTranslations()` from `next-intl` |
| 83 | +- replace `i18n.language` with `useLocale()` from `next-intl` |
| 84 | +- remove `useAddTranslations(i18n)` usage |
| 85 | +- remove `i18n.changeLanguage()` call (locale handled via URL) |
| 86 | + |
| 87 | +#### `packages/app/src/components/RouteProtector.tsx` |
| 88 | + |
| 89 | +- replace `useTranslation` with `useTranslations` from `next-intl` |
| 90 | +- replace `i18nConfig` with `routing` from `@/i18n/routing` |
| 91 | + |
| 92 | +#### `packages/app/src/app/[locale]/(main)/admin/translations/TranslationsView.tsx` |
| 93 | + |
| 94 | +- replace `getI18n()` / `i18n.language` with `useLocale()` from `next-intl` |
| 95 | +- accept `staticMessages` prop (preloaded by page.tsx via dynamic `import()`) |
| 96 | +- replace `i18n.addResourceBundle()` with `staticMessages[locale]` lookup |
| 97 | +- replace `window.location.reload()` with `router.refresh()` |
| 98 | +- use `routing.locales` instead of `i18nConfig.locales` |
| 99 | +- fix component name typo: `TranlsationView` → `TranslationsView` |
| 100 | + |
| 101 | +#### `packages/app/src/app/[locale]/(main)/admin/translations/page.tsx` |
| 102 | + |
| 103 | +- load static translation messages for all locales via dynamic `import()` |
| 104 | +- pass as `staticMessages` prop to `TranslationsView` |
| 105 | + |
| 106 | +#### `packages/app/src/utils/withBaseStylingShowNotification.ts` |
| 107 | + |
| 108 | +- **BREAKING for callers:** no longer internally calls `getI18n()` |
| 109 | +- now accepts `t` as an optional `TranslateFunction` parameter |
| 110 | +- all callers must pass the `t` function from `useTranslations()` explicitly |
| 111 | + |
| 112 | +#### All view components |
37 | 113 |
|
38 | | -- add `@mantine/dates/styles.layer.css` import |
| 114 | +- replace `import { useTranslation } from 'react-i18next'` with `import { useTranslations } from 'next-intl'` |
| 115 | +- replace `const { t } = useTranslation()` with `const t = useTranslations()` |
| 116 | + |
| 117 | +Changed files: |
| 118 | +- `src/app/[locale]/(main)/admin/rights/RightsView.tsx` |
| 119 | +- `src/app/[locale]/(main)/admin/roles/RolesView.tsx` |
| 120 | +- `src/app/[locale]/(main)/admin/users/UsersView.tsx` |
| 121 | +- `src/app/[locale]/(main)/admin/users/[id]/UpdateUserView.tsx` |
| 122 | +- `src/app/[locale]/(main)/admin/users/add/AddUserView.tsx` |
| 123 | +- `src/app/[locale]/(main)/legal-notice/LegalNoticeView.tsx` |
| 124 | +- `src/app/[locale]/(main)/privacy-policy/PrivacyPolicyView.tsx` |
| 125 | +- `src/app/[locale]/(main)/profile/ProfileView.tsx` |
| 126 | +- `src/app/[locale]/(public)/set-password/SetPasswordView.tsx` |
| 127 | +- `src/app/[locale]/(main)/translations/_components/TranslationsChangeForm.tsx` |
| 128 | +- `src/api/contact.ts` |
| 129 | + |
| 130 | +#### All page.tsx files |
| 131 | + |
| 132 | +- simplify `generateMetadata`: replace `initTranslations(locale)` with `getTranslations()` from `next-intl/server` |
| 133 | +- remove `Props`/`params`/`ResolvingMetadata` types — page components no longer receive `params` prop |
| 134 | + |
| 135 | +Changed files: |
| 136 | +- `src/app/[locale]/(main)/admin/rights/page.tsx` |
| 137 | +- `src/app/[locale]/(main)/admin/roles/page.tsx` |
| 138 | +- `src/app/[locale]/(main)/admin/users/page.tsx` |
| 139 | +- `src/app/[locale]/(main)/admin/users/[id]/page.tsx` |
| 140 | +- `src/app/[locale]/(main)/admin/users/add/page.tsx` |
| 141 | +- `src/app/[locale]/(main)/contact/page.tsx` |
| 142 | +- `src/app/[locale]/(main)/legal-notice/page.tsx` |
| 143 | +- `src/app/[locale]/(main)/privacy-policy/page.tsx` |
| 144 | +- `src/app/[locale]/(main)/page.tsx` |
| 145 | +- `src/app/[locale]/(public)/login/page.tsx` |
| 146 | +- `src/app/[locale]/(public)/set-password/page.tsx` |
| 147 | + |
| 148 | +### Raise Node.js minimum version to 24 |
| 149 | + |
| 150 | +#### `packages/app/package.json` |
| 151 | + |
| 152 | +- change `engines.node` from `>=20` to `>=24` |
| 153 | +- update `volta.node` from `20.15.0` to `24.0.0` |
| 154 | + |
| 155 | +### Hydrate user state from JWT claims (https://github.com/Frachtwerk/essencium-frontend/pull/996) |
| 156 | + |
| 157 | +#### `packages/app/src/api/auth-atoms.ts` |
| 158 | + |
| 159 | +- add file: Jotai atoms for auth state extracted from `auth.ts` |
| 160 | +- exports `authTokenAtom`, `userAtom`, `userRightsAtom`, `isSsoAtom`, `ssoProviderAtom` |
| 161 | +- import atoms from this file instead of `auth.ts` |
| 162 | + |
| 163 | +#### `packages/app/src/api/auth.ts` |
| 164 | + |
| 165 | +- add `getAuthStateFromToken(token)`: parses JWT claims into `UserOutput` + rights array |
| 166 | +- `useCreateToken` and `useRenewToken`: hydrate `userAtom`/`userRightsAtom` immediately on success |
| 167 | +- `useLogout`: resets atoms via Jotai setters instead of `localStorage.removeItem` |
| 168 | +- auth atoms moved to `auth-atoms.ts` and re-exported |
| 169 | + |
| 170 | +#### `packages/app/src/api/api.ts` |
| 171 | + |
| 172 | +- add `getStoredAuthToken()` with safe JSON.parse (handles raw and JSON-stringified tokens) |
| 173 | +- `clearAuthState()` uses `getDefaultStore()` from `jotai/vanilla` to reset atoms |
| 174 | +- 401 interceptor: add SSR guard, fix login path check to `.includes('/login')` for locale-aware paths |
| 175 | + |
| 176 | +#### `packages/app/src/api/me.ts` |
| 177 | + |
| 178 | +- rewrite `useGetMe` from `createUseFind` factory to a direct `useQuery` hook |
| 179 | +- explicitly depends on `authTokenAtom` so query is disabled when no token is present |
| 180 | + |
| 181 | +#### `packages/app/src/utils/parseJwt.ts` |
| 182 | + |
| 183 | +- return type changed from `Record<string, number>` to `JwtPayload` (`Record<string, unknown> & { expiringIn: number }`) |
| 184 | +- add validation: returns `null` if `payload.exp` is not a number |
| 185 | + |
| 186 | +### Add API token management (https://github.com/Frachtwerk/essencium-frontend/pull/972) |
| 187 | + |
| 188 | +#### `packages/types/src/right.ts` |
| 189 | + |
| 190 | +- add `API_TOKEN`, `API_TOKEN_ADMIN`, and `SESSION_TOKEN_ADMIN` to the `RIGHTS` enum |
| 191 | + |
| 192 | +#### `packages/types/src/apiToken.ts` |
| 193 | + |
| 194 | +- add file with `ApiTokenOutput`, `ApiTokenInput`, `ApiTokenStatus`, `FilterObjectApiToken` types and Zod schemas |
| 195 | + |
| 196 | +#### `packages/app/src/api/apiToken.ts` |
| 197 | + |
| 198 | +- add file with API hooks: `useGetApiTokens`, `useGetApiToken`, `useCreateApiToken`, `useDeleteApiToken`, `useRevokeApiToken`, `useGetAllApiTokens`, `useGetAllApiTokensGrouped`, `useGetTokenExpirationInfo` |
39 | 199 |
|
40 | 200 | #### `packages/app/src/app/[locale]/(main)/profile/_components/ApiTokensSection.tsx` |
41 | 201 |
|
|
69 | 229 |
|
70 | 230 | - add translations for `navigation.apiTokens`, `profileView.apiTokens`, `apiTokensView`, and `actions.close` |
71 | 231 |
|
72 | | -#### `packages/lib/src/theme.css` |
73 | | - |
74 | | -- add directional spacing utilities (`py-*`, `px-*`, `pt-*`, `pb-*`, `pl-*`, `pr-*`, `my-*`, `mx-*`, `mt-*`, `mb-*`, `ml-*`, `mr-*`) mapped to Mantine spacing tokens |
75 | | - |
76 | 232 | ## [9.5.0 (2026-03-10)]() |
77 | 233 |
|
78 | 234 | ### Enable React Compiler (https://github.com/Frachtwerk/essencium-frontend/pull/908) |
|
0 commit comments