@zoyth/simple-site-framework 1.0.0
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/LICENSE +21 -0
- package/README.md +572 -0
- package/bin/create-simple-site.js +390 -0
- package/bin/simple-site.js +664 -0
- package/dist/client.js +135 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +107 -0
- package/dist/client.mjs.map +1 -0
- package/dist/components/index.d.mts +3936 -0
- package/dist/components/index.d.ts +3936 -0
- package/dist/components/index.js +38265 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +38173 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/index.d.mts +298 -0
- package/dist/config/index.d.ts +298 -0
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +1 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/index.d.mts +2184 -0
- package/dist/index.d.ts +2184 -0
- package/dist/index.js +1713 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1605 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/i18n/index.js +665 -0
- package/dist/lib/i18n/index.js.map +1 -0
- package/dist/lib/i18n/index.mjs +621 -0
- package/dist/lib/i18n/index.mjs.map +1 -0
- package/docs/DOCUMENTATION-STRUCTURE.md +1156 -0
- package/docs/EXPORTS.md +125 -0
- package/docs/PERFORMANCE.md +757 -0
- package/docs/POLICY-PAGES.md +867 -0
- package/docs/ROADMAP.md +334 -0
- package/docs/SEO.md +455 -0
- package/docs/SITEMAP.md +708 -0
- package/docs/STRUCTURED-DATA.md +671 -0
- package/docs/accessibility/common-patterns.md +529 -0
- package/docs/accessibility/keyboard-navigation.md +263 -0
- package/docs/accessibility/overview.md +122 -0
- package/docs/accessibility/screen-readers.md +311 -0
- package/docs/accessibility/wcag-compliance.md +159 -0
- package/docs/api/README.md +164 -0
- package/docs/api/components/Accessibility.md +356 -0
- package/docs/api/components/Button.md +240 -0
- package/docs/api/components/HeroSection.md +306 -0
- package/docs/architecture/decisions.md +449 -0
- package/docs/components/AnalyticsTracker.md +58 -0
- package/docs/components/AnimatedCounter.md +48 -0
- package/docs/components/AnimatedSection.md +56 -0
- package/docs/components/BlogCard.md +42 -0
- package/docs/components/Checkbox.md +56 -0
- package/docs/components/CodeBlock.md +52 -0
- package/docs/components/ComparisonTable.md +40 -0
- package/docs/components/ComponentDemo.md +38 -0
- package/docs/components/CountdownTimer.md +51 -0
- package/docs/components/ExitIntentModal.md +56 -0
- package/docs/components/FAQAccordion.md +66 -0
- package/docs/components/FeaturesGrid.md +55 -0
- package/docs/components/FileUpload.md +54 -0
- package/docs/components/I18nMetaTags.md +55 -0
- package/docs/components/Icon.md +53 -0
- package/docs/components/LazySection.md +46 -0
- package/docs/components/LiveProof.md +53 -0
- package/docs/components/LoadingSpinner.md +46 -0
- package/docs/components/MultiStepForm.md +48 -0
- package/docs/components/PolicyLayout.md +55 -0
- package/docs/components/PricingTable.md +49 -0
- package/docs/components/Radio.md +59 -0
- package/docs/components/SEOMetaTags.md +58 -0
- package/docs/components/ScriptInjector.md +50 -0
- package/docs/components/Select.md +72 -0
- package/docs/components/Skeleton.md +47 -0
- package/docs/components/StatsSection.md +48 -0
- package/docs/components/StickyBar.md +62 -0
- package/docs/components/StructuredData.md +99 -0
- package/docs/components/StyleGuide.md +46 -0
- package/docs/components/TableOfContents.md +47 -0
- package/docs/components/TestimonialCarousel.md +42 -0
- package/docs/components/Timeline.md +51 -0
- package/docs/components/Toast.md +59 -0
- package/docs/components/TrackedLink.md +62 -0
- package/docs/components/TrustBadges.md +44 -0
- package/docs/components/conversion/MobileCTA.md +363 -0
- package/docs/components/forms/ContactForm.md +75 -0
- package/docs/components/forms/FormField.md +74 -0
- package/docs/components/layout/Footer.md +601 -0
- package/docs/components/layout/Header.md +549 -0
- package/docs/components/layout/LanguageSelector.md +54 -0
- package/docs/components/layout/LanguageSwitcher.md +24 -0
- package/docs/components/overview.md +447 -0
- package/docs/components/sections/AboutSection.md +48 -0
- package/docs/components/sections/CTASection.md +596 -0
- package/docs/components/sections/CaseStudySection.md +47 -0
- package/docs/components/sections/ContactSection.md +599 -0
- package/docs/components/sections/FeatureSection.md +44 -0
- package/docs/components/sections/HeroSection.md +404 -0
- package/docs/components/sections/LogosSection.md +47 -0
- package/docs/components/sections/PersonalTaxesSection.md +23 -0
- package/docs/components/sections/RecruitingSection.md +23 -0
- package/docs/components/sections/SecurePortalSection.md +23 -0
- package/docs/components/sections/ServicePageLayout.md +52 -0
- package/docs/components/sections/ServicesSection.md +49 -0
- package/docs/components/sections/TestimonialSection.md +44 -0
- package/docs/components/sections/WhyChooseUsSection.md +54 -0
- package/docs/components/ui/Breadcrumb.md +70 -0
- package/docs/components/ui/Button.md +514 -0
- package/docs/components/ui/Card.md +501 -0
- package/docs/components/ui/Input.md +54 -0
- package/docs/components/ui/MobileLinks.md +43 -0
- package/docs/components/ui/Modal.md +60 -0
- package/docs/components/ui/Tabs.md +62 -0
- package/docs/components/ui/Textarea.md +52 -0
- package/docs/core-concepts/configuration-driven.md +552 -0
- package/docs/core-concepts/overview.md +351 -0
- package/docs/features/accessibility/README.md +73 -0
- package/docs/features/accessibility/aria-support.md +177 -0
- package/docs/features/accessibility/color-contrast.md +155 -0
- package/docs/features/accessibility/focus-management.md +187 -0
- package/docs/features/accessibility/testing.md +196 -0
- package/docs/features/analytics/README.md +51 -0
- package/docs/features/analytics/ab-testing.md +171 -0
- package/docs/features/analytics/conversion-tracking.md +207 -0
- package/docs/features/analytics/custom-events.md +219 -0
- package/docs/features/analytics/privacy.md +198 -0
- package/docs/features/analytics/setup.md +114 -0
- package/docs/features/analytics/tracking-events.md +224 -0
- package/docs/features/i18n/README.md +51 -0
- package/docs/features/i18n/best-practices.md +273 -0
- package/docs/features/i18n/configuration.md +84 -0
- package/docs/features/i18n/formatting.md +133 -0
- package/docs/features/i18n/locale-detection.md +122 -0
- package/docs/features/i18n/routing.md +99 -0
- package/docs/features/i18n/rtl-support.md +191 -0
- package/docs/features/i18n/translations.md +129 -0
- package/docs/features/internationalization.md +595 -0
- package/docs/features/performance/README.md +77 -0
- package/docs/features/performance/bundle-size.md +134 -0
- package/docs/features/performance/caching.md +131 -0
- package/docs/features/performance/code-splitting.md +121 -0
- package/docs/features/performance/image-optimization.md +110 -0
- package/docs/features/performance/lazy-loading.md +92 -0
- package/docs/features/performance/monitoring.md +148 -0
- package/docs/features/seo/README.md +51 -0
- package/docs/features/seo/best-practices.md +184 -0
- package/docs/features/seo/canonical-urls.md +182 -0
- package/docs/features/seo/meta-tags.md +126 -0
- package/docs/features/seo/open-graph.md +166 -0
- package/docs/features/seo/robots-txt.md +146 -0
- package/docs/features/seo/sitemaps.md +162 -0
- package/docs/features/seo/structured-data.md +166 -0
- package/docs/getting-started/installation.md +292 -0
- package/docs/getting-started/introduction.md +195 -0
- package/docs/getting-started/quick-start.md +460 -0
- package/docs/guides/analytics-setup.md +616 -0
- package/docs/i18n/CONFIGURATION.md +353 -0
- package/docs/i18n/EXAMPLES.md +402 -0
- package/docs/i18n/MIGRATION.md +260 -0
- package/docs/i18n/SEO.md +392 -0
- package/docs/i18n/STATIC-GENERATION-FIX.md +71 -0
- package/docs/migration/changelog.md +136 -0
- package/docs/migration/overview.md +233 -0
- package/docs/recipes/adding-animations.md +475 -0
- package/docs/recipes/forms-with-validation.md +393 -0
- package/package.json +152 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# i18n Usage Examples
|
|
2
|
+
|
|
3
|
+
Practical examples for common i18n scenarios.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Single Language Site](#single-language-site)
|
|
7
|
+
- [Bilingual Site](#bilingual-site-2-languages)
|
|
8
|
+
- [Multi-Language Site](#multi-language-site-3-languages)
|
|
9
|
+
- [RTL Language Support](#rtl-language-support)
|
|
10
|
+
- [Custom Slug Translations](#custom-slug-translations)
|
|
11
|
+
- [Language-Specific Content](#language-specific-content)
|
|
12
|
+
|
|
13
|
+
## Single Language Site
|
|
14
|
+
|
|
15
|
+
For a site with no i18n overhead:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// src/config/i18n.ts
|
|
19
|
+
export const i18nConfig = {
|
|
20
|
+
locales: ['en'],
|
|
21
|
+
defaultLocale: 'en',
|
|
22
|
+
localePrefix: 'never',
|
|
23
|
+
localeDetection: false,
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**URLs:** `/about`, `/contact` (no locale prefix)
|
|
28
|
+
|
|
29
|
+
**Folder structure:**
|
|
30
|
+
```
|
|
31
|
+
src/app/
|
|
32
|
+
├── layout.tsx
|
|
33
|
+
├── page.tsx
|
|
34
|
+
└── about/
|
|
35
|
+
└── page.tsx
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**No `[locale]` folder needed** - middleware handles everything.
|
|
39
|
+
|
|
40
|
+
## Bilingual Site (2 Languages)
|
|
41
|
+
|
|
42
|
+
Classic bilingual setup with text toggle:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// src/config/i18n.ts
|
|
46
|
+
export const i18nConfig = {
|
|
47
|
+
locales: ['en', 'fr'],
|
|
48
|
+
defaultLocale: 'en',
|
|
49
|
+
localePrefix: 'as-needed',
|
|
50
|
+
localeDetection: true,
|
|
51
|
+
localeNames: {
|
|
52
|
+
en: 'English',
|
|
53
|
+
fr: 'Français',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**URLs:**
|
|
59
|
+
- English: `/about` (default, no prefix)
|
|
60
|
+
- French: `/fr/about` (with prefix)
|
|
61
|
+
|
|
62
|
+
**Layout with language selector:**
|
|
63
|
+
```tsx
|
|
64
|
+
// src/app/[locale]/layout.tsx
|
|
65
|
+
import { setI18nConfig, getTextDirection } from 'simple-site-framework/lib/i18n';
|
|
66
|
+
import { LanguageSelector, I18nMetaTags } from 'simple-site-framework';
|
|
67
|
+
import { i18nConfig } from '../../config/i18n';
|
|
68
|
+
|
|
69
|
+
setI18nConfig(i18nConfig);
|
|
70
|
+
|
|
71
|
+
export default async function Layout({ children, params }) {
|
|
72
|
+
const { locale } = await params;
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<html lang={locale} dir={getTextDirection(locale)}>
|
|
76
|
+
<head>
|
|
77
|
+
<I18nMetaTags
|
|
78
|
+
currentLocale={locale}
|
|
79
|
+
pathname="/about"
|
|
80
|
+
baseUrl="https://example.com"
|
|
81
|
+
/>
|
|
82
|
+
</head>
|
|
83
|
+
<body>
|
|
84
|
+
<header>
|
|
85
|
+
<nav>
|
|
86
|
+
{/* Auto-detects text toggle for 2 languages */}
|
|
87
|
+
<LanguageSelector currentLocale={locale} />
|
|
88
|
+
</nav>
|
|
89
|
+
</header>
|
|
90
|
+
<main>{children}</main>
|
|
91
|
+
</body>
|
|
92
|
+
</html>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Multi-Language Site (3+ Languages)
|
|
98
|
+
|
|
99
|
+
Site with multiple languages and dropdown selector:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// src/config/i18n.ts
|
|
103
|
+
export const i18nConfig = {
|
|
104
|
+
locales: ['en', 'fr', 'es', 'de', 'ja'],
|
|
105
|
+
defaultLocale: 'en',
|
|
106
|
+
localePrefix: 'always',
|
|
107
|
+
localeDetection: true,
|
|
108
|
+
localeNames: {
|
|
109
|
+
en: 'English',
|
|
110
|
+
fr: 'Français',
|
|
111
|
+
es: 'Español',
|
|
112
|
+
de: 'Deutsch',
|
|
113
|
+
ja: '日本語',
|
|
114
|
+
},
|
|
115
|
+
localeLabels: {
|
|
116
|
+
en: 'EN',
|
|
117
|
+
fr: 'FR',
|
|
118
|
+
es: 'ES',
|
|
119
|
+
de: 'DE',
|
|
120
|
+
ja: 'JA',
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**URLs:** All have prefix
|
|
126
|
+
- `/en/about`
|
|
127
|
+
- `/fr/about`
|
|
128
|
+
- `/es/about`
|
|
129
|
+
- `/de/about`
|
|
130
|
+
- `/ja/about`
|
|
131
|
+
|
|
132
|
+
**Layout with dropdown:**
|
|
133
|
+
```tsx
|
|
134
|
+
// LanguageSelector auto-switches to dropdown with 3+ languages
|
|
135
|
+
<LanguageSelector currentLocale={locale} className="ml-4" />
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## RTL Language Support
|
|
139
|
+
|
|
140
|
+
Adding Arabic and Hebrew:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// src/config/i18n.ts
|
|
144
|
+
export const i18nConfig = {
|
|
145
|
+
locales: ['en', 'ar', 'he'],
|
|
146
|
+
defaultLocale: 'en',
|
|
147
|
+
rtlLocales: ['ar', 'he'],
|
|
148
|
+
localeNames: {
|
|
149
|
+
en: 'English',
|
|
150
|
+
ar: 'العربية',
|
|
151
|
+
he: 'עברית',
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Layout with RTL support:**
|
|
157
|
+
```tsx
|
|
158
|
+
import { getTextDirection } from 'simple-site-framework/lib/i18n';
|
|
159
|
+
|
|
160
|
+
<html lang={locale} dir={getTextDirection(locale)}>
|
|
161
|
+
{/* dir="ltr" for English, dir="rtl" for Arabic/Hebrew */}
|
|
162
|
+
</html>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**RTL-aware CSS:**
|
|
166
|
+
```css
|
|
167
|
+
/* Use logical properties */
|
|
168
|
+
.container {
|
|
169
|
+
padding-inline-start: 1rem; /* Left in LTR, right in RTL */
|
|
170
|
+
margin-inline-end: 2rem; /* Right in LTR, left in RTL */
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* Or use dir selector */
|
|
174
|
+
[dir="rtl"] .icon {
|
|
175
|
+
transform: scaleX(-1); /* Flip icons in RTL */
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Custom Slug Translations
|
|
180
|
+
|
|
181
|
+
Translate URL slugs between languages:
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
// src/config/i18n.ts
|
|
185
|
+
export const i18nConfig = {
|
|
186
|
+
locales: ['en', 'fr', 'es'],
|
|
187
|
+
defaultLocale: 'en',
|
|
188
|
+
slugTranslations: {
|
|
189
|
+
en: {
|
|
190
|
+
'/products': '/produits',
|
|
191
|
+
'/services': '/servicios',
|
|
192
|
+
'/about': '/a-propos',
|
|
193
|
+
},
|
|
194
|
+
fr: {
|
|
195
|
+
'/produits': '/products',
|
|
196
|
+
'/servicios': '/services',
|
|
197
|
+
'/a-propos': '/about',
|
|
198
|
+
},
|
|
199
|
+
es: {
|
|
200
|
+
'/products': '/productos',
|
|
201
|
+
'/services': '/servicios',
|
|
202
|
+
'/about': '/acerca-de',
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**URLs:**
|
|
209
|
+
- English: `/products`
|
|
210
|
+
- French: `/fr/produits`
|
|
211
|
+
- Spanish: `/es/productos`
|
|
212
|
+
|
|
213
|
+
**Language selector handles translation automatically.**
|
|
214
|
+
|
|
215
|
+
## Language-Specific Content
|
|
216
|
+
|
|
217
|
+
Using localized strings in components:
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
// Component with localized content
|
|
221
|
+
import { getLocalizedString } from 'simple-site-framework/lib/content';
|
|
222
|
+
|
|
223
|
+
interface Props {
|
|
224
|
+
locale: string;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function PricingSection({ locale }: Props) {
|
|
228
|
+
const content = {
|
|
229
|
+
heading: {
|
|
230
|
+
en: 'Pricing Plans',
|
|
231
|
+
fr: 'Plans tarifaires',
|
|
232
|
+
es: 'Planes de precios',
|
|
233
|
+
},
|
|
234
|
+
description: {
|
|
235
|
+
en: 'Choose the plan that fits your needs',
|
|
236
|
+
fr: 'Choisissez le plan qui vous convient',
|
|
237
|
+
es: 'Elige el plan que se adapte a tus necesidades',
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<section>
|
|
243
|
+
<h2>{getLocalizedString(content.heading, locale)}</h2>
|
|
244
|
+
<p>{getLocalizedString(content.description, locale)}</p>
|
|
245
|
+
</section>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Formatters
|
|
251
|
+
|
|
252
|
+
Using locale-aware formatters:
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
import {
|
|
256
|
+
formatDate,
|
|
257
|
+
formatNumber,
|
|
258
|
+
formatCurrency,
|
|
259
|
+
formatRelativeTime,
|
|
260
|
+
} from 'simple-site-framework/lib/i18n';
|
|
261
|
+
|
|
262
|
+
export function ProductCard({ locale, product }) {
|
|
263
|
+
return (
|
|
264
|
+
<div>
|
|
265
|
+
<h3>{product.name}</h3>
|
|
266
|
+
|
|
267
|
+
{/* Format currency */}
|
|
268
|
+
<p className="price">
|
|
269
|
+
{formatCurrency(product.price, locale, 'USD')}
|
|
270
|
+
{/* en: "$29.99" | fr: "29,99 $US" */}
|
|
271
|
+
</p>
|
|
272
|
+
|
|
273
|
+
{/* Format date */}
|
|
274
|
+
<time>
|
|
275
|
+
{formatDate(product.releaseDate, locale, { dateStyle: 'long' })}
|
|
276
|
+
{/* en: "January 15, 2026" | fr: "15 janvier 2026" */}
|
|
277
|
+
</time>
|
|
278
|
+
|
|
279
|
+
{/* Format relative time */}
|
|
280
|
+
<span>
|
|
281
|
+
{formatRelativeTime(-2, 'day', locale)}
|
|
282
|
+
{/* en: "2 days ago" | fr: "il y a 2 jours" */}
|
|
283
|
+
</span>
|
|
284
|
+
|
|
285
|
+
{/* Format number */}
|
|
286
|
+
<p>
|
|
287
|
+
{formatNumber(product.views, locale)} views
|
|
288
|
+
{/* en: "1,234,567" | fr: "1 234 567" */}
|
|
289
|
+
</p>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Middleware Configuration
|
|
296
|
+
|
|
297
|
+
### Basic Middleware
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
// src/middleware.ts
|
|
301
|
+
import { createI18nMiddleware } from 'simple-site-framework/lib/i18n';
|
|
302
|
+
import { i18nConfig } from './src/config/i18n';
|
|
303
|
+
|
|
304
|
+
export default createI18nMiddleware(i18nConfig);
|
|
305
|
+
|
|
306
|
+
export const config = {
|
|
307
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
|
308
|
+
};
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Exclude Additional Routes
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
export const config = {
|
|
315
|
+
matcher: [
|
|
316
|
+
'/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)',
|
|
317
|
+
],
|
|
318
|
+
};
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Custom Middleware Logic
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { createI18nMiddleware } from 'simple-site-framework/lib/i18n';
|
|
325
|
+
import { NextRequest } from 'next/server';
|
|
326
|
+
import { i18nConfig } from './src/config/i18n';
|
|
327
|
+
|
|
328
|
+
const i18nMiddleware = createI18nMiddleware(i18nConfig);
|
|
329
|
+
|
|
330
|
+
export default function middleware(request: NextRequest) {
|
|
331
|
+
// Run i18n middleware first
|
|
332
|
+
const response = i18nMiddleware(request);
|
|
333
|
+
|
|
334
|
+
// Add custom logic
|
|
335
|
+
response.headers.set('X-Custom-Header', 'value');
|
|
336
|
+
|
|
337
|
+
return response;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export const config = {
|
|
341
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
|
342
|
+
};
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Static Generation
|
|
346
|
+
|
|
347
|
+
Generate static pages for all locales:
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
// src/app/[locale]/blog/[slug]/page.tsx
|
|
351
|
+
import { i18nConfig } from '../../../config/i18n';
|
|
352
|
+
|
|
353
|
+
export async function generateStaticParams() {
|
|
354
|
+
const posts = await getPosts();
|
|
355
|
+
|
|
356
|
+
return i18nConfig.locales.flatMap((locale) =>
|
|
357
|
+
posts.map((post) => ({
|
|
358
|
+
locale,
|
|
359
|
+
slug: post.slug,
|
|
360
|
+
}))
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Utilities
|
|
366
|
+
|
|
367
|
+
### Check Locale Support
|
|
368
|
+
|
|
369
|
+
```tsx
|
|
370
|
+
import { validateLocale } from 'simple-site-framework/lib/i18n';
|
|
371
|
+
|
|
372
|
+
if (validateLocale(userLocale)) {
|
|
373
|
+
// Use user's locale
|
|
374
|
+
} else {
|
|
375
|
+
// Fall back to default
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Get Alternate Locales
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
import { getAlternateLocales } from 'simple-site-framework/lib/i18n';
|
|
383
|
+
|
|
384
|
+
const alternates = getAlternateLocales('en');
|
|
385
|
+
// ['fr', 'es', 'de'] (all except 'en')
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Match Locale Variants
|
|
389
|
+
|
|
390
|
+
```tsx
|
|
391
|
+
import { matchLocale } from 'simple-site-framework/lib/i18n';
|
|
392
|
+
|
|
393
|
+
matchLocale('en-US'); // 'en' (if 'en' is supported)
|
|
394
|
+
matchLocale('fr-CA'); // 'fr' (if 'fr' is supported)
|
|
395
|
+
matchLocale('de-DE'); // null (if 'de' not supported)
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## See Also
|
|
399
|
+
|
|
400
|
+
- [Configuration Reference](./CONFIGURATION.md)
|
|
401
|
+
- [Migration Guide](./MIGRATION.md)
|
|
402
|
+
- [SEO Guide](./SEO.md)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# i18n Migration Guide
|
|
2
|
+
|
|
3
|
+
This guide helps you migrate from the hardcoded bilingual (fr/en) system to the new flexible i18n configuration.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
**Old System (pre-v0.2.0):**
|
|
8
|
+
- Hardcoded support for French and English only
|
|
9
|
+
- Locale routing always required `[locale]` folder
|
|
10
|
+
- No configuration needed (but inflexible)
|
|
11
|
+
|
|
12
|
+
**New System (v0.2.0+):**
|
|
13
|
+
- Support for any number of languages
|
|
14
|
+
- Flexible locale prefix modes
|
|
15
|
+
- Project-specific configuration
|
|
16
|
+
- Middleware for automatic routing
|
|
17
|
+
|
|
18
|
+
## Breaking Changes
|
|
19
|
+
|
|
20
|
+
1. **Configuration Required**: Projects must call `setI18nConfig()` before using i18n features
|
|
21
|
+
2. **No Hardcoded Locales**: Import from your config, not from framework
|
|
22
|
+
3. **LanguageSelector**: Replaces `LanguageSwitcher` (which is now deprecated)
|
|
23
|
+
|
|
24
|
+
## Migration Steps
|
|
25
|
+
|
|
26
|
+
### Step 1: Create i18n Configuration
|
|
27
|
+
|
|
28
|
+
Create `/src/config/i18n.ts` in your project:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import type { I18nConfig } from 'simple-site-framework/lib/i18n';
|
|
32
|
+
|
|
33
|
+
export const i18nConfig: I18nConfig = {
|
|
34
|
+
// Supported locales
|
|
35
|
+
locales: ['fr', 'en'],
|
|
36
|
+
|
|
37
|
+
// Default locale
|
|
38
|
+
defaultLocale: 'fr',
|
|
39
|
+
|
|
40
|
+
// Locale prefix mode (matches old behavior)
|
|
41
|
+
localePrefix: 'always',
|
|
42
|
+
|
|
43
|
+
// Enable browser language detection
|
|
44
|
+
localeDetection: true,
|
|
45
|
+
|
|
46
|
+
// Display names for language selector
|
|
47
|
+
localeNames: {
|
|
48
|
+
fr: 'Français',
|
|
49
|
+
en: 'English',
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Short labels for compact display
|
|
53
|
+
localeLabels: {
|
|
54
|
+
fr: 'FR',
|
|
55
|
+
en: 'EN',
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**To match old behavior exactly**, use `localePrefix: 'always'`.
|
|
61
|
+
|
|
62
|
+
### Step 2: Create Middleware
|
|
63
|
+
|
|
64
|
+
Create `/src/middleware.ts` in your project:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { createI18nMiddleware } from 'simple-site-framework/lib/i18n';
|
|
68
|
+
import { i18nConfig } from './src/config/i18n';
|
|
69
|
+
|
|
70
|
+
export default createI18nMiddleware(i18nConfig);
|
|
71
|
+
|
|
72
|
+
export const config = {
|
|
73
|
+
// Exclude API routes, static assets, etc.
|
|
74
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Step 3: Initialize Config in Layout
|
|
79
|
+
|
|
80
|
+
Update `/src/app/[locale]/layout.tsx`:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { setI18nConfig } from 'simple-site-framework/lib/i18n';
|
|
84
|
+
import { i18nConfig } from '../../config/i18n';
|
|
85
|
+
|
|
86
|
+
// Initialize config at the top level
|
|
87
|
+
setI18nConfig(i18nConfig);
|
|
88
|
+
|
|
89
|
+
export async function generateStaticParams() {
|
|
90
|
+
return i18nConfig.locales.map((locale) => ({ locale }));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default async function LocaleLayout({
|
|
94
|
+
children,
|
|
95
|
+
params,
|
|
96
|
+
}: {
|
|
97
|
+
children: React.ReactNode;
|
|
98
|
+
params: Promise<{ locale: string }>;
|
|
99
|
+
}) {
|
|
100
|
+
const { locale } = (await params) as { locale: string };
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<html lang={locale}>
|
|
104
|
+
<body>{children}</body>
|
|
105
|
+
</html>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Step 4: Update Language Switcher (Optional)
|
|
111
|
+
|
|
112
|
+
Replace `LanguageSwitcher` with `LanguageSelector`:
|
|
113
|
+
|
|
114
|
+
**Before:**
|
|
115
|
+
```typescript
|
|
116
|
+
import { LanguageSwitcher } from 'simple-site-framework/components/layout/LanguageSwitcher';
|
|
117
|
+
|
|
118
|
+
<LanguageSwitcher currentLocale={locale} />
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**After:**
|
|
122
|
+
```typescript
|
|
123
|
+
import { LanguageSelector } from 'simple-site-framework/components/layout/LanguageSelector';
|
|
124
|
+
|
|
125
|
+
<LanguageSelector currentLocale={locale} />
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Note:** `LanguageSwitcher` still works (it's a wrapper), but it's deprecated and will be removed in v1.0.0.
|
|
129
|
+
|
|
130
|
+
### Step 5: Add SEO Meta Tags (Optional but Recommended)
|
|
131
|
+
|
|
132
|
+
Add `I18nMetaTags` to your layout or pages:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
import { I18nMetaTags } from 'simple-site-framework';
|
|
136
|
+
|
|
137
|
+
export default function Layout({ children, params }) {
|
|
138
|
+
return (
|
|
139
|
+
<html>
|
|
140
|
+
<head>
|
|
141
|
+
<I18nMetaTags
|
|
142
|
+
currentLocale={params.locale}
|
|
143
|
+
pathname="/about"
|
|
144
|
+
baseUrl="https://yoursite.com"
|
|
145
|
+
/>
|
|
146
|
+
</head>
|
|
147
|
+
<body>{children}</body>
|
|
148
|
+
</html>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Step 6: Test Your Changes
|
|
154
|
+
|
|
155
|
+
1. **Run build**: `npm run build`
|
|
156
|
+
2. **Test language switching**: Click language selector
|
|
157
|
+
3. **Test direct URLs**: Navigate to `/fr/about`, `/en/about`
|
|
158
|
+
4. **Test browser detection**: Clear cookies, visit `/`
|
|
159
|
+
5. **Check SEO tags**: View page source for hreflang tags
|
|
160
|
+
|
|
161
|
+
## Common Issues
|
|
162
|
+
|
|
163
|
+
### Issue: "i18n configuration not initialized"
|
|
164
|
+
|
|
165
|
+
**Cause**: Forgot to call `setI18nConfig()` in layout
|
|
166
|
+
|
|
167
|
+
**Fix**: Add this at the top of your layout file:
|
|
168
|
+
```typescript
|
|
169
|
+
import { setI18nConfig } from 'simple-site-framework/lib/i18n';
|
|
170
|
+
import { i18nConfig } from '../../config/i18n';
|
|
171
|
+
|
|
172
|
+
setI18nConfig(i18nConfig);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Issue: Routes not working
|
|
176
|
+
|
|
177
|
+
**Cause**: Middleware not created or incorrect matcher
|
|
178
|
+
|
|
179
|
+
**Fix**: Ensure `/src/middleware.ts` exists and exports the middleware with correct matcher.
|
|
180
|
+
|
|
181
|
+
### Issue: Language selector not appearing
|
|
182
|
+
|
|
183
|
+
**Cause**: Component not imported or config not initialized
|
|
184
|
+
|
|
185
|
+
**Fix**: Import `LanguageSelector` and ensure config is initialized before rendering.
|
|
186
|
+
|
|
187
|
+
## Upgrading to Better Prefix Modes
|
|
188
|
+
|
|
189
|
+
Once migrated, consider switching to `localePrefix: 'as-needed'` for better UX:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
export const i18nConfig: I18nConfig = {
|
|
193
|
+
locales: ['fr', 'en'],
|
|
194
|
+
defaultLocale: 'fr',
|
|
195
|
+
localePrefix: 'as-needed', // Changed from 'always'
|
|
196
|
+
// ... rest of config
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**With 'as-needed':**
|
|
201
|
+
- French (default): `/about` (no prefix)
|
|
202
|
+
- English: `/en/about` (with prefix)
|
|
203
|
+
|
|
204
|
+
**Benefits:**
|
|
205
|
+
- Cleaner URLs for default language
|
|
206
|
+
- Better SEO (canonical URLs simpler)
|
|
207
|
+
- Matches user expectations for primary market
|
|
208
|
+
|
|
209
|
+
## Adding More Languages
|
|
210
|
+
|
|
211
|
+
To add more languages after migration:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
export const i18nConfig: I18nConfig = {
|
|
215
|
+
locales: ['fr', 'en', 'es', 'de'], // Added Spanish and German
|
|
216
|
+
defaultLocale: 'fr',
|
|
217
|
+
localePrefix: 'as-needed',
|
|
218
|
+
localeNames: {
|
|
219
|
+
fr: 'Français',
|
|
220
|
+
en: 'English',
|
|
221
|
+
es: 'Español', // Add display names
|
|
222
|
+
de: 'Deutsch',
|
|
223
|
+
},
|
|
224
|
+
localeLabels: {
|
|
225
|
+
fr: 'FR',
|
|
226
|
+
en: 'EN',
|
|
227
|
+
es: 'ES', // Add labels
|
|
228
|
+
de: 'DE',
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The `LanguageSelector` automatically switches to dropdown mode with 3+ languages.
|
|
234
|
+
|
|
235
|
+
## RTL Language Support
|
|
236
|
+
|
|
237
|
+
To add RTL languages (Arabic, Hebrew, etc.):
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
export const i18nConfig: I18nConfig = {
|
|
241
|
+
locales: ['en', 'ar', 'he'],
|
|
242
|
+
defaultLocale: 'en',
|
|
243
|
+
rtlLocales: ['ar', 'he'], // Specify RTL locales
|
|
244
|
+
// ... rest of config
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Update your layout to use text direction:
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
import { getTextDirection } from 'simple-site-framework/lib/i18n';
|
|
252
|
+
|
|
253
|
+
<html lang={locale} dir={getTextDirection(locale)}>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Need Help?
|
|
257
|
+
|
|
258
|
+
- Check [CONFIGURATION.md](./CONFIGURATION.md) for complete config reference
|
|
259
|
+
- Check [EXAMPLES.md](./EXAMPLES.md) for usage examples
|
|
260
|
+
- Open an issue on GitHub: https://github.com/zoyth/simple-site-framework/issues
|