Refactor localization keys to use dot notation for consistency across… (#464)
* Refactor localization keys to use dot notation for consistency across documentation and components * chore: bump version to 3.0.1 in package.json * Remove console log from SidebarLayout and update migration documentation for AlertDialog usage within Dropdowns * Update dashboard image to improve visual assets
This commit is contained in:
committed by
GitHub
parent
7ebff31475
commit
f9dfdf3ac8
@@ -54,8 +54,6 @@ async function SidebarLayout({
|
|||||||
image: picture_url,
|
image: picture_url,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
console.log(state);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TeamAccountWorkspaceContextProvider value={data}>
|
<TeamAccountWorkspaceContextProvider value={data}>
|
||||||
<SidebarProvider defaultOpen={state.open}>
|
<SidebarProvider defaultOpen={state.open}>
|
||||||
|
|||||||
@@ -103,13 +103,13 @@ import { HomeIcon, SettingsIcon } from 'lucide-react';
|
|||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
label: 'common:routes.home',
|
label: 'common.routes.home',
|
||||||
path: pathsConfig.app.personalAccount,
|
path: pathsConfig.app.personalAccount,
|
||||||
Icon: <HomeIcon className="w-4" />,
|
Icon: <HomeIcon className="w-4" />,
|
||||||
end: true,
|
end: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'common:routes.settings',
|
label: 'common.routes.settings',
|
||||||
path: pathsConfig.app.settings,
|
path: pathsConfig.app.settings,
|
||||||
Icon: <SettingsIcon className="w-4" />,
|
Icon: <SettingsIcon className="w-4" />,
|
||||||
},
|
},
|
||||||
@@ -122,18 +122,18 @@ export default [
|
|||||||
// config/team-account-navigation.config.tsx
|
// config/team-account-navigation.config.tsx
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
label: 'common:routes.dashboard',
|
label: 'common.routes.dashboard',
|
||||||
path: createPath(pathsConfig.app.teamAccount, account),
|
path: createPath(pathsConfig.app.teamAccount, account),
|
||||||
Icon: <LayoutDashboardIcon className="w-4" />,
|
Icon: <LayoutDashboardIcon className="w-4" />,
|
||||||
end: true,
|
end: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'common:routes.projects',
|
label: 'common.routes.projects',
|
||||||
path: createPath(pathsConfig.app.projects, account),
|
path: createPath(pathsConfig.app.projects, account),
|
||||||
Icon: <FolderIcon className="w-4" />,
|
Icon: <FolderIcon className="w-4" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'common:routes.members',
|
label: 'common.routes.members',
|
||||||
path: createPath(pathsConfig.app.members, account),
|
path: createPath(pathsConfig.app.members, account),
|
||||||
Icon: <UsersIcon className="w-4" />,
|
Icon: <UsersIcon className="w-4" />,
|
||||||
},
|
},
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 121 KiB |
@@ -444,17 +444,17 @@ async function validateEnterpriseFeatureAccess(context: FeatureContext) {
|
|||||||
policies: [
|
policies: [
|
||||||
createPolicy(async (ctx) =>
|
createPolicy(async (ctx) =>
|
||||||
ctx.subscription?.plan === 'enterprise' && ctx.subscription.active
|
ctx.subscription?.plan === 'enterprise' && ctx.subscription.active
|
||||||
? allow({ billing: 'enterprise-subscription' })
|
? allow({ billing. 'enterprise-subscription' })
|
||||||
: deny('Enterprise subscription required')
|
: deny('Enterprise subscription required')
|
||||||
),
|
),
|
||||||
createPolicy(async (ctx) =>
|
createPolicy(async (ctx) =>
|
||||||
ctx.trial?.type === 'enterprise' && ctx.trial.daysRemaining > 0
|
ctx.trial?.type === 'enterprise' && ctx.trial.daysRemaining > 0
|
||||||
? allow({ billing: 'enterprise-trial', daysLeft: ctx.trial.daysRemaining })
|
? allow({ billing. 'enterprise-trial', daysLeft: ctx.trial.daysRemaining })
|
||||||
: deny('Active enterprise trial required')
|
: deny('Active enterprise trial required')
|
||||||
),
|
),
|
||||||
createPolicy(async (ctx) =>
|
createPolicy(async (ctx) =>
|
||||||
ctx.adminOverride?.enabled && ctx.user.role === 'super-admin'
|
ctx.adminOverride?.enabled && ctx.user.role === 'super-admin'
|
||||||
? allow({ billing: 'admin-override' })
|
? allow({ billing. 'admin-override' })
|
||||||
: deny('Admin override not available')
|
: deny('Admin override not available')
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ order: 9
|
|||||||
description: "A guide to updating this kit from v2 to v3 using git and AI Agents"
|
description: "A guide to updating this kit from v2 to v3 using git and AI Agents"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
The source for this page is available at `docs/installation/v3-migration.mdoc`. You can reference this file to AI agents for automatic migrations.
|
||||||
|
|
||||||
v3 is a major upgrade that modernizes the entire stack:
|
v3 is a major upgrade that modernizes the entire stack:
|
||||||
|
|
||||||
- **Zod v4** — faster validation, smaller bundle, cleaner API
|
- **Zod v4** — faster validation, smaller bundle, cleaner API
|
||||||
@@ -85,6 +87,12 @@ Each step is tagged so you can merge incrementally:
|
|||||||
| 9 | `v3-step/remove-edge-csrf` | Drops CSRF middleware in favor of Server Actions |
|
| 9 | `v3-step/remove-edge-csrf` | Drops CSRF middleware in favor of Server Actions |
|
||||||
| 10 | `v3-step/final` | Centralizes dependency versions |
|
| 10 | `v3-step/final` | Centralizes dependency versions |
|
||||||
|
|
||||||
|
After merging the last tag (`v3-step/final`), merge the latest `main` to pick up any fixes and improvements released after the migration tags:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git pull upstream main
|
||||||
|
```
|
||||||
|
|
||||||
### Before starting the migration
|
### Before starting the migration
|
||||||
|
|
||||||
Please make sure your `main` branch is up to date with the branch `v2`. Also,
|
Please make sure your `main` branch is up to date with the branch `v2`. Also,
|
||||||
@@ -412,6 +420,30 @@ If you used Radix primitives directly, these sub-components were renamed:
|
|||||||
|
|
||||||
Base UI also introduces a **Positioner** wrapper for floating components (Popover, Tooltip, Select, DropdownMenu). Props like `align`, `side`, `sideOffset` move from `Content`/`Popup` to the `Positioner`.
|
Base UI also introduces a **Positioner** wrapper for floating components (Popover, Tooltip, Select, DropdownMenu). Props like `align`, `side`, `sideOffset` move from `Content`/`Popup` to the `Positioner`.
|
||||||
|
|
||||||
|
### AlertDialog inside Dropdowns
|
||||||
|
|
||||||
|
Base UI does not support nesting an `AlertDialog` trigger around a `DropdownMenuItem`. If you have an `AlertDialogTrigger` wrapping a dropdown item, remove the trigger and instead control the dialog with state, placing it outside the dropdown:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- <AlertDialog>
|
||||||
|
- <AlertDialogTrigger asChild>
|
||||||
|
- <DropdownMenuItem>Delete</DropdownMenuItem>
|
||||||
|
- </AlertDialogTrigger>
|
||||||
|
- <AlertDialogContent>...</AlertDialogContent>
|
||||||
|
- </AlertDialog>
|
||||||
|
+ const [isAlertOpen, setIsAlertOpen] = useState(false);
|
||||||
|
+
|
||||||
|
+ <DropdownMenuItem onClick={() => setIsAlertOpen(true)}>
|
||||||
|
+ Delete
|
||||||
|
+ </DropdownMenuItem>
|
||||||
|
+
|
||||||
|
+ <AlertDialog open={isAlertOpen} onOpenChange={setIsAlertOpen}>
|
||||||
|
+ <AlertDialogContent>...</AlertDialogContent>
|
||||||
|
+ </AlertDialog>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `useState` to control `open` and `onOpenChange` on the `AlertDialog`, and trigger it from the dropdown item's `onClick` handler. Note: Base UI uses `onClick` instead of Radix's `onSelect` on menu items.
|
||||||
|
|
||||||
### Sidebar Import Path Change
|
### Sidebar Import Path Change
|
||||||
|
|
||||||
The shadcn sidebar component moved:
|
The shadcn sidebar component moved:
|
||||||
@@ -457,16 +489,36 @@ The interpolation syntax changed from double to single curly braces:
|
|||||||
|
|
||||||
This applies to **every custom translation string** that uses variables.
|
This applies to **every custom translation string** that uses variables.
|
||||||
|
|
||||||
### withI18n Removal
|
### i18next API Removal
|
||||||
|
|
||||||
The `withI18n` higher-order component is removed. If you wrapped page exports
|
The entire `react-i18next` / `i18next` API surface is removed in v3. This includes:
|
||||||
with it, remove the wrapper:
|
|
||||||
|
- **`withI18n` HOC** — no longer needed. If you wrapped Server Component page exports with it, remove the wrapper:
|
||||||
|
|
||||||
```diff
|
```diff
|
||||||
- export default withI18n(MyPage);
|
- export default withI18n(MyPage);
|
||||||
+ export default MyPage;
|
+ export default MyPage;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **`useTranslation` hook** — replace with `useTranslations` from `next-intl`:
|
||||||
|
|
||||||
|
```diff
|
||||||
|
- import { useTranslation } from 'react-i18next';
|
||||||
|
- const { t } = useTranslation('namespace');
|
||||||
|
+ import { useTranslations } from 'next-intl';
|
||||||
|
+ const t = useTranslations('namespace');
|
||||||
|
```
|
||||||
|
|
||||||
|
- **`createI18nServerInstance` / `getTranslation`** — replace with `getTranslations` from `next-intl/server` (see above).
|
||||||
|
|
||||||
|
- **`Trans` component** — now imported from `@kit/ui/trans` (backed by `next-intl`), not from `react-i18next`. The API is the same but key syntax uses dots instead of colons.
|
||||||
|
|
||||||
|
- **`i18next.init` / custom i18n configuration** — replaced by the `next-intl` config in `apps/web/i18n/request.ts`. Remove any custom i18next initialization code.
|
||||||
|
|
||||||
|
- **Custom namespaces** — if you added custom translation namespaces, register them in `apps/web/i18n/request.ts` so `next-intl` can load them.
|
||||||
|
|
||||||
|
- **Custom locales** — if you added custom locales, register them in `packages/i18n/src/locales.tsx`.
|
||||||
|
|
||||||
### next.config.mjs
|
### next.config.mjs
|
||||||
|
|
||||||
Your `next.config.mjs` must be wrapped with `createNextIntlPlugin`:
|
Your `next.config.mjs` must be wrapped with `createNextIntlPlugin`:
|
||||||
@@ -484,9 +536,14 @@ Without this wrapper, `next-intl` will not work.
|
|||||||
|
|
||||||
### Messages Files
|
### Messages Files
|
||||||
|
|
||||||
**Message files** moved to `apps/web/i18n/messages/{locale}/`.
|
**Message files** moved from `apps/web/public/locales/{locale}/` to `apps/web/i18n/messages/{locale}/`.
|
||||||
|
|
||||||
Please migrate your existing messages to `apps/web/i18n/messages/{locale}/`.
|
Migrate your custom translation files to the new location:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example: move English translations
|
||||||
|
mv apps/web/public/locales/en/* apps/web/i18n/messages/en/
|
||||||
|
```
|
||||||
|
|
||||||
### Navigation Config
|
### Navigation Config
|
||||||
|
|
||||||
|
|||||||
@@ -2333,13 +2333,13 @@ Let's update the navigation menu to add a new link to the projects page. Update
|
|||||||
|
|
||||||
```tsx {% title="apps/web/config/team-account-navigation.config.tsx" %} {7-11}
|
```tsx {% title="apps/web/config/team-account-navigation.config.tsx" %} {7-11}
|
||||||
{
|
{
|
||||||
label: 'common:routes.dashboard',
|
label: 'common.routes.dashboard',
|
||||||
path: pathsConfig.app.accountHome.replace('[account]', account),
|
path: pathsConfig.app.accountHome.replace('[account]', account),
|
||||||
Icon: <LayoutDashboard className={iconClasses} />,
|
Icon: <LayoutDashboard className={iconClasses} />,
|
||||||
end: true,
|
end: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'common:routes.projects',
|
label: 'common.routes.projects',
|
||||||
path: `/home/${account}/projects`,
|
path: `/home/${account}/projects`,
|
||||||
Icon: <FolderKanban className={iconClasses} />,
|
Icon: <FolderKanban className={iconClasses} />,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -670,19 +670,19 @@ function PlanDetails({
|
|||||||
<span className={'text-sm font-medium'}>
|
<span className={'text-sm font-medium'}>
|
||||||
<b>
|
<b>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey={`billing:plans.${selectedProduct.id}.name`}
|
i18nKey={`billing.plans.${selectedProduct.id}.name`}
|
||||||
defaults={selectedProduct.name}
|
defaults={selectedProduct.name}
|
||||||
/>
|
/>
|
||||||
</b>{' '}
|
</b>{' '}
|
||||||
<If condition={isRecurring}>
|
<If condition={isRecurring}>
|
||||||
/ <Trans i18nKey={`billing:billingInterval.${selectedInterval}`} />
|
/ <Trans i18nKey={`billing.billingInterval.${selectedInterval}`} />
|
||||||
</If>
|
</If>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<span className={'text-muted-foreground text-sm'}>
|
<span className={'text-muted-foreground text-sm'}>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey={`billing:plans.${selectedProduct.id}.description`}
|
i18nKey={`billing.plans.${selectedProduct.id}.description`}
|
||||||
defaults={selectedProduct.description}
|
defaults={selectedProduct.description}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "next-supabase-saas-kit-turbo",
|
"name": "next-supabase-saas-kit-turbo",
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"author": {
|
"author": {
|
||||||
"name": "MakerKit",
|
"name": "MakerKit",
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ function PricingItem(
|
|||||||
const lineItem = props.primaryLineItem!;
|
const lineItem = props.primaryLineItem!;
|
||||||
const isCustom = props.plan.custom ?? false;
|
const isCustom = props.plan.custom ?? false;
|
||||||
|
|
||||||
const i18nKey = `billing.units.${lineItem.unit}` as never;
|
const i18nKey = lineItem?.unit ? `billing.units.${lineItem.unit}` : '';
|
||||||
|
|
||||||
const unitLabel = lineItem?.unit
|
const unitLabel = lineItem?.unit
|
||||||
? t.has(i18nKey)
|
? t.has(i18nKey)
|
||||||
|
|||||||
Reference in New Issue
Block a user