lynkow 3.6.0 → 3.6.2
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 +370 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,18 +65,63 @@ interface ClientConfig {
|
|
|
65
65
|
}
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
### Next.js App Router
|
|
68
|
+
### Next.js App Router Caching
|
|
69
|
+
|
|
70
|
+
> **Important**: By default, Next.js App Router caches all `fetch()` requests **indefinitely**. This means content updates in Lynkow won't appear until you redeploy or clear the cache.
|
|
71
|
+
|
|
72
|
+
You must configure `fetchOptions` to control caching behavior:
|
|
73
|
+
|
|
74
|
+
#### Option 1: No Cache (Always Fresh)
|
|
75
|
+
|
|
76
|
+
Best for frequently updated content or during development:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const lynkow = createClient({
|
|
80
|
+
siteId: process.env.LYNKOW_SITE_ID!,
|
|
81
|
+
fetchOptions: {
|
|
82
|
+
cache: 'no-store', // Disable Next.js fetch cache entirely
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Option 2: Time-based Revalidation (ISR)
|
|
88
|
+
|
|
89
|
+
Best for production with occasional updates:
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
const lynkow = createClient({
|
|
93
|
+
siteId: process.env.LYNKOW_SITE_ID!,
|
|
94
|
+
fetchOptions: {
|
|
95
|
+
next: { revalidate: 60 }, // Revalidate every 60 seconds
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### Option 3: On-Demand Revalidation
|
|
101
|
+
|
|
102
|
+
For precise cache control, use Next.js [on-demand revalidation](https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#on-demand-revalidation):
|
|
69
103
|
|
|
70
104
|
```typescript
|
|
105
|
+
// lib/lynkow.ts
|
|
71
106
|
const lynkow = createClient({
|
|
72
107
|
siteId: process.env.LYNKOW_SITE_ID!,
|
|
73
|
-
locale: 'fr',
|
|
74
108
|
fetchOptions: {
|
|
75
|
-
next: {
|
|
109
|
+
next: { tags: ['lynkow'] }, // Tag all requests
|
|
76
110
|
},
|
|
77
111
|
})
|
|
112
|
+
|
|
113
|
+
// app/api/revalidate/route.ts
|
|
114
|
+
import { revalidateTag } from 'next/cache'
|
|
115
|
+
|
|
116
|
+
export async function POST(request: Request) {
|
|
117
|
+
// Verify webhook secret here
|
|
118
|
+
revalidateTag('lynkow')
|
|
119
|
+
return Response.json({ revalidated: true })
|
|
120
|
+
}
|
|
78
121
|
```
|
|
79
122
|
|
|
123
|
+
Then configure a webhook in Lynkow to call your revalidate endpoint when content changes.
|
|
124
|
+
|
|
80
125
|
## Browser vs Server
|
|
81
126
|
|
|
82
127
|
The SDK is isomorphic and works on both browser and server environments. Some features are browser-only:
|
|
@@ -522,13 +567,328 @@ console.log(lynkow.locale) // 'fr'
|
|
|
522
567
|
// Get available locales
|
|
523
568
|
console.log(lynkow.availableLocales) // ['fr', 'en', 'es']
|
|
524
569
|
|
|
525
|
-
// Change locale
|
|
570
|
+
// Change locale (browser-only)
|
|
526
571
|
lynkow.setLocale('en')
|
|
527
572
|
|
|
528
573
|
// Locale detection (browser-only)
|
|
529
574
|
// Priority: localStorage > URL path > HTML lang > default
|
|
530
575
|
```
|
|
531
576
|
|
|
577
|
+
## Next.js App Router Integration
|
|
578
|
+
|
|
579
|
+
The SDK works seamlessly with Next.js App Router, but there are important patterns to follow for Server Components and internationalization.
|
|
580
|
+
|
|
581
|
+
### Basic Setup
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
// lib/lynkow.ts
|
|
585
|
+
import { createClient } from 'lynkow'
|
|
586
|
+
|
|
587
|
+
export const lynkow = createClient({
|
|
588
|
+
siteId: process.env.NEXT_PUBLIC_LYNKOW_SITE_ID!,
|
|
589
|
+
})
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Server Components
|
|
593
|
+
|
|
594
|
+
In Server Components, you can fetch data directly:
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
// app/page.tsx
|
|
598
|
+
import { lynkow } from '@/lib/lynkow'
|
|
599
|
+
|
|
600
|
+
export default async function HomePage() {
|
|
601
|
+
const page = await lynkow.pages.getByPath('/')
|
|
602
|
+
|
|
603
|
+
return (
|
|
604
|
+
<main>
|
|
605
|
+
<h1>{page.data.title}</h1>
|
|
606
|
+
</main>
|
|
607
|
+
)
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Internationalization (i18n)
|
|
612
|
+
|
|
613
|
+
The SDK supports multi-language content via the `locale` option. There are two main approaches:
|
|
614
|
+
|
|
615
|
+
#### Approach 1: Cookie-based Locale (Recommended)
|
|
616
|
+
|
|
617
|
+
This approach uses a cookie to store the user's language preference. The locale is managed entirely via cookie, not URL.
|
|
618
|
+
|
|
619
|
+
**1. Read the cookie in Server Components:**
|
|
620
|
+
|
|
621
|
+
```typescript
|
|
622
|
+
// app/page.tsx
|
|
623
|
+
import { cookies } from 'next/headers'
|
|
624
|
+
import { lynkow } from '@/lib/lynkow'
|
|
625
|
+
|
|
626
|
+
export default async function HomePage() {
|
|
627
|
+
// Read locale from cookie
|
|
628
|
+
const cookieStore = await cookies()
|
|
629
|
+
const localeCookie = cookieStore.get('_lkw_lang')
|
|
630
|
+
|
|
631
|
+
// Get site config for default locale and i18n settings
|
|
632
|
+
const siteConfig = await lynkow.site.getConfig()
|
|
633
|
+
const locale = localeCookie?.value || siteConfig.defaultLocale || 'fr'
|
|
634
|
+
|
|
635
|
+
// Fetch content with the correct locale
|
|
636
|
+
const page = await lynkow.pages.getByPath('/', { locale })
|
|
637
|
+
|
|
638
|
+
return (
|
|
639
|
+
<main>
|
|
640
|
+
<h1>{page.data.title}</h1>
|
|
641
|
+
<LanguageSwitcher
|
|
642
|
+
i18n={siteConfig.i18n}
|
|
643
|
+
currentLocale={locale}
|
|
644
|
+
/>
|
|
645
|
+
</main>
|
|
646
|
+
)
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
**2. Create a Language Switcher component:**
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
// components/LanguageSwitcher.tsx
|
|
654
|
+
'use client'
|
|
655
|
+
|
|
656
|
+
import Cookies from 'js-cookie'
|
|
657
|
+
import type { I18nConfig } from 'lynkow'
|
|
658
|
+
|
|
659
|
+
interface LanguageSwitcherProps {
|
|
660
|
+
i18n: I18nConfig
|
|
661
|
+
currentLocale: string
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export function LanguageSwitcher({ i18n, currentLocale }: LanguageSwitcherProps) {
|
|
665
|
+
// Don't render if only one locale
|
|
666
|
+
if (!i18n.switcher || i18n.locales.length <= 1) {
|
|
667
|
+
return null
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const handleLocaleChange = (localeCode: string) => {
|
|
671
|
+
if (localeCode === currentLocale) return
|
|
672
|
+
|
|
673
|
+
// Set cookie with locale preference
|
|
674
|
+
Cookies.set(i18n.detection.cookieName, localeCode, {
|
|
675
|
+
expires: i18n.detection.cookieMaxAge,
|
|
676
|
+
sameSite: 'lax',
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
// Reload page to fetch content in new locale
|
|
680
|
+
window.location.reload()
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const currentLocaleInfo = i18n.locales.find(l => l.code === currentLocale)
|
|
684
|
+
|
|
685
|
+
return (
|
|
686
|
+
<select
|
|
687
|
+
value={currentLocale}
|
|
688
|
+
onChange={(e) => handleLocaleChange(e.target.value)}
|
|
689
|
+
>
|
|
690
|
+
{i18n.locales.map((locale) => (
|
|
691
|
+
<option key={locale.code} value={locale.code}>
|
|
692
|
+
{locale.flag} {locale.name}
|
|
693
|
+
</option>
|
|
694
|
+
))}
|
|
695
|
+
</select>
|
|
696
|
+
)
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**3. Install js-cookie for client-side cookie management:**
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
npm install js-cookie
|
|
704
|
+
npm install -D @types/js-cookie
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
#### Approach 2: URL-based Locale (with Next.js i18n)
|
|
708
|
+
|
|
709
|
+
For URL-based routing like `/en/about`, `/fr/about`, use Next.js built-in i18n:
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
// next.config.js
|
|
713
|
+
module.exports = {
|
|
714
|
+
i18n: {
|
|
715
|
+
locales: ['fr', 'en', 'es'],
|
|
716
|
+
defaultLocale: 'fr',
|
|
717
|
+
},
|
|
718
|
+
}
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
Then access locale from params:
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
// app/[locale]/page.tsx
|
|
725
|
+
import { lynkow } from '@/lib/lynkow'
|
|
726
|
+
|
|
727
|
+
interface Props {
|
|
728
|
+
params: { locale: string }
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
export default async function HomePage({ params }: Props) {
|
|
732
|
+
const page = await lynkow.pages.getByPath('/', { locale: params.locale })
|
|
733
|
+
|
|
734
|
+
return <h1>{page.data.title}</h1>
|
|
735
|
+
}
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
### Site Configuration & i18n Data
|
|
739
|
+
|
|
740
|
+
The `site.getConfig()` method returns all i18n information needed for language switching:
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
const config = await lynkow.site.getConfig()
|
|
744
|
+
|
|
745
|
+
// Flat fields for simple usage
|
|
746
|
+
console.log(config.defaultLocale) // 'fr'
|
|
747
|
+
console.log(config.enabledLocales) // ['fr', 'en']
|
|
748
|
+
|
|
749
|
+
// Rich i18n config for building UI
|
|
750
|
+
console.log(config.i18n)
|
|
751
|
+
// {
|
|
752
|
+
// enabled: true,
|
|
753
|
+
// default: 'fr',
|
|
754
|
+
// locales: [
|
|
755
|
+
// { code: 'fr', name: 'Français', flag: '🇫🇷' },
|
|
756
|
+
// { code: 'en', name: 'English', flag: '🇬🇧' }
|
|
757
|
+
// ],
|
|
758
|
+
// switcher: true,
|
|
759
|
+
// detection: {
|
|
760
|
+
// enabled: true,
|
|
761
|
+
// order: ['cookie', 'browser'],
|
|
762
|
+
// cookieName: '_lkw_lang',
|
|
763
|
+
// cookieMaxAge: 365
|
|
764
|
+
// }
|
|
765
|
+
// }
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
### Complete Example: Internationalized Page
|
|
769
|
+
|
|
770
|
+
Here's a complete example of a multi-language page with language switcher:
|
|
771
|
+
|
|
772
|
+
```typescript
|
|
773
|
+
// app/page.tsx
|
|
774
|
+
import { cookies } from 'next/headers'
|
|
775
|
+
import { lynkow } from '@/lib/lynkow'
|
|
776
|
+
import { LanguageSwitcher } from '@/components/LanguageSwitcher'
|
|
777
|
+
import { HomeContent } from './HomeContent'
|
|
778
|
+
|
|
779
|
+
// Default content as fallback
|
|
780
|
+
const defaultContent = {
|
|
781
|
+
title: 'Welcome',
|
|
782
|
+
description: 'Default description',
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const defaultI18n = {
|
|
786
|
+
enabled: false,
|
|
787
|
+
default: 'en',
|
|
788
|
+
locales: [{ code: 'en', name: 'English', flag: '🇬🇧' }],
|
|
789
|
+
switcher: false,
|
|
790
|
+
detection: {
|
|
791
|
+
enabled: false,
|
|
792
|
+
order: [] as string[],
|
|
793
|
+
cookieName: '_lkw_lang',
|
|
794
|
+
cookieMaxAge: 365
|
|
795
|
+
},
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export default async function HomePage() {
|
|
799
|
+
let content = defaultContent
|
|
800
|
+
let i18n = defaultI18n
|
|
801
|
+
let currentLocale = 'en'
|
|
802
|
+
|
|
803
|
+
// Read locale preference from cookie
|
|
804
|
+
const cookieStore = await cookies()
|
|
805
|
+
const localeCookie = cookieStore.get('_lkw_lang')
|
|
806
|
+
|
|
807
|
+
try {
|
|
808
|
+
// 1. Get site config (includes i18n settings)
|
|
809
|
+
const siteConfig = await lynkow.site.getConfig()
|
|
810
|
+
if (siteConfig?.i18n) {
|
|
811
|
+
i18n = siteConfig.i18n
|
|
812
|
+
currentLocale = localeCookie?.value || siteConfig.defaultLocale || 'en'
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// 2. Fetch page content with locale
|
|
816
|
+
const page = await lynkow.pages.getByPath('/', { locale: currentLocale })
|
|
817
|
+
if (page?.data) {
|
|
818
|
+
content = {
|
|
819
|
+
title: page.data.title as string || defaultContent.title,
|
|
820
|
+
description: page.data.description as string || defaultContent.description,
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// Update locale from actual page response
|
|
825
|
+
if (page?.locale) {
|
|
826
|
+
currentLocale = page.locale
|
|
827
|
+
}
|
|
828
|
+
} catch (error) {
|
|
829
|
+
console.error('Failed to fetch content:', error)
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return (
|
|
833
|
+
<div>
|
|
834
|
+
<header>
|
|
835
|
+
<LanguageSwitcher i18n={i18n} currentLocale={currentLocale} />
|
|
836
|
+
</header>
|
|
837
|
+
<main>
|
|
838
|
+
<HomeContent content={content} />
|
|
839
|
+
</main>
|
|
840
|
+
</div>
|
|
841
|
+
)
|
|
842
|
+
}
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
### Important Notes
|
|
846
|
+
|
|
847
|
+
1. **Server Components are stateless**: The SDK client doesn't maintain state between requests. Always pass `{ locale }` explicitly to API calls.
|
|
848
|
+
|
|
849
|
+
2. **Cookie reading requires `cookies()`**: Use `next/headers` to read cookies in Server Components. This automatically makes the route dynamic.
|
|
850
|
+
|
|
851
|
+
3. **Client-side locale changes**: When the user changes language, set the cookie and reload the page. The server will read the new cookie value.
|
|
852
|
+
|
|
853
|
+
4. **No automatic locale detection on server**: The SDK's `setLocale()` and locale detection only work in the browser. On the server, you must read the locale from cookies/URL and pass it to API calls.
|
|
854
|
+
|
|
855
|
+
5. **Page alternates**: Each page response includes `alternates` with links to other language versions:
|
|
856
|
+
```typescript
|
|
857
|
+
const page = await lynkow.pages.getByPath('/')
|
|
858
|
+
console.log(page.alternates)
|
|
859
|
+
// [
|
|
860
|
+
// { locale: 'fr', path: '/', current: true },
|
|
861
|
+
// { locale: 'en', path: '/', current: false }
|
|
862
|
+
// ]
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
### TypeScript Types for i18n
|
|
866
|
+
|
|
867
|
+
```typescript
|
|
868
|
+
import type { I18nConfig, LocaleInfo, SiteConfig } from 'lynkow'
|
|
869
|
+
|
|
870
|
+
// LocaleInfo
|
|
871
|
+
interface LocaleInfo {
|
|
872
|
+
code: string // 'fr', 'en'
|
|
873
|
+
name: string // 'Français', 'English'
|
|
874
|
+
flag: string // '🇫🇷', '🇬🇧'
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// I18nConfig
|
|
878
|
+
interface I18nConfig {
|
|
879
|
+
enabled: boolean
|
|
880
|
+
default: string
|
|
881
|
+
locales: LocaleInfo[]
|
|
882
|
+
switcher: boolean
|
|
883
|
+
detection: {
|
|
884
|
+
enabled: boolean
|
|
885
|
+
order: string[]
|
|
886
|
+
cookieName: string
|
|
887
|
+
cookieMaxAge: number
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
```
|
|
891
|
+
|
|
532
892
|
## Cache Management
|
|
533
893
|
|
|
534
894
|
The SDK caches API responses automatically.
|
|
@@ -652,6 +1012,12 @@ import type {
|
|
|
652
1012
|
- **Content enhancements**: Code block copy buttons, text alignment CSS fixes
|
|
653
1013
|
- Automatic binding with MutationObserver for dynamic content
|
|
654
1014
|
|
|
1015
|
+
### New in v3.6
|
|
1016
|
+
|
|
1017
|
+
- **i18n types**: Added `I18nConfig` and `LocaleInfo` types for building language switchers
|
|
1018
|
+
- **Site config alignment**: API now returns both flat fields (`defaultLocale`, `enabledLocales`) and rich `i18n` object
|
|
1019
|
+
- **Next.js App Router documentation**: Comprehensive guide for Server Components and cookie-based i18n
|
|
1020
|
+
|
|
655
1021
|
### Upgrade Steps
|
|
656
1022
|
|
|
657
1023
|
```typescript
|