@djangocfg/layouts 2.1.308 → 2.1.310

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -139,11 +139,16 @@ Wraps `BaseApp` and picks **admin → private → public** layout by path (`matc
139
139
 
140
140
  ```tsx
141
141
  import { useLocaleSwitcher } from '@djangocfg/nextjs/i18n/client';
142
+ import { routing } from '@djangocfg/nextjs/i18n/routing';
142
143
 
143
144
  const { locale, locales, changeLocale } = useLocaleSwitcher();
144
- <AppLayout i18n={{ locale, locales, onLocaleChange: changeLocale }}>{children}</AppLayout>
145
+ <AppLayout i18n={{ locale, locales, onLocaleChange: changeLocale, routing }}>
146
+ {children}
147
+ </AppLayout>
145
148
  ```
146
149
 
150
+ `i18n.routing` is the object returned by `defineRouting()` (`next-intl/routing`). When passed, `BaseApp` builds a locale-aware `Link` adapter from it and mounts a `LinkProvider`, so every `<Link>` rendered inside layouts (navbar, footer, drawer, …) keeps the active locale prefix on click. Drop it for default-locale-only apps — raw `next/link` works fine.
151
+
147
152
  ---
148
153
 
149
154
  ## Monitor & debug
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/layouts",
3
- "version": "2.1.308",
3
+ "version": "2.1.310",
4
4
  "description": "Simple, straightforward layout components for Next.js - import and use with props",
5
5
  "keywords": [
6
6
  "layouts",
@@ -74,19 +74,20 @@
74
74
  "check": "tsc --noEmit"
75
75
  },
76
76
  "peerDependencies": {
77
- "@djangocfg/api": "^2.1.308",
78
- "@djangocfg/centrifugo": "^2.1.308",
79
- "@djangocfg/debuger": "^2.1.308",
80
- "@djangocfg/i18n": "^2.1.308",
81
- "@djangocfg/monitor": "^2.1.308",
82
- "@djangocfg/ui-core": "^2.1.308",
83
- "@djangocfg/ui-nextjs": "^2.1.308",
84
- "@djangocfg/ui-tools": "^2.1.308",
77
+ "@djangocfg/api": "^2.1.310",
78
+ "@djangocfg/centrifugo": "^2.1.310",
79
+ "@djangocfg/debuger": "^2.1.310",
80
+ "@djangocfg/i18n": "^2.1.310",
81
+ "@djangocfg/monitor": "^2.1.310",
82
+ "@djangocfg/ui-core": "^2.1.310",
83
+ "@djangocfg/ui-nextjs": "^2.1.310",
84
+ "@djangocfg/ui-tools": "^2.1.310",
85
85
  "@hookform/resolvers": "^5.2.2",
86
86
  "consola": "^3.4.2",
87
87
  "lucide-react": "^0.545.0",
88
88
  "moment": "^2.30.1",
89
89
  "next": "^16.2.2",
90
+ "next-intl": "^4.9.1",
90
91
  "p-retry": "^7.0.0",
91
92
  "react": "^19.1.0",
92
93
  "react-dom": "^19.1.0",
@@ -110,19 +111,20 @@
110
111
  "uuid": "^11.1.0"
111
112
  },
112
113
  "devDependencies": {
113
- "@djangocfg/api": "^2.1.308",
114
- "@djangocfg/centrifugo": "^2.1.308",
115
- "@djangocfg/debuger": "^2.1.308",
116
- "@djangocfg/i18n": "^2.1.308",
117
- "@djangocfg/monitor": "^2.1.308",
118
- "@djangocfg/typescript-config": "^2.1.308",
119
- "@djangocfg/ui-core": "^2.1.308",
120
- "@djangocfg/ui-nextjs": "^2.1.308",
121
- "@djangocfg/ui-tools": "^2.1.308",
114
+ "@djangocfg/api": "^2.1.310",
115
+ "@djangocfg/centrifugo": "^2.1.310",
116
+ "@djangocfg/debuger": "^2.1.310",
117
+ "@djangocfg/i18n": "^2.1.310",
118
+ "@djangocfg/monitor": "^2.1.310",
119
+ "@djangocfg/typescript-config": "^2.1.310",
120
+ "@djangocfg/ui-core": "^2.1.310",
121
+ "@djangocfg/ui-nextjs": "^2.1.310",
122
+ "@djangocfg/ui-tools": "^2.1.310",
122
123
  "@types/node": "^24.7.2",
123
124
  "@types/react": "^19.1.0",
124
125
  "@types/react-dom": "^19.1.0",
125
126
  "eslint": "^9.37.0",
127
+ "next-intl": "^4.9.1",
126
128
  "typescript": "^5.9.3"
127
129
  },
128
130
  "publishConfig": {
@@ -49,6 +49,7 @@ import { CentrifugoProvider } from '@djangocfg/centrifugo';
49
49
  import { Toaster, TooltipProvider } from '@djangocfg/ui-core/components';
50
50
  import { DialogProvider } from '@djangocfg/ui-core/lib/dialog-service';
51
51
  import { NextRouterAdapter, NextLinkProvider } from '@djangocfg/ui-core/adapters/nextjs';
52
+ import { NextIntlLinkBridge } from './NextIntlLinkBridge';
52
53
  import { ThemeProvider } from '@djangocfg/ui-nextjs/theme';
53
54
  import { ThemeStyleBridge } from '../../theme/ThemeStyleBridge';
54
55
  import { ErrorBoundary } from '../../components/errors/ErrorBoundary';
@@ -158,7 +159,13 @@ export function BaseApp({
158
159
  onMonitorCapture={(d) => FrontendMonitor.capture(errorDetailToMonitorEvent(d))}
159
160
  >
160
161
  <MonitorProvider {...monitorConfig} />
161
- <LayoutI18nProvider value={i18n}>{children}</LayoutI18nProvider>
162
+ <LayoutI18nProvider value={i18n}>
163
+ {i18n?.routing ? (
164
+ <NextIntlLinkBridge routing={i18n.routing}>{children}</NextIntlLinkBridge>
165
+ ) : (
166
+ children
167
+ )}
168
+ </LayoutI18nProvider>
162
169
  <NextTopLoader
163
170
  color="hsl(var(--primary))"
164
171
  height={3}
@@ -0,0 +1,67 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * NextIntlLinkBridge
5
+ *
6
+ * Bridges next-intl's locale-aware `Link` (built from a `routing` object via
7
+ * `createNavigation()`) into our framework-agnostic `LinkProvider`. Mounted
8
+ * by `BaseApp` only when the consumer passes `i18n.routing`.
9
+ *
10
+ * Why this lives in @djangocfg/layouts (not @djangocfg/ui-core):
11
+ * ui-core stays free of next-intl. Layouts already depend on next + nextjs
12
+ * plumbing, so taking a peer on next-intl here is honest about scope and
13
+ * keeps consumer apps from having to wire `createNavigation` themselves.
14
+ */
15
+
16
+ import { forwardRef, useMemo, type ReactNode } from 'react';
17
+ import { createNavigation } from 'next-intl/navigation';
18
+
19
+ import {
20
+ LinkProvider,
21
+ type LinkComponent,
22
+ type LinkComponentProps,
23
+ } from '@djangocfg/ui-core/components';
24
+
25
+ import type { NextIntlRouting } from '../types/layout.types';
26
+
27
+ export interface NextIntlLinkBridgeProps {
28
+ /** The `routing` object returned by `defineRouting()` from `next-intl/routing`. */
29
+ routing: NextIntlRouting;
30
+ children: ReactNode;
31
+ }
32
+
33
+ export function NextIntlLinkBridge({ routing, children }: NextIntlLinkBridgeProps) {
34
+ const adapter = useMemo<LinkComponent>(() => {
35
+ // The `createNavigation` overload signature splits between localized and
36
+ // shared routing variants and TS can't narrow either way through our
37
+ // opaque `NextIntlRouting` alias — cast to satisfy the localized branch
38
+ // and rely on next-intl to handle both at runtime.
39
+ const { Link: IntlLink } = createNavigation(
40
+ routing as unknown as Parameters<typeof createNavigation>[0],
41
+ );
42
+ return forwardRef<HTMLAnchorElement, LinkComponentProps>(
43
+ function NextIntlLinkAdapter(
44
+ { href, replace, scroll, prefetch, children: kids, ...rest },
45
+ ref,
46
+ ) {
47
+ // next-intl's Link accepts the same props we expose. The cast is safe
48
+ // because LinkComponentProps is a structural subset.
49
+ const Link = IntlLink as unknown as LinkComponent;
50
+ return (
51
+ <Link
52
+ href={href}
53
+ replace={replace}
54
+ scroll={scroll}
55
+ prefetch={prefetch}
56
+ ref={ref}
57
+ {...rest}
58
+ >
59
+ {kids}
60
+ </Link>
61
+ );
62
+ },
63
+ );
64
+ }, [routing]);
65
+
66
+ return <LinkProvider value={adapter}>{children}</LinkProvider>;
67
+ }
@@ -29,20 +29,11 @@ All internal anchors (navbar, footer, mobile drawer) use `<Link>` from
29
29
  `@djangocfg/ui-core/components`. In Next.js apps it renders through `next/link`
30
30
  automatically (via `NextLinkProvider`, which `BaseApp` mounts for you).
31
31
 
32
- For a locale-aware Link (e.g. `next-intl`'s `createNavigation().Link`), swap
33
- the underlying component by mounting your own `LinkProvider` from
34
- `@djangocfg/ui-core/components` higher in the tree:
35
-
36
- ```tsx
37
- import { LinkProvider } from '@djangocfg/ui-core/components';
38
- import { Link as I18nLink } from '@/i18n/navigation';
39
-
40
- <LinkProvider value={I18nLink}>
41
- <PublicLayout navbar={<FloatingNavbar config={…} />} footer={…}>
42
- {children}
43
- </PublicLayout>
44
- </LinkProvider>
45
- ```
32
+ For multi-locale apps (`next-intl` with `localePrefix: 'always' | 'as-needed'`)
33
+ pass `i18n.routing` to `AppLayout` / `BaseApp`. The bridge inside `BaseApp`
34
+ calls `createNavigation(routing)` and swaps the default `next/link` adapter
35
+ for the locale-aware one — every navbar / footer / drawer link keeps the
36
+ active locale prefix on click. See `@djangocfg/layouts` README → **i18n**.
46
37
 
47
38
  ## Navbar variants
48
39
 
@@ -13,6 +13,15 @@ import type { AnalyticsConfig } from '../../snippets/Analytics/types';
13
13
  import type { PwaInstallConfig } from '@djangocfg/ui-nextjs/pwa';
14
14
  import type { ErrorBoundaryConfig, ErrorTrackingConfig } from '../../components/errors/types';
15
15
 
16
+ /**
17
+ * Opaque alias for the `routing` object returned by `defineRouting()` from
18
+ * `next-intl/routing`. Typed as `unknown` so consumer apps can pass shared
19
+ * (no `pathnames`) or localized routing without fighting next-intl's
20
+ * conditional generics. The bridge inside `BaseApp` casts it back when it
21
+ * calls `createNavigation(routing)`.
22
+ */
23
+ export type NextIntlRouting = object & { __nextIntlRouting?: never };
24
+
16
25
  // Import local provider configs
17
26
  import type { ThemeConfig, SWRConfigOptions, CentrifugoConfig } from './providers.types';
18
27
  import type { MonitorConfig } from '@djangocfg/monitor';
@@ -117,6 +126,14 @@ export interface I18nLayoutConfig {
117
126
  brand?: I18nBrandConfig;
118
127
  /** Custom UI strings for the locale-switcher dialog. */
119
128
  dialogLabels?: I18nDialogLabels;
129
+ /**
130
+ * Optional `routing` object from `next-intl`'s `defineRouting()`. When
131
+ * passed, `BaseApp` builds a locale-aware Link adapter from it and mounts
132
+ * a `LinkProvider` so every `<Link>` rendered inside layouts keeps the
133
+ * active locale prefix on click. Without it, non-default-locale users get
134
+ * sent back to the default locale on every navbar / footer click.
135
+ */
136
+ routing?: NextIntlRouting;
120
137
  }
121
138
 
122
139
  // ============================================================================