@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,273 @@
|
|
|
1
|
+
# i18n Best Practices
|
|
2
|
+
|
|
3
|
+
Guidelines and patterns for effective internationalization.
|
|
4
|
+
|
|
5
|
+
## Planning
|
|
6
|
+
|
|
7
|
+
### Choose Locales Carefully
|
|
8
|
+
|
|
9
|
+
- Start with languages your target audience actually speaks
|
|
10
|
+
- Consider regional variants (en-US vs en-GB, fr-FR vs fr-CA)
|
|
11
|
+
- Don't add languages you can't properly maintain
|
|
12
|
+
|
|
13
|
+
### URL Structure
|
|
14
|
+
|
|
15
|
+
Choose the right `localePrefix` mode:
|
|
16
|
+
|
|
17
|
+
- **'as-needed'** (Recommended) - Clean default locale URLs, prefixed alternatives
|
|
18
|
+
- **'always'** - Explicit language in all URLs, equal treatment
|
|
19
|
+
- **'never'** - Simplest URLs, harder to share specific language links
|
|
20
|
+
|
|
21
|
+
## Content Management
|
|
22
|
+
|
|
23
|
+
### Keep Translations Complete
|
|
24
|
+
|
|
25
|
+
Ensure all locales have translations:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// ✅ Good - All locales covered
|
|
29
|
+
const heading = {
|
|
30
|
+
en: 'Welcome',
|
|
31
|
+
fr: 'Bienvenue',
|
|
32
|
+
es: 'Bienvenido',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// ❌ Bad - Missing Spanish
|
|
36
|
+
const heading = {
|
|
37
|
+
en: 'Welcome',
|
|
38
|
+
fr: 'Bienvenue',
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Use TypeScript for Safety
|
|
43
|
+
|
|
44
|
+
Define translation keys with types:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
type TranslationKeys = {
|
|
48
|
+
heading: LocalizedString;
|
|
49
|
+
description: LocalizedString;
|
|
50
|
+
cta: LocalizedString;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const translations: TranslationKeys = {
|
|
54
|
+
heading: { en: '...', fr: '...' },
|
|
55
|
+
description: { en: '...', fr: '...' },
|
|
56
|
+
cta: { en: '...', fr: '...' },
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Organize Translations
|
|
61
|
+
|
|
62
|
+
Group by feature or page:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
config/translations/
|
|
66
|
+
├── common.ts # Shared across site
|
|
67
|
+
├── navigation.ts # Nav, footer links
|
|
68
|
+
├── home.ts # Homepage
|
|
69
|
+
├── about.ts # About page
|
|
70
|
+
└── services.ts # Services page
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## SEO Optimization
|
|
74
|
+
|
|
75
|
+
### Use hreflang Tags
|
|
76
|
+
|
|
77
|
+
Framework automatically generates hreflang tags via I18nMetaTags:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { I18nMetaTags } from '@zoyth/simple-site-framework/components';
|
|
81
|
+
|
|
82
|
+
<I18nMetaTags
|
|
83
|
+
currentLocale={locale}
|
|
84
|
+
pathname={pathname}
|
|
85
|
+
baseUrl="https://example.com"
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Separate Sitemaps
|
|
90
|
+
|
|
91
|
+
Generate sitemap per locale or include all in one with locale information.
|
|
92
|
+
|
|
93
|
+
### Canonical URLs
|
|
94
|
+
|
|
95
|
+
Set canonical URLs correctly for each locale variant.
|
|
96
|
+
|
|
97
|
+
## Performance
|
|
98
|
+
|
|
99
|
+
### Static Generation
|
|
100
|
+
|
|
101
|
+
Pre-generate all locale variants:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
export async function generateStaticParams() {
|
|
105
|
+
const locales = ['en', 'fr', 'es'];
|
|
106
|
+
|
|
107
|
+
return locales.map(locale => ({ locale }));
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Code Splitting
|
|
112
|
+
|
|
113
|
+
Split translations by page to reduce bundle size:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// Load translations on-demand
|
|
117
|
+
const translations = await import(`./translations/${locale}.ts`);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Avoid Over-Translation
|
|
121
|
+
|
|
122
|
+
Don't translate:
|
|
123
|
+
- Brand names
|
|
124
|
+
- Product names (unless officially localized)
|
|
125
|
+
- Technical terms without clear equivalents
|
|
126
|
+
- Code examples
|
|
127
|
+
|
|
128
|
+
## User Experience
|
|
129
|
+
|
|
130
|
+
### Language Detection
|
|
131
|
+
|
|
132
|
+
- Enable browser detection for first visit
|
|
133
|
+
- Store preference in cookie
|
|
134
|
+
- Allow manual override via LanguageSelector
|
|
135
|
+
- Don't force redirects on every visit
|
|
136
|
+
|
|
137
|
+
### Language Switcher Placement
|
|
138
|
+
|
|
139
|
+
Place LanguageSelector where users expect it:
|
|
140
|
+
- Header (top-right is common)
|
|
141
|
+
- Footer
|
|
142
|
+
- Mobile menu
|
|
143
|
+
|
|
144
|
+
### Preserve Context
|
|
145
|
+
|
|
146
|
+
When switching languages:
|
|
147
|
+
```typescript
|
|
148
|
+
// ✅ Good - Stay on same page
|
|
149
|
+
/en/about → /fr/about
|
|
150
|
+
|
|
151
|
+
// ❌ Bad - Go to homepage
|
|
152
|
+
/en/about → /fr/
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Content Guidelines
|
|
156
|
+
|
|
157
|
+
### Avoid Concatenation
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// ❌ Bad - Word order varies by language
|
|
161
|
+
const message = `${userName} ${action} ${item}`;
|
|
162
|
+
|
|
163
|
+
// ✅ Good - Full sentence per locale
|
|
164
|
+
const message = {
|
|
165
|
+
en: `${userName} purchased ${item}`,
|
|
166
|
+
fr: `${userName} a acheté ${item}`,
|
|
167
|
+
};
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Handle Pluralization
|
|
171
|
+
|
|
172
|
+
Different languages have different plural rules:
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const itemCount = {
|
|
176
|
+
en: count === 1 ? '1 item' : `${count} items`,
|
|
177
|
+
fr: count <= 1 ? '1 article' : `${count} articles`,
|
|
178
|
+
ar: /* Arabic has 6 plural forms! */
|
|
179
|
+
};
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Consider using a library like `react-intl` for complex pluralization.
|
|
183
|
+
|
|
184
|
+
### Date and Time Clarity
|
|
185
|
+
|
|
186
|
+
Always use locale-aware formatting:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { formatDate } from '@zoyth/simple-site-framework/lib/i18n';
|
|
190
|
+
|
|
191
|
+
formatDate(date, locale, { dateStyle: 'long' });
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Testing
|
|
195
|
+
|
|
196
|
+
### Test All Locales
|
|
197
|
+
|
|
198
|
+
- Navigate through site in each locale
|
|
199
|
+
- Test forms and validation messages
|
|
200
|
+
- Verify date/number formatting
|
|
201
|
+
- Check layout with longer translations (German often longer than English)
|
|
202
|
+
- Test RTL languages if supported
|
|
203
|
+
|
|
204
|
+
### Automated Testing
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
describe('i18n', () => {
|
|
208
|
+
const locales = ['en', 'fr', 'es'];
|
|
209
|
+
|
|
210
|
+
locales.forEach(locale => {
|
|
211
|
+
it(`renders ${locale} homepage`, () => {
|
|
212
|
+
// Test each locale
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Maintenance
|
|
219
|
+
|
|
220
|
+
### Version Control
|
|
221
|
+
|
|
222
|
+
Track translations in git alongside code:
|
|
223
|
+
- Easy to review changes
|
|
224
|
+
- See translation history
|
|
225
|
+
- Merge conflicts are manageable
|
|
226
|
+
|
|
227
|
+
### Translation Workflow
|
|
228
|
+
|
|
229
|
+
1. Develop feature in default locale
|
|
230
|
+
2. Extract translatable strings
|
|
231
|
+
3. Send to translators
|
|
232
|
+
4. Review and integrate translations
|
|
233
|
+
5. Test all locales
|
|
234
|
+
6. Deploy
|
|
235
|
+
|
|
236
|
+
### Professional Translation
|
|
237
|
+
|
|
238
|
+
For production sites:
|
|
239
|
+
- Use professional translators
|
|
240
|
+
- Avoid machine translation for customer-facing content
|
|
241
|
+
- Consider translation management platforms
|
|
242
|
+
- Review translations in context
|
|
243
|
+
|
|
244
|
+
## Common Pitfalls
|
|
245
|
+
|
|
246
|
+
### Don't Assume English
|
|
247
|
+
|
|
248
|
+
Framework doesn't assume English as default - you choose the default locale.
|
|
249
|
+
|
|
250
|
+
### Don't Hardcode Strings
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// ❌ Bad
|
|
254
|
+
<button>Click here</button>
|
|
255
|
+
|
|
256
|
+
// ✅ Good
|
|
257
|
+
<button>{buttonText}</button>
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Don't Skip Metadata
|
|
261
|
+
|
|
262
|
+
Translate:
|
|
263
|
+
- Page titles
|
|
264
|
+
- Meta descriptions
|
|
265
|
+
- Alt text for images
|
|
266
|
+
- Form labels and errors
|
|
267
|
+
- Button text
|
|
268
|
+
|
|
269
|
+
## See Also
|
|
270
|
+
|
|
271
|
+
- [Configuration](./configuration.md)
|
|
272
|
+
- [Translations](./translations.md)
|
|
273
|
+
- [SEO Guide](../seo/best-practices.md)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# i18n Configuration
|
|
2
|
+
|
|
3
|
+
Configure internationalization settings for your multi-language site.
|
|
4
|
+
|
|
5
|
+
## Configuration File
|
|
6
|
+
|
|
7
|
+
Create your i18n configuration:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// src/config/i18n.ts
|
|
11
|
+
import type { I18nConfig } from '@zoyth/simple-site-framework/lib/i18n';
|
|
12
|
+
|
|
13
|
+
export const i18nConfig: I18nConfig = {
|
|
14
|
+
locales: ['en', 'fr', 'es', 'de'],
|
|
15
|
+
defaultLocale: 'en',
|
|
16
|
+
localePrefix: 'as-needed',
|
|
17
|
+
localeDetection: true,
|
|
18
|
+
localeNames: {
|
|
19
|
+
en: 'English',
|
|
20
|
+
fr: 'Français',
|
|
21
|
+
es: 'Español',
|
|
22
|
+
de: 'Deutsch',
|
|
23
|
+
},
|
|
24
|
+
localeLabels: {
|
|
25
|
+
en: 'EN',
|
|
26
|
+
fr: 'FR',
|
|
27
|
+
es: 'ES',
|
|
28
|
+
de: 'DE',
|
|
29
|
+
},
|
|
30
|
+
rtlLocales: ['ar', 'he'],
|
|
31
|
+
localeCookie: {
|
|
32
|
+
name: 'NEXT_LOCALE',
|
|
33
|
+
maxAge: 365 * 24 * 60 * 60, // 1 year
|
|
34
|
+
sameSite: 'lax',
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Configuration Options
|
|
40
|
+
|
|
41
|
+
| Option | Type | Required | Description |
|
|
42
|
+
|--------|------|----------|-------------|
|
|
43
|
+
| `locales` | `string[]` | Yes | Supported locale codes |
|
|
44
|
+
| `defaultLocale` | `string` | Yes | Default/fallback locale |
|
|
45
|
+
| `localePrefix` | `'always' \| 'as-needed' \| 'never'` | No | URL prefix mode (default: 'as-needed') |
|
|
46
|
+
| `localeDetection` | `boolean` | No | Auto-detect from browser (default: true) |
|
|
47
|
+
| `localeNames` | `Record<string, string>` | No | Full language names for UI |
|
|
48
|
+
| `localeLabels` | `Record<string, string>` | No | Short labels for UI |
|
|
49
|
+
| `rtlLocales` | `string[]` | No | Right-to-left locales |
|
|
50
|
+
| `localeCookie` | `object` | No | Cookie configuration |
|
|
51
|
+
|
|
52
|
+
## Locale Prefix Modes
|
|
53
|
+
|
|
54
|
+
### 'always'
|
|
55
|
+
All URLs include locale prefix:
|
|
56
|
+
- `/en/about`
|
|
57
|
+
- `/fr/about`
|
|
58
|
+
|
|
59
|
+
### 'as-needed' (Default)
|
|
60
|
+
Only non-default locales have prefix:
|
|
61
|
+
- `/about` (default locale)
|
|
62
|
+
- `/fr/about` (other locales)
|
|
63
|
+
|
|
64
|
+
### 'never'
|
|
65
|
+
No locale prefixes, detection via cookie/header:
|
|
66
|
+
- `/about` (all languages)
|
|
67
|
+
|
|
68
|
+
## Initialization
|
|
69
|
+
|
|
70
|
+
Initialize config in your root layout:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// app/[locale]/layout.tsx
|
|
74
|
+
import { setI18nConfig } from '@zoyth/simple-site-framework/lib/i18n';
|
|
75
|
+
import { i18nConfig } from '../../config/i18n';
|
|
76
|
+
|
|
77
|
+
setI18nConfig(i18nConfig);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## See Also
|
|
81
|
+
|
|
82
|
+
- [Routing](./routing.md)
|
|
83
|
+
- [Locale Detection](./locale-detection.md)
|
|
84
|
+
- [Configuration Guide](../../i18n/CONFIGURATION.md)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Locale-Aware Formatting
|
|
2
|
+
|
|
3
|
+
Format dates, numbers, and currency based on user's locale.
|
|
4
|
+
|
|
5
|
+
## Date Formatting
|
|
6
|
+
|
|
7
|
+
Format dates according to locale conventions:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { formatDate } from '@zoyth/simple-site-framework/lib/i18n';
|
|
11
|
+
|
|
12
|
+
const date = new Date('2024-12-25');
|
|
13
|
+
|
|
14
|
+
formatDate(date, 'en'); // "12/25/2024"
|
|
15
|
+
formatDate(date, 'fr'); // "25/12/2024"
|
|
16
|
+
formatDate(date, 'de'); // "25.12.2024"
|
|
17
|
+
|
|
18
|
+
// With options
|
|
19
|
+
formatDate(date, 'en', {
|
|
20
|
+
dateStyle: 'long',
|
|
21
|
+
}); // "December 25, 2024"
|
|
22
|
+
|
|
23
|
+
formatDate(date, 'fr', {
|
|
24
|
+
dateStyle: 'long',
|
|
25
|
+
}); // "25 décembre 2024"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Number Formatting
|
|
29
|
+
|
|
30
|
+
Format numbers with locale-specific separators:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { formatNumber } from '@zoyth/simple-site-framework/lib/i18n';
|
|
34
|
+
|
|
35
|
+
const number = 1234567.89;
|
|
36
|
+
|
|
37
|
+
formatNumber(number, 'en'); // "1,234,567.89"
|
|
38
|
+
formatNumber(number, 'fr'); // "1 234 567,89"
|
|
39
|
+
formatNumber(number, 'de'); // "1.234.567,89"
|
|
40
|
+
|
|
41
|
+
// With options
|
|
42
|
+
formatNumber(number, 'en', {
|
|
43
|
+
minimumFractionDigits: 2,
|
|
44
|
+
maximumFractionDigits: 2,
|
|
45
|
+
}); // "1,234,567.89"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Currency Formatting
|
|
49
|
+
|
|
50
|
+
Format currency amounts:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { formatCurrency } from '@zoyth/simple-site-framework/lib/i18n';
|
|
54
|
+
|
|
55
|
+
const amount = 1234.56;
|
|
56
|
+
|
|
57
|
+
formatCurrency(amount, 'en', 'USD'); // "$1,234.56"
|
|
58
|
+
formatCurrency(amount, 'fr', 'EUR'); // "1 234,56 €"
|
|
59
|
+
formatCurrency(amount, 'de', 'EUR'); // "1.234,56 €"
|
|
60
|
+
formatCurrency(amount, 'ja', 'JPY'); // "¥1,235"
|
|
61
|
+
|
|
62
|
+
// With options
|
|
63
|
+
formatCurrency(amount, 'en', 'USD', {
|
|
64
|
+
currencyDisplay: 'name',
|
|
65
|
+
}); // "1,234.56 US dollars"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Relative Time Formatting
|
|
69
|
+
|
|
70
|
+
Format relative time periods:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { formatRelativeTime } from '@zoyth/simple-site-framework/lib/i18n';
|
|
74
|
+
|
|
75
|
+
formatRelativeTime(-1, 'day', 'en'); // "1 day ago"
|
|
76
|
+
formatRelativeTime(-1, 'day', 'fr'); // "il y a 1 jour"
|
|
77
|
+
formatRelativeTime(2, 'week', 'en'); // "in 2 weeks"
|
|
78
|
+
formatRelativeTime(2, 'week', 'es'); // "dentro de 2 semanas"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## List Formatting
|
|
82
|
+
|
|
83
|
+
Format lists according to locale:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const items = ['Apple', 'Banana', 'Orange'];
|
|
87
|
+
|
|
88
|
+
new Intl.ListFormat('en').format(items);
|
|
89
|
+
// "Apple, Banana, and Orange"
|
|
90
|
+
|
|
91
|
+
new Intl.ListFormat('fr').format(items);
|
|
92
|
+
// "Apple, Banana et Orange"
|
|
93
|
+
|
|
94
|
+
new Intl.ListFormat('es', { type: 'disjunction' }).format(items);
|
|
95
|
+
// "Apple, Banana o Orange"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Intl API Reference
|
|
99
|
+
|
|
100
|
+
All formatters use the native JavaScript Intl API:
|
|
101
|
+
|
|
102
|
+
- `Intl.DateTimeFormat` - Date/time formatting
|
|
103
|
+
- `Intl.NumberFormat` - Number/currency formatting
|
|
104
|
+
- `Intl.RelativeTimeFormat` - Relative time formatting
|
|
105
|
+
- `Intl.ListFormat` - List formatting
|
|
106
|
+
|
|
107
|
+
## Usage in Components
|
|
108
|
+
|
|
109
|
+
Example with formatted dates:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { formatDate } from '@zoyth/simple-site-framework/lib/i18n';
|
|
113
|
+
|
|
114
|
+
export function BlogPost({ date, locale }: Props) {
|
|
115
|
+
const formattedDate = formatDate(date, locale, {
|
|
116
|
+
dateStyle: 'long',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<article>
|
|
121
|
+
<time dateTime={date.toISOString()}>
|
|
122
|
+
{formattedDate}
|
|
123
|
+
</time>
|
|
124
|
+
</article>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## See Also
|
|
130
|
+
|
|
131
|
+
- [Translations](./translations.md)
|
|
132
|
+
- [Configuration](./configuration.md)
|
|
133
|
+
- [MDN Intl Documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Locale Detection
|
|
2
|
+
|
|
3
|
+
Automatic language detection from browser, cookies, and URL.
|
|
4
|
+
|
|
5
|
+
## Detection Strategy
|
|
6
|
+
|
|
7
|
+
The middleware detects locale in this order:
|
|
8
|
+
|
|
9
|
+
1. **URL Parameter** - Explicit locale in path (`/fr/about`)
|
|
10
|
+
2. **Cookie** - Previously selected locale (`NEXT_LOCALE` cookie)
|
|
11
|
+
3. **Accept-Language Header** - Browser language preference
|
|
12
|
+
4. **Default Locale** - Configured fallback
|
|
13
|
+
|
|
14
|
+
## Browser Detection
|
|
15
|
+
|
|
16
|
+
Enable in configuration:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
export const i18nConfig = {
|
|
20
|
+
locales: ['en', 'fr', 'es', 'de'],
|
|
21
|
+
defaultLocale: 'en',
|
|
22
|
+
localeDetection: true, // Enable browser detection
|
|
23
|
+
};
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Accept-Language Header
|
|
27
|
+
|
|
28
|
+
The framework parses the `Accept-Language` header:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
Accept-Language: fr-CA,fr;q=0.9,en;q=0.8,de;q=0.5
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Matches:
|
|
35
|
+
1. Exact match: `fr-CA` → `fr`
|
|
36
|
+
2. Language prefix: `fr` → `fr`
|
|
37
|
+
3. Quality values (q) determine priority
|
|
38
|
+
4. Fallback to default if no match
|
|
39
|
+
|
|
40
|
+
## Cookie Persistence
|
|
41
|
+
|
|
42
|
+
When user selects a language, it's stored in a cookie:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Cookie configuration
|
|
46
|
+
localeCookie: {
|
|
47
|
+
name: 'NEXT_LOCALE',
|
|
48
|
+
maxAge: 365 * 24 * 60 * 60, // 1 year
|
|
49
|
+
sameSite: 'lax',
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Cookie persists across:
|
|
54
|
+
- Browser sessions
|
|
55
|
+
- Different pages
|
|
56
|
+
- Returning visits
|
|
57
|
+
|
|
58
|
+
## Manual Cookie Setting
|
|
59
|
+
|
|
60
|
+
Set locale cookie programmatically:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { setLocaleCookie } from '@zoyth/simple-site-framework/lib/i18n';
|
|
64
|
+
|
|
65
|
+
function handleLanguageChange(locale: string) {
|
|
66
|
+
setLocaleCookie(locale);
|
|
67
|
+
// Navigate to new locale...
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Detection Flow
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
User visits site
|
|
75
|
+
↓
|
|
76
|
+
Check URL for locale
|
|
77
|
+
↓ (not found)
|
|
78
|
+
Check NEXT_LOCALE cookie
|
|
79
|
+
↓ (not found)
|
|
80
|
+
Parse Accept-Language header
|
|
81
|
+
↓ (no match)
|
|
82
|
+
Use default locale
|
|
83
|
+
↓
|
|
84
|
+
Redirect if needed (based on localePrefix mode)
|
|
85
|
+
↓
|
|
86
|
+
Set cookie with detected locale
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Disabling Detection
|
|
90
|
+
|
|
91
|
+
Disable automatic browser detection:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
export const i18nConfig = {
|
|
95
|
+
locales: ['en', 'fr'],
|
|
96
|
+
defaultLocale: 'en',
|
|
97
|
+
localeDetection: false, // Disable
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Users must explicitly select language via LanguageSelector.
|
|
102
|
+
|
|
103
|
+
## Testing Detection
|
|
104
|
+
|
|
105
|
+
Test different scenarios:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Test with specific Accept-Language
|
|
109
|
+
curl -H "Accept-Language: fr-FR,fr;q=0.9" http://localhost:3000/
|
|
110
|
+
|
|
111
|
+
# Test with cookie
|
|
112
|
+
curl -H "Cookie: NEXT_LOCALE=es" http://localhost:3000/
|
|
113
|
+
|
|
114
|
+
# Test URL override
|
|
115
|
+
curl http://localhost:3000/de/
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## See Also
|
|
119
|
+
|
|
120
|
+
- [Routing](./routing.md)
|
|
121
|
+
- [Configuration](./configuration.md)
|
|
122
|
+
- [LanguageSelector Component](../../components/layout/LanguageSelector.md)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Locale Routing
|
|
2
|
+
|
|
3
|
+
URL routing and navigation for multi-language sites.
|
|
4
|
+
|
|
5
|
+
## Route Structure
|
|
6
|
+
|
|
7
|
+
Based on your `localePrefix` configuration:
|
|
8
|
+
|
|
9
|
+
### 'as-needed' Mode (Default)
|
|
10
|
+
```
|
|
11
|
+
/ → Default locale homepage
|
|
12
|
+
/about → Default locale about page
|
|
13
|
+
/fr/ → French homepage
|
|
14
|
+
/fr/about → French about page
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### 'always' Mode
|
|
18
|
+
```
|
|
19
|
+
/en/ → English homepage
|
|
20
|
+
/en/about → English about page
|
|
21
|
+
/fr/ → French homepage
|
|
22
|
+
/fr/about → French about page
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 'never' Mode
|
|
26
|
+
```
|
|
27
|
+
/ → Homepage (locale from cookie/header)
|
|
28
|
+
/about → About page (locale from cookie/header)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Middleware Setup
|
|
32
|
+
|
|
33
|
+
Create middleware for automatic routing:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// src/middleware.ts
|
|
37
|
+
import { createI18nMiddleware } from '@zoyth/simple-site-framework/lib/i18n';
|
|
38
|
+
import { i18nConfig } from './config/i18n';
|
|
39
|
+
|
|
40
|
+
export default createI18nMiddleware(i18nConfig);
|
|
41
|
+
|
|
42
|
+
export const config = {
|
|
43
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
|
44
|
+
};
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Navigation
|
|
48
|
+
|
|
49
|
+
### Using Next.js Link
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import Link from 'next/link';
|
|
53
|
+
|
|
54
|
+
// Same-locale navigation
|
|
55
|
+
<Link href="/about">About</Link>
|
|
56
|
+
|
|
57
|
+
// Cross-locale navigation
|
|
58
|
+
<Link href="/fr/about">À propos</Link>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Language Switching
|
|
62
|
+
|
|
63
|
+
Use the LanguageSelector component:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { LanguageSelector } from '@zoyth/simple-site-framework/components';
|
|
67
|
+
|
|
68
|
+
<LanguageSelector currentLocale={locale} />
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Dynamic Routes
|
|
72
|
+
|
|
73
|
+
Handle dynamic routes with locale parameter:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// app/[locale]/blog/[slug]/page.tsx
|
|
77
|
+
export async function generateStaticParams() {
|
|
78
|
+
const locales = ['en', 'fr'];
|
|
79
|
+
const slugs = ['post-1', 'post-2'];
|
|
80
|
+
|
|
81
|
+
return locales.flatMap(locale =>
|
|
82
|
+
slugs.map(slug => ({ locale, slug }))
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Redirects
|
|
88
|
+
|
|
89
|
+
The middleware automatically:
|
|
90
|
+
1. Detects missing locale in URL
|
|
91
|
+
2. Determines user's preferred locale
|
|
92
|
+
3. Redirects to appropriate localized URL
|
|
93
|
+
4. Sets locale cookie for persistence
|
|
94
|
+
|
|
95
|
+
## See Also
|
|
96
|
+
|
|
97
|
+
- [Configuration](./configuration.md)
|
|
98
|
+
- [Locale Detection](./locale-detection.md)
|
|
99
|
+
- [Routing Examples](../../i18n/EXAMPLES.md)
|