@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,595 @@
|
|
|
1
|
+
# Internationalization (i18n)
|
|
2
|
+
|
|
3
|
+
Simple Site Framework provides flexible multi-language support that works for single-language sites, bilingual sites, or sites with 20+ languages.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The i18n system includes:
|
|
8
|
+
|
|
9
|
+
- **Flexible routing** - Single language (no overhead), bilingual, or multi-language
|
|
10
|
+
- **Automatic detection** - Browser language and cookie persistence
|
|
11
|
+
- **Type-safe content** - LocalizedString type with full TypeScript support
|
|
12
|
+
- **SEO optimized** - hreflang tags, canonical URLs, language alternates
|
|
13
|
+
- **Smart UI** - Language selector adapts (toggle for 2 languages, dropdown for 3+)
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Single Language Site (No i18n)
|
|
18
|
+
|
|
19
|
+
If you don't need multiple languages, just use strings:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
<HeroSection
|
|
23
|
+
heading="Welcome to Our Site"
|
|
24
|
+
description="We help businesses grow"
|
|
25
|
+
/>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
No configuration needed. No routing overhead.
|
|
29
|
+
|
|
30
|
+
### 2. Bilingual Site
|
|
31
|
+
|
|
32
|
+
**Step 1:** Create i18n config
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// src/config/i18n.ts
|
|
36
|
+
import type { I18nConfig } from '@zoyth/simple-site-framework';
|
|
37
|
+
|
|
38
|
+
export const i18nConfig: I18nConfig = {
|
|
39
|
+
locales: ['en', 'fr'],
|
|
40
|
+
defaultLocale: 'en',
|
|
41
|
+
localePrefix: 'as-needed', // /about for EN, /fr/about for FR
|
|
42
|
+
localeDetection: true,
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Step 2:** Use LocalizedString
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
<HeroSection
|
|
50
|
+
heading={{
|
|
51
|
+
en: 'Welcome to Our Site',
|
|
52
|
+
fr: 'Bienvenue sur notre site'
|
|
53
|
+
}}
|
|
54
|
+
description={{
|
|
55
|
+
en: 'We help businesses grow',
|
|
56
|
+
fr: 'Nous aidons les entreprises à croître'
|
|
57
|
+
}}
|
|
58
|
+
locale={locale}
|
|
59
|
+
/>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Step 3:** Language selector appears automatically
|
|
63
|
+
|
|
64
|
+
The framework renders a simple toggle between EN/FR.
|
|
65
|
+
|
|
66
|
+
### 3. Multi-Language Site (3+ Languages)
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
// src/config/i18n.ts
|
|
70
|
+
export const i18nConfig: I18nConfig = {
|
|
71
|
+
locales: ['en', 'fr', 'es', 'de', 'ja'],
|
|
72
|
+
defaultLocale: 'en',
|
|
73
|
+
localePrefix: 'as-needed',
|
|
74
|
+
localeDetection: true,
|
|
75
|
+
localeNames: {
|
|
76
|
+
en: 'English',
|
|
77
|
+
fr: 'Français',
|
|
78
|
+
es: 'Español',
|
|
79
|
+
de: 'Deutsch',
|
|
80
|
+
ja: '日本語',
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The framework renders a dropdown selector with all 5 languages.
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
### I18nConfig Options
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
interface I18nConfig {
|
|
93
|
+
/** Supported locales */
|
|
94
|
+
locales: readonly string[];
|
|
95
|
+
|
|
96
|
+
/** Default/fallback locale */
|
|
97
|
+
defaultLocale: string;
|
|
98
|
+
|
|
99
|
+
/** URL prefix mode */
|
|
100
|
+
localePrefix?: 'always' | 'as-needed' | 'never';
|
|
101
|
+
|
|
102
|
+
/** Auto-detect browser language */
|
|
103
|
+
localeDetection?: boolean;
|
|
104
|
+
|
|
105
|
+
/** Full language names for UI */
|
|
106
|
+
localeNames?: Record<string, string>;
|
|
107
|
+
|
|
108
|
+
/** Short labels for compact UI */
|
|
109
|
+
localeLabels?: Record<string, string>;
|
|
110
|
+
|
|
111
|
+
/** RTL locales (Arabic, Hebrew) */
|
|
112
|
+
rtlLocales?: readonly string[];
|
|
113
|
+
|
|
114
|
+
/** Cookie configuration */
|
|
115
|
+
localeCookie?: {
|
|
116
|
+
name?: string;
|
|
117
|
+
maxAge?: number;
|
|
118
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Locale Prefix Modes
|
|
124
|
+
|
|
125
|
+
#### 'as-needed' (Recommended)
|
|
126
|
+
|
|
127
|
+
Default locale has no prefix, others do:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
/about → English (default locale)
|
|
131
|
+
/fr/about → French
|
|
132
|
+
/es/about → Spanish
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Benefits:**
|
|
136
|
+
- Clean URLs for default language
|
|
137
|
+
- SEO-friendly (default as canonical)
|
|
138
|
+
- Best user experience
|
|
139
|
+
|
|
140
|
+
#### 'always'
|
|
141
|
+
|
|
142
|
+
All locales have prefix:
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
/en/about → English
|
|
146
|
+
/fr/about → French
|
|
147
|
+
/es/about → Spanish
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Benefits:**
|
|
151
|
+
- Explicit language in every URL
|
|
152
|
+
- Equal treatment of all languages
|
|
153
|
+
- Easier to switch default locale later
|
|
154
|
+
|
|
155
|
+
#### 'never'
|
|
156
|
+
|
|
157
|
+
No prefixes, cookie/header detection only:
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
/about → Shows based on user's language preference
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Benefits:**
|
|
164
|
+
- Simplest URLs
|
|
165
|
+
- Good for markets with one dominant language
|
|
166
|
+
|
|
167
|
+
**Drawbacks:**
|
|
168
|
+
- Harder to share specific language links
|
|
169
|
+
- Requires JavaScript for language switching
|
|
170
|
+
|
|
171
|
+
## LocalizedString Type
|
|
172
|
+
|
|
173
|
+
The core i18n type:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
type LocalizedString = {
|
|
177
|
+
[locale: string]: string;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Example
|
|
181
|
+
const heading: LocalizedString = {
|
|
182
|
+
en: 'About Us',
|
|
183
|
+
fr: 'À propos de nous',
|
|
184
|
+
es: 'Sobre nosotros'
|
|
185
|
+
};
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Type-Safe LocalizedStrings
|
|
189
|
+
|
|
190
|
+
Enforce required locales:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
type RequiredLocales<T> = {
|
|
194
|
+
[K in 'en' | 'fr']: T;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const heading: RequiredLocales<string> = {
|
|
198
|
+
en: 'Welcome',
|
|
199
|
+
fr: 'Bienvenue',
|
|
200
|
+
// TypeScript error if missing either
|
|
201
|
+
};
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Using in Components
|
|
205
|
+
|
|
206
|
+
All framework components accept LocalizedString:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
<HeroSection
|
|
210
|
+
heading={{ en: 'Welcome', fr: 'Bienvenue' }}
|
|
211
|
+
description={{ en: 'Start your journey', fr: 'Commencez votre voyage' }}
|
|
212
|
+
cta={{
|
|
213
|
+
text: { en: 'Get Started', fr: 'Commencer' },
|
|
214
|
+
href: '/contact'
|
|
215
|
+
}}
|
|
216
|
+
locale={locale}
|
|
217
|
+
/>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Utility Function
|
|
221
|
+
|
|
222
|
+
Get the right language:
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { getLocalizedString } from '@zoyth/simple-site-framework';
|
|
226
|
+
|
|
227
|
+
const text = getLocalizedString(
|
|
228
|
+
{ en: 'Hello', fr: 'Bonjour' },
|
|
229
|
+
locale
|
|
230
|
+
);
|
|
231
|
+
// Returns 'Hello' if locale is 'en'
|
|
232
|
+
// Returns 'Bonjour' if locale is 'fr'
|
|
233
|
+
// Falls back to default locale if locale not found
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Next.js Integration
|
|
237
|
+
|
|
238
|
+
### App Router Structure
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
src/app/
|
|
242
|
+
[locale]/
|
|
243
|
+
layout.tsx # Locale-aware layout
|
|
244
|
+
page.tsx # Homepage
|
|
245
|
+
about/
|
|
246
|
+
page.tsx # About page
|
|
247
|
+
services/
|
|
248
|
+
page.tsx # Services page
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Layout Setup
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
// src/app/[locale]/layout.tsx
|
|
255
|
+
import { i18nConfig } from '@/config/i18n';
|
|
256
|
+
import { LanguageSelector } from '@zoyth/simple-site-framework';
|
|
257
|
+
|
|
258
|
+
export async function generateStaticParams() {
|
|
259
|
+
return i18nConfig.locales.map((locale) => ({ locale }));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export default function LocaleLayout({
|
|
263
|
+
children,
|
|
264
|
+
params: { locale }
|
|
265
|
+
}: {
|
|
266
|
+
children: React.ReactNode;
|
|
267
|
+
params: { locale: string };
|
|
268
|
+
}) {
|
|
269
|
+
return (
|
|
270
|
+
<html lang={locale} dir={isRtlLocale(locale) ? 'rtl' : 'ltr'}>
|
|
271
|
+
<body>
|
|
272
|
+
<Header>
|
|
273
|
+
<LanguageSelector currentLocale={locale} />
|
|
274
|
+
</Header>
|
|
275
|
+
{children}
|
|
276
|
+
</body>
|
|
277
|
+
</html>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Page Example
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// src/app/[locale]/about/page.tsx
|
|
286
|
+
import { aboutContent } from '@/config/content/about';
|
|
287
|
+
import { AboutSection } from '@zoyth/simple-site-framework';
|
|
288
|
+
|
|
289
|
+
export default function AboutPage({
|
|
290
|
+
params: { locale }
|
|
291
|
+
}: {
|
|
292
|
+
params: { locale: string };
|
|
293
|
+
}) {
|
|
294
|
+
return <AboutSection {...aboutContent} locale={locale} />;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Middleware Setup
|
|
299
|
+
|
|
300
|
+
Create middleware for automatic redirection and language detection:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// src/middleware.ts
|
|
304
|
+
import { createI18nMiddleware } from '@zoyth/simple-site-framework/lib/i18n';
|
|
305
|
+
import { i18nConfig } from './config/i18n';
|
|
306
|
+
|
|
307
|
+
export default createI18nMiddleware(i18nConfig);
|
|
308
|
+
|
|
309
|
+
export const config = {
|
|
310
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
|
311
|
+
};
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**What it does:**
|
|
315
|
+
1. Detects locale from URL, cookie, or browser
|
|
316
|
+
2. Redirects to appropriate URL based on `localePrefix` mode
|
|
317
|
+
3. Sets locale cookie for persistence
|
|
318
|
+
4. Validates locale is supported
|
|
319
|
+
|
|
320
|
+
## SEO Optimization
|
|
321
|
+
|
|
322
|
+
### Meta Tags
|
|
323
|
+
|
|
324
|
+
Add language alternates:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// src/app/[locale]/page.tsx
|
|
328
|
+
import { I18nMetaTags } from '@zoyth/simple-site-framework';
|
|
329
|
+
|
|
330
|
+
export default function Page({ params: { locale } }) {
|
|
331
|
+
return (
|
|
332
|
+
<>
|
|
333
|
+
<I18nMetaTags
|
|
334
|
+
currentLocale={locale}
|
|
335
|
+
pathname="/about"
|
|
336
|
+
baseUrl="https://yoursite.com"
|
|
337
|
+
/>
|
|
338
|
+
{/* Page content */}
|
|
339
|
+
</>
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Generates:
|
|
345
|
+
```html
|
|
346
|
+
<link rel="canonical" href="https://yoursite.com/about" />
|
|
347
|
+
<link rel="alternate" hrefLang="en" href="https://yoursite.com/about" />
|
|
348
|
+
<link rel="alternate" hrefLang="fr" href="https://yoursite.com/fr/about" />
|
|
349
|
+
<link rel="alternate" hrefLang="x-default" href="https://yoursite.com/about" />
|
|
350
|
+
<meta property="og:locale" content="en" />
|
|
351
|
+
<meta property="og:locale:alternate" content="fr" />
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Sitemap Generation
|
|
355
|
+
|
|
356
|
+
Generate locale-specific sitemaps:
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
// src/app/sitemap.ts
|
|
360
|
+
import { i18nConfig } from '@/config/i18n';
|
|
361
|
+
|
|
362
|
+
export default function sitemap() {
|
|
363
|
+
const pages = ['', 'about', 'services', 'contact'];
|
|
364
|
+
|
|
365
|
+
return pages.flatMap(page =>
|
|
366
|
+
i18nConfig.locales.map(locale => ({
|
|
367
|
+
url: `https://yoursite.com/${locale !== 'en' ? `${locale}/` : ''}${page}`,
|
|
368
|
+
lastModified: new Date(),
|
|
369
|
+
changeFrequency: 'weekly',
|
|
370
|
+
priority: page === '' ? 1.0 : 0.8,
|
|
371
|
+
alternates: {
|
|
372
|
+
languages: Object.fromEntries(
|
|
373
|
+
i18nConfig.locales.map(l => [
|
|
374
|
+
l,
|
|
375
|
+
`https://yoursite.com/${l !== 'en' ? `${l}/` : ''}${page}`
|
|
376
|
+
])
|
|
377
|
+
)
|
|
378
|
+
}
|
|
379
|
+
}))
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
## Content Management
|
|
385
|
+
|
|
386
|
+
### Pattern 1: Separate Files per Locale
|
|
387
|
+
|
|
388
|
+
```
|
|
389
|
+
src/content/
|
|
390
|
+
en/
|
|
391
|
+
home.ts
|
|
392
|
+
about.ts
|
|
393
|
+
fr/
|
|
394
|
+
home.ts
|
|
395
|
+
about.ts
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
// src/content/en/home.ts
|
|
400
|
+
export const homeContent = {
|
|
401
|
+
hero: {
|
|
402
|
+
heading: 'Welcome to Our Site',
|
|
403
|
+
description: 'We help businesses grow'
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/content/fr/home.ts
|
|
408
|
+
export const homeContent = {
|
|
409
|
+
hero: {
|
|
410
|
+
heading: 'Bienvenue sur notre site',
|
|
411
|
+
description: 'Nous aidons les entreprises à croître'
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// Usage
|
|
416
|
+
import { homeContent as homeContentEn } from '@/content/en/home';
|
|
417
|
+
import { homeContent as homeContentFr } from '@/content/fr/home';
|
|
418
|
+
|
|
419
|
+
const content = locale === 'en' ? homeContentEn : homeContentFr;
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Pattern 2: Unified LocalizedStrings
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
// src/content/home.ts
|
|
426
|
+
export const homeContent = {
|
|
427
|
+
hero: {
|
|
428
|
+
heading: {
|
|
429
|
+
en: 'Welcome to Our Site',
|
|
430
|
+
fr: 'Bienvenue sur notre site'
|
|
431
|
+
},
|
|
432
|
+
description: {
|
|
433
|
+
en: 'We help businesses grow',
|
|
434
|
+
fr: 'Nous aidons les entreprises à croître'
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// Usage - pass locale to component
|
|
440
|
+
<HeroSection {...homeContent.hero} locale={locale} />
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Pattern 3: CMS Integration
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// Fetch localized content from CMS
|
|
447
|
+
async function getContent(slug: string, locale: string) {
|
|
448
|
+
const response = await fetch(
|
|
449
|
+
`https://cms.yoursite.com/api/content/${slug}?locale=${locale}`
|
|
450
|
+
);
|
|
451
|
+
return response.json();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Use in page
|
|
455
|
+
export default async function Page({ params: { locale, slug } }) {
|
|
456
|
+
const content = await getContent(slug, locale);
|
|
457
|
+
return <HeroSection {...content.hero} locale={locale} />;
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## Language Selector
|
|
462
|
+
|
|
463
|
+
The framework provides an adaptive language selector:
|
|
464
|
+
|
|
465
|
+
```typescript
|
|
466
|
+
import { LanguageSelector } from '@zoyth/simple-site-framework';
|
|
467
|
+
|
|
468
|
+
<LanguageSelector
|
|
469
|
+
currentLocale={locale}
|
|
470
|
+
variant="auto" // 'text' for 2 languages, 'dropdown' for 3+
|
|
471
|
+
/>
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
**Automatic behavior:**
|
|
475
|
+
- 2 languages → Simple text toggle
|
|
476
|
+
- 3+ languages → Dropdown menu
|
|
477
|
+
- Generates URLs based on `localePrefix` mode
|
|
478
|
+
- Sets cookie on language change
|
|
479
|
+
- Handles slug translations
|
|
480
|
+
|
|
481
|
+
## Advanced Features
|
|
482
|
+
|
|
483
|
+
### RTL Language Support
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
export const i18nConfig: I18nConfig = {
|
|
487
|
+
locales: ['en', 'ar', 'he'],
|
|
488
|
+
defaultLocale: 'en',
|
|
489
|
+
rtlLocales: ['ar', 'he'],
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// Apply to layout
|
|
493
|
+
<html lang={locale} dir={getTextDirection(locale)}>
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Slug Translations
|
|
497
|
+
|
|
498
|
+
Translate URL slugs:
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// src/config/slug-translations.ts
|
|
502
|
+
export const slugTranslations = {
|
|
503
|
+
'/about': {
|
|
504
|
+
en: '/about',
|
|
505
|
+
fr: '/a-propos',
|
|
506
|
+
es: '/acerca-de'
|
|
507
|
+
},
|
|
508
|
+
'/services': {
|
|
509
|
+
en: '/services',
|
|
510
|
+
fr: '/services',
|
|
511
|
+
es: '/servicios'
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Date & Number Formatting
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
import { formatDate, formatNumber, formatCurrency } from '@zoyth/simple-site-framework/lib/i18n';
|
|
520
|
+
|
|
521
|
+
// Dates
|
|
522
|
+
formatDate(new Date(), 'fr', { dateStyle: 'long' });
|
|
523
|
+
// → "15 janvier 2026"
|
|
524
|
+
|
|
525
|
+
// Numbers
|
|
526
|
+
formatNumber(1234567.89, 'fr');
|
|
527
|
+
// → "1 234 567,89"
|
|
528
|
+
|
|
529
|
+
// Currency
|
|
530
|
+
formatCurrency(99.99, 'fr', 'EUR');
|
|
531
|
+
// → "99,99 €"
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## Best Practices
|
|
535
|
+
|
|
536
|
+
### ✅ Do
|
|
537
|
+
|
|
538
|
+
- Use LocalizedString for all user-facing text
|
|
539
|
+
- Set up middleware for automatic detection
|
|
540
|
+
- Include hreflang tags for SEO
|
|
541
|
+
- Test with actual users of each language
|
|
542
|
+
- Use professional translation services
|
|
543
|
+
- Keep locale switching visible and accessible
|
|
544
|
+
|
|
545
|
+
### ❌ Don't
|
|
546
|
+
|
|
547
|
+
- Hardcode text in component files
|
|
548
|
+
- Forget to add new locales to all LocalizedStrings
|
|
549
|
+
- Use machine translation without review
|
|
550
|
+
- Assume all languages read left-to-right
|
|
551
|
+
- Forget to translate meta tags and alt text
|
|
552
|
+
- Use flags to represent languages (accessibility issue)
|
|
553
|
+
|
|
554
|
+
## Migration from Hardcoded Locales
|
|
555
|
+
|
|
556
|
+
If upgrading from the old hardcoded `['fr', 'en']` system:
|
|
557
|
+
|
|
558
|
+
1. Create i18n config file
|
|
559
|
+
2. Set `locales: ['fr', 'en']` to match previous behavior
|
|
560
|
+
3. Keep `localePrefix: 'always'` for identical URLs
|
|
561
|
+
4. Test thoroughly before changing `localePrefix`
|
|
562
|
+
|
|
563
|
+
## Troubleshooting
|
|
564
|
+
|
|
565
|
+
### Wrong language displaying
|
|
566
|
+
|
|
567
|
+
**Check:**
|
|
568
|
+
1. `locale` prop is being passed to components
|
|
569
|
+
2. LocalizedString has entry for that locale
|
|
570
|
+
3. Middleware is configured correctly
|
|
571
|
+
4. Cookie/browser detection working
|
|
572
|
+
|
|
573
|
+
### Language selector not appearing
|
|
574
|
+
|
|
575
|
+
**Check:**
|
|
576
|
+
1. LanguageSelector is in layout
|
|
577
|
+
2. `currentLocale` prop is provided
|
|
578
|
+
3. More than 1 locale in i18nConfig
|
|
579
|
+
|
|
580
|
+
### SEO tags missing
|
|
581
|
+
|
|
582
|
+
**Check:**
|
|
583
|
+
1. I18nMetaTags component is used
|
|
584
|
+
2. baseUrl is correct
|
|
585
|
+
3. pathname matches actual route
|
|
586
|
+
|
|
587
|
+
## Related Documentation
|
|
588
|
+
|
|
589
|
+
- **[Configuration-Driven](../core-concepts/configuration-driven.md)** - Content organization patterns
|
|
590
|
+
- **[LanguageSelector](../components/layout/LanguageSelector.md)** - Language switcher component
|
|
591
|
+
- **[SEO](./seo.md)** - Search engine optimization
|
|
592
|
+
|
|
593
|
+
## API Reference
|
|
594
|
+
|
|
595
|
+
Full i18n utilities: **[API Reference](../api/utilities.md#i18n)**
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Performance
|
|
2
|
+
|
|
3
|
+
Performance optimization features for fast page loads and great Core Web Vitals.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The framework provides several built-in performance features:
|
|
8
|
+
|
|
9
|
+
- Server Components by default (zero client JS)
|
|
10
|
+
- Lazy loading for below-the-fold content
|
|
11
|
+
- Code splitting with dynamic imports
|
|
12
|
+
- Image optimization via Next.js Image
|
|
13
|
+
- Minimal bundle size
|
|
14
|
+
|
|
15
|
+
## Key Concepts
|
|
16
|
+
|
|
17
|
+
Most framework components are Server Components, meaning they ship zero JavaScript to the browser. Only interactive components (marked as Client Components) add to the JS bundle.
|
|
18
|
+
|
|
19
|
+
## Topics
|
|
20
|
+
|
|
21
|
+
- [Lazy Loading](./lazy-loading.md) - Defer loading of non-critical content
|
|
22
|
+
- [Image Optimization](./image-optimization.md) - Optimize images for web
|
|
23
|
+
- [Code Splitting](./code-splitting.md) - Split bundles for faster loads
|
|
24
|
+
- [Caching](./caching.md) - Caching strategies
|
|
25
|
+
- [Bundle Size](./bundle-size.md) - Minimize JavaScript bundle
|
|
26
|
+
- [Monitoring](./monitoring.md) - Measure and track performance
|
|
27
|
+
|
|
28
|
+
## Quick Wins
|
|
29
|
+
|
|
30
|
+
### Use LazySection
|
|
31
|
+
|
|
32
|
+
Wrap below-the-fold sections:
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { LazySection } from '@zoyth/simple-site-framework/components';
|
|
36
|
+
|
|
37
|
+
<HeroSection {...heroProps} /> {/* Above fold - load immediately */}
|
|
38
|
+
|
|
39
|
+
<LazySection> {/* Below fold - lazy loaded */}
|
|
40
|
+
<FeaturesGrid features={features} />
|
|
41
|
+
</LazySection>
|
|
42
|
+
|
|
43
|
+
<LazySection>
|
|
44
|
+
<TestimonialSection testimonials={testimonials} />
|
|
45
|
+
</LazySection>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Prefer Server Components
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// ✅ Server Component (default) - no client JS
|
|
52
|
+
import { HeroSection } from '@zoyth/simple-site-framework/components';
|
|
53
|
+
|
|
54
|
+
// ⚠️ Client Component - adds to JS bundle
|
|
55
|
+
'use client';
|
|
56
|
+
import { AnalyticsTracker } from '@zoyth/simple-site-framework/client';
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Optimize Images
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import Image from 'next/image';
|
|
63
|
+
|
|
64
|
+
<Image
|
|
65
|
+
src="/hero.jpg"
|
|
66
|
+
alt="Hero image"
|
|
67
|
+
width={1200}
|
|
68
|
+
height={600}
|
|
69
|
+
priority // Above-fold images
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## See Also
|
|
74
|
+
|
|
75
|
+
- [Existing Performance Documentation](../../PERFORMANCE.md)
|
|
76
|
+
- [LazySection Component](../../components/LazySection.md)
|
|
77
|
+
- [AnimatedSection Component](../../components/AnimatedSection.md)
|