@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.
Files changed (166) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +572 -0
  3. package/bin/create-simple-site.js +390 -0
  4. package/bin/simple-site.js +664 -0
  5. package/dist/client.js +135 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/client.mjs +107 -0
  8. package/dist/client.mjs.map +1 -0
  9. package/dist/components/index.d.mts +3936 -0
  10. package/dist/components/index.d.ts +3936 -0
  11. package/dist/components/index.js +38265 -0
  12. package/dist/components/index.js.map +1 -0
  13. package/dist/components/index.mjs +38173 -0
  14. package/dist/components/index.mjs.map +1 -0
  15. package/dist/config/index.d.mts +298 -0
  16. package/dist/config/index.d.ts +298 -0
  17. package/dist/config/index.js +19 -0
  18. package/dist/config/index.js.map +1 -0
  19. package/dist/config/index.mjs +1 -0
  20. package/dist/config/index.mjs.map +1 -0
  21. package/dist/index.d.mts +2184 -0
  22. package/dist/index.d.ts +2184 -0
  23. package/dist/index.js +1713 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/index.mjs +1605 -0
  26. package/dist/index.mjs.map +1 -0
  27. package/dist/lib/i18n/index.js +665 -0
  28. package/dist/lib/i18n/index.js.map +1 -0
  29. package/dist/lib/i18n/index.mjs +621 -0
  30. package/dist/lib/i18n/index.mjs.map +1 -0
  31. package/docs/DOCUMENTATION-STRUCTURE.md +1156 -0
  32. package/docs/EXPORTS.md +125 -0
  33. package/docs/PERFORMANCE.md +757 -0
  34. package/docs/POLICY-PAGES.md +867 -0
  35. package/docs/ROADMAP.md +334 -0
  36. package/docs/SEO.md +455 -0
  37. package/docs/SITEMAP.md +708 -0
  38. package/docs/STRUCTURED-DATA.md +671 -0
  39. package/docs/accessibility/common-patterns.md +529 -0
  40. package/docs/accessibility/keyboard-navigation.md +263 -0
  41. package/docs/accessibility/overview.md +122 -0
  42. package/docs/accessibility/screen-readers.md +311 -0
  43. package/docs/accessibility/wcag-compliance.md +159 -0
  44. package/docs/api/README.md +164 -0
  45. package/docs/api/components/Accessibility.md +356 -0
  46. package/docs/api/components/Button.md +240 -0
  47. package/docs/api/components/HeroSection.md +306 -0
  48. package/docs/architecture/decisions.md +449 -0
  49. package/docs/components/AnalyticsTracker.md +58 -0
  50. package/docs/components/AnimatedCounter.md +48 -0
  51. package/docs/components/AnimatedSection.md +56 -0
  52. package/docs/components/BlogCard.md +42 -0
  53. package/docs/components/Checkbox.md +56 -0
  54. package/docs/components/CodeBlock.md +52 -0
  55. package/docs/components/ComparisonTable.md +40 -0
  56. package/docs/components/ComponentDemo.md +38 -0
  57. package/docs/components/CountdownTimer.md +51 -0
  58. package/docs/components/ExitIntentModal.md +56 -0
  59. package/docs/components/FAQAccordion.md +66 -0
  60. package/docs/components/FeaturesGrid.md +55 -0
  61. package/docs/components/FileUpload.md +54 -0
  62. package/docs/components/I18nMetaTags.md +55 -0
  63. package/docs/components/Icon.md +53 -0
  64. package/docs/components/LazySection.md +46 -0
  65. package/docs/components/LiveProof.md +53 -0
  66. package/docs/components/LoadingSpinner.md +46 -0
  67. package/docs/components/MultiStepForm.md +48 -0
  68. package/docs/components/PolicyLayout.md +55 -0
  69. package/docs/components/PricingTable.md +49 -0
  70. package/docs/components/Radio.md +59 -0
  71. package/docs/components/SEOMetaTags.md +58 -0
  72. package/docs/components/ScriptInjector.md +50 -0
  73. package/docs/components/Select.md +72 -0
  74. package/docs/components/Skeleton.md +47 -0
  75. package/docs/components/StatsSection.md +48 -0
  76. package/docs/components/StickyBar.md +62 -0
  77. package/docs/components/StructuredData.md +99 -0
  78. package/docs/components/StyleGuide.md +46 -0
  79. package/docs/components/TableOfContents.md +47 -0
  80. package/docs/components/TestimonialCarousel.md +42 -0
  81. package/docs/components/Timeline.md +51 -0
  82. package/docs/components/Toast.md +59 -0
  83. package/docs/components/TrackedLink.md +62 -0
  84. package/docs/components/TrustBadges.md +44 -0
  85. package/docs/components/conversion/MobileCTA.md +363 -0
  86. package/docs/components/forms/ContactForm.md +75 -0
  87. package/docs/components/forms/FormField.md +74 -0
  88. package/docs/components/layout/Footer.md +601 -0
  89. package/docs/components/layout/Header.md +549 -0
  90. package/docs/components/layout/LanguageSelector.md +54 -0
  91. package/docs/components/layout/LanguageSwitcher.md +24 -0
  92. package/docs/components/overview.md +447 -0
  93. package/docs/components/sections/AboutSection.md +48 -0
  94. package/docs/components/sections/CTASection.md +596 -0
  95. package/docs/components/sections/CaseStudySection.md +47 -0
  96. package/docs/components/sections/ContactSection.md +599 -0
  97. package/docs/components/sections/FeatureSection.md +44 -0
  98. package/docs/components/sections/HeroSection.md +404 -0
  99. package/docs/components/sections/LogosSection.md +47 -0
  100. package/docs/components/sections/PersonalTaxesSection.md +23 -0
  101. package/docs/components/sections/RecruitingSection.md +23 -0
  102. package/docs/components/sections/SecurePortalSection.md +23 -0
  103. package/docs/components/sections/ServicePageLayout.md +52 -0
  104. package/docs/components/sections/ServicesSection.md +49 -0
  105. package/docs/components/sections/TestimonialSection.md +44 -0
  106. package/docs/components/sections/WhyChooseUsSection.md +54 -0
  107. package/docs/components/ui/Breadcrumb.md +70 -0
  108. package/docs/components/ui/Button.md +514 -0
  109. package/docs/components/ui/Card.md +501 -0
  110. package/docs/components/ui/Input.md +54 -0
  111. package/docs/components/ui/MobileLinks.md +43 -0
  112. package/docs/components/ui/Modal.md +60 -0
  113. package/docs/components/ui/Tabs.md +62 -0
  114. package/docs/components/ui/Textarea.md +52 -0
  115. package/docs/core-concepts/configuration-driven.md +552 -0
  116. package/docs/core-concepts/overview.md +351 -0
  117. package/docs/features/accessibility/README.md +73 -0
  118. package/docs/features/accessibility/aria-support.md +177 -0
  119. package/docs/features/accessibility/color-contrast.md +155 -0
  120. package/docs/features/accessibility/focus-management.md +187 -0
  121. package/docs/features/accessibility/testing.md +196 -0
  122. package/docs/features/analytics/README.md +51 -0
  123. package/docs/features/analytics/ab-testing.md +171 -0
  124. package/docs/features/analytics/conversion-tracking.md +207 -0
  125. package/docs/features/analytics/custom-events.md +219 -0
  126. package/docs/features/analytics/privacy.md +198 -0
  127. package/docs/features/analytics/setup.md +114 -0
  128. package/docs/features/analytics/tracking-events.md +224 -0
  129. package/docs/features/i18n/README.md +51 -0
  130. package/docs/features/i18n/best-practices.md +273 -0
  131. package/docs/features/i18n/configuration.md +84 -0
  132. package/docs/features/i18n/formatting.md +133 -0
  133. package/docs/features/i18n/locale-detection.md +122 -0
  134. package/docs/features/i18n/routing.md +99 -0
  135. package/docs/features/i18n/rtl-support.md +191 -0
  136. package/docs/features/i18n/translations.md +129 -0
  137. package/docs/features/internationalization.md +595 -0
  138. package/docs/features/performance/README.md +77 -0
  139. package/docs/features/performance/bundle-size.md +134 -0
  140. package/docs/features/performance/caching.md +131 -0
  141. package/docs/features/performance/code-splitting.md +121 -0
  142. package/docs/features/performance/image-optimization.md +110 -0
  143. package/docs/features/performance/lazy-loading.md +92 -0
  144. package/docs/features/performance/monitoring.md +148 -0
  145. package/docs/features/seo/README.md +51 -0
  146. package/docs/features/seo/best-practices.md +184 -0
  147. package/docs/features/seo/canonical-urls.md +182 -0
  148. package/docs/features/seo/meta-tags.md +126 -0
  149. package/docs/features/seo/open-graph.md +166 -0
  150. package/docs/features/seo/robots-txt.md +146 -0
  151. package/docs/features/seo/sitemaps.md +162 -0
  152. package/docs/features/seo/structured-data.md +166 -0
  153. package/docs/getting-started/installation.md +292 -0
  154. package/docs/getting-started/introduction.md +195 -0
  155. package/docs/getting-started/quick-start.md +460 -0
  156. package/docs/guides/analytics-setup.md +616 -0
  157. package/docs/i18n/CONFIGURATION.md +353 -0
  158. package/docs/i18n/EXAMPLES.md +402 -0
  159. package/docs/i18n/MIGRATION.md +260 -0
  160. package/docs/i18n/SEO.md +392 -0
  161. package/docs/i18n/STATIC-GENERATION-FIX.md +71 -0
  162. package/docs/migration/changelog.md +136 -0
  163. package/docs/migration/overview.md +233 -0
  164. package/docs/recipes/adding-animations.md +475 -0
  165. package/docs/recipes/forms-with-validation.md +393 -0
  166. package/package.json +152 -0
@@ -0,0 +1,664 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * simple-site CLI tool
5
+ * Add components, configure settings, and manage your project
6
+ */
7
+
8
+ const { Command } = require('commander');
9
+ const inquirer = require('inquirer');
10
+ const chalk = require('chalk');
11
+ const ora = require('ora');
12
+ const path = require('path');
13
+ const fs = require('fs-extra');
14
+
15
+ const program = new Command();
16
+
17
+ program
18
+ .name('simple-site')
19
+ .description('Simple Site Framework CLI')
20
+ .version('0.1.0');
21
+
22
+ // Add component command
23
+ program
24
+ .command('add <component>')
25
+ .description('Add a component to your project')
26
+ .action(async (component) => {
27
+ try {
28
+ await addComponent(component);
29
+ } catch (error) {
30
+ console.error(chalk.red('\n✖ Error:'), error.message);
31
+ process.exit(1);
32
+ }
33
+ });
34
+
35
+ // Add section command
36
+ program
37
+ .command('section <name>')
38
+ .alias('add-section')
39
+ .description('Add a new section to your project')
40
+ .action(async (name) => {
41
+ try {
42
+ await addSection(name);
43
+ } catch (error) {
44
+ console.error(chalk.red('\n✖ Error:'), error.message);
45
+ process.exit(1);
46
+ }
47
+ });
48
+
49
+ // Add page command
50
+ program
51
+ .command('page <path>')
52
+ .alias('add-page')
53
+ .description('Add a new page to your project')
54
+ .action(async (pagePath) => {
55
+ try {
56
+ await addPage(pagePath);
57
+ } catch (error) {
58
+ console.error(chalk.red('\n✖ Error:'), error.message);
59
+ process.exit(1);
60
+ }
61
+ });
62
+
63
+ // List components command
64
+ program
65
+ .command('list')
66
+ .description('List available components')
67
+ .action(() => {
68
+ listComponents();
69
+ });
70
+
71
+ // Search components command
72
+ program
73
+ .command('search <query>')
74
+ .description('Search for components')
75
+ .action((query) => {
76
+ searchComponents(query);
77
+ });
78
+
79
+ // Configuration wizard
80
+ program
81
+ .command('config')
82
+ .description('Interactive configuration wizard')
83
+ .action(async () => {
84
+ try {
85
+ await configWizard();
86
+ } catch (error) {
87
+ console.error(chalk.red('\n✖ Error:'), error.message);
88
+ process.exit(1);
89
+ }
90
+ });
91
+
92
+ // Migration command
93
+ program
94
+ .command('migrate')
95
+ .description('Run migration tasks (e.g., migrate i18n)')
96
+ .argument('[task]', 'Migration task to run (i18n)')
97
+ .option('--dry-run', 'Preview changes without writing files')
98
+ .action(async (task, options) => {
99
+ try {
100
+ if (!task || task === 'i18n') {
101
+ await migrateI18n(options);
102
+ } else {
103
+ console.log(chalk.yellow(`\n⚠ Unknown migration task: ${task}`));
104
+ console.log(chalk.gray('Available migrations: i18n\n'));
105
+ }
106
+ } catch (error) {
107
+ console.error(chalk.red('\n✖ Error:'), error.message);
108
+ process.exit(1);
109
+ }
110
+ });
111
+
112
+ program.parse();
113
+
114
+ // Component registry
115
+ const COMPONENTS = {
116
+ // Layout
117
+ 'hero': { category: 'Layout', name: 'HeroSection', description: 'Hero section with CTA' },
118
+ 'features': { category: 'Layout', name: 'FeaturesGrid', description: 'Features grid display' },
119
+ 'footer': { category: 'Layout', name: 'Footer', description: 'Site footer' },
120
+ 'header': { category: 'Layout', name: 'Header', description: 'Site header with navigation' },
121
+
122
+ // Conversion
123
+ 'pricing': { category: 'Conversion', name: 'PricingTable', description: 'Pricing comparison table' },
124
+ 'testimonials': { category: 'Conversion', name: 'TestimonialCarousel', description: 'Customer testimonials carousel' },
125
+ 'stats': { category: 'Conversion', name: 'StatsSection', description: 'Statistics with animated counters' },
126
+ 'trust-badges': { category: 'Conversion', name: 'TrustBadges', description: 'Trust and credibility badges' },
127
+ 'countdown': { category: 'Conversion', name: 'CountdownTimer', description: 'Countdown timer for urgency' },
128
+ 'exit-intent': { category: 'Conversion', name: 'ExitIntentModal', description: 'Exit intent modal' },
129
+
130
+ // Content
131
+ 'faq': { category: 'Content', name: 'FAQAccordion', description: 'FAQ accordion' },
132
+ 'blog-card': { category: 'Content', name: 'BlogCard', description: 'Blog post preview card' },
133
+ 'timeline': { category: 'Content', name: 'Timeline', description: 'Timeline for history/process' },
134
+ 'comparison': { category: 'Content', name: 'ComparisonTable', description: 'Feature comparison table' },
135
+ 'tabs': { category: 'Content', name: 'Tabs', description: 'Tabbed content with URL sync' },
136
+
137
+ // Forms
138
+ 'contact-form': { category: 'Forms', name: 'ContactSection', description: 'Contact form section' },
139
+ 'multi-step-form': { category: 'Forms', name: 'MultiStepForm', description: 'Multi-step form with validation' },
140
+ 'file-upload': { category: 'Forms', name: 'FileUpload', description: 'File upload with drag & drop' },
141
+ 'select': { category: 'Forms', name: 'Select', description: 'Dropdown select' },
142
+ 'checkbox': { category: 'Forms', name: 'Checkbox', description: 'Checkbox input' },
143
+ 'radio': { category: 'Forms', name: 'Radio', description: 'Radio button input' },
144
+
145
+ // UI
146
+ 'button': { category: 'UI', name: 'Button', description: 'Button component' },
147
+ 'modal': { category: 'UI', name: 'Modal', description: 'Modal dialog' },
148
+ 'toast': { category: 'UI', name: 'Toast', description: 'Toast notifications' },
149
+ 'loading': { category: 'UI', name: 'LoadingSpinner', description: 'Loading spinner' },
150
+ 'skeleton': { category: 'UI', name: 'Skeleton', description: 'Skeleton loader' }
151
+ };
152
+
153
+ async function addComponent(componentKey) {
154
+ const component = COMPONENTS[componentKey.toLowerCase()];
155
+
156
+ if (!component) {
157
+ console.log(chalk.yellow(`\n⚠ Component "${componentKey}" not found.\n`));
158
+ console.log(chalk.gray('Run `simple-site list` to see available components'));
159
+ return;
160
+ }
161
+
162
+ const spinner = ora(`Adding ${component.name}...`).start();
163
+
164
+ try {
165
+ // Check if we're in a project directory
166
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
167
+ if (!fs.existsSync(packageJsonPath)) {
168
+ spinner.fail('Not in a project directory');
169
+ console.log(chalk.yellow('\nRun this command from your project root directory'));
170
+ return;
171
+ }
172
+
173
+ // Create component usage example
174
+ const examplePath = path.join(process.cwd(), 'examples', `${componentKey}-example.tsx`);
175
+ fs.ensureDirSync(path.dirname(examplePath));
176
+
177
+ const exampleCode = generateComponentExample(component.name, componentKey);
178
+ fs.writeFileSync(examplePath, exampleCode);
179
+
180
+ spinner.succeed(`Added ${component.name}!`);
181
+ console.log(chalk.green(`\n✓ Example created: examples/${componentKey}-example.tsx`));
182
+ console.log(chalk.gray(`\nImport with: import { ${component.name} } from '@zoyth/simple-site-framework/components'\n`));
183
+
184
+ } catch (error) {
185
+ spinner.fail('Failed to add component');
186
+ throw error;
187
+ }
188
+ }
189
+
190
+ function generateComponentExample(componentName, key) {
191
+ const examples = {
192
+ 'HeroSection': `import { HeroSection } from '@zoyth/simple-site-framework/components'
193
+
194
+ export function HeroExample() {
195
+ return (
196
+ <HeroSection
197
+ title={{ en: "Welcome to Our Site", fr: "Bienvenue sur notre site" }}
198
+ subtitle="Build amazing websites with Simple Site Framework"
199
+ primaryCTA={{
200
+ label: "Get Started",
201
+ href: "/contact"
202
+ }}
203
+ locale="en"
204
+ />
205
+ )
206
+ }`,
207
+ 'PricingTable': `import { PricingTable } from '@zoyth/simple-site-framework/components'
208
+
209
+ export function PricingExample() {
210
+ return (
211
+ <PricingTable
212
+ tiers={[
213
+ {
214
+ name: { en: "Basic", fr: "Basique" },
215
+ price: { monthly: 29, annual: 290 },
216
+ features: [
217
+ { name: "Feature 1", included: true },
218
+ { name: "Feature 2", included: true },
219
+ { name: "Feature 3", included: false }
220
+ ],
221
+ cta: { label: "Get Started", href: "/signup" }
222
+ }
223
+ ]}
224
+ locale="en"
225
+ />
226
+ )
227
+ }`
228
+ };
229
+
230
+ return examples[componentName] || `import { ${componentName} } from '@zoyth/simple-site-framework/components'
231
+
232
+ export function ${componentName}Example() {
233
+ return (
234
+ <${componentName} />
235
+ )
236
+ }`;
237
+ }
238
+
239
+ async function addSection(name) {
240
+ const spinner = ora(`Creating section ${name}...`).start();
241
+
242
+ try {
243
+ const sectionPath = path.join(process.cwd(), 'app', 'sections', `${name}.tsx`);
244
+ fs.ensureDirSync(path.dirname(sectionPath));
245
+
246
+ const sectionCode = `export function ${capitalize(name)}Section() {
247
+ return (
248
+ <section className="py-16">
249
+ <div className="container mx-auto px-4">
250
+ <h2 className="text-3xl font-bold mb-8">${capitalize(name)}</h2>
251
+ {/* Add your content here */}
252
+ </div>
253
+ </section>
254
+ )
255
+ }`;
256
+
257
+ fs.writeFileSync(sectionPath, sectionCode);
258
+
259
+ spinner.succeed(`Created ${name} section!`);
260
+ console.log(chalk.green(`\n✓ Section created: app/sections/${name}.tsx\n`));
261
+
262
+ } catch (error) {
263
+ spinner.fail('Failed to create section');
264
+ throw error;
265
+ }
266
+ }
267
+
268
+ async function addPage(pagePath) {
269
+ const spinner = ora(`Creating page ${pagePath}...`).start();
270
+
271
+ try {
272
+ const fullPath = path.join(process.cwd(), 'app', pagePath, 'page.tsx');
273
+ fs.ensureDirSync(path.dirname(fullPath));
274
+
275
+ const pageCode = `export default function ${capitalize(path.basename(pagePath))}Page() {
276
+ return (
277
+ <main className="min-h-screen">
278
+ <div className="container mx-auto px-4 py-16">
279
+ <h1 className="text-4xl font-bold mb-8">${capitalize(path.basename(pagePath))}</h1>
280
+ {/* Add your content here */}
281
+ </div>
282
+ </main>
283
+ )
284
+ }`;
285
+
286
+ fs.writeFileSync(fullPath, pageCode);
287
+
288
+ spinner.succeed(`Created page!`);
289
+ console.log(chalk.green(`\n✓ Page created: app/${pagePath}/page.tsx\n`));
290
+
291
+ } catch (error) {
292
+ spinner.fail('Failed to create page');
293
+ throw error;
294
+ }
295
+ }
296
+
297
+ function listComponents() {
298
+ console.log(chalk.bold.cyan('\n📦 Available Components\n'));
299
+
300
+ const byCategory = {};
301
+ for (const [key, component] of Object.entries(COMPONENTS)) {
302
+ if (!byCategory[component.category]) {
303
+ byCategory[component.category] = [];
304
+ }
305
+ byCategory[component.category].push({ key, ...component });
306
+ }
307
+
308
+ for (const [category, components] of Object.entries(byCategory)) {
309
+ console.log(chalk.bold(`\n${category}:`));
310
+ components.forEach(comp => {
311
+ console.log(chalk.gray(` ${comp.key.padEnd(20)} ${comp.description}`));
312
+ });
313
+ }
314
+
315
+ console.log(chalk.gray(`\nUse: simple-site add <component>`));
316
+ console.log(chalk.gray(`Example: simple-site add pricing\n`));
317
+ }
318
+
319
+ function searchComponents(query) {
320
+ const results = Object.entries(COMPONENTS).filter(([key, comp]) =>
321
+ key.includes(query.toLowerCase()) ||
322
+ comp.name.toLowerCase().includes(query.toLowerCase()) ||
323
+ comp.description.toLowerCase().includes(query.toLowerCase())
324
+ );
325
+
326
+ if (results.length === 0) {
327
+ console.log(chalk.yellow(`\n⚠ No components found matching "${query}"\n`));
328
+ return;
329
+ }
330
+
331
+ console.log(chalk.bold.cyan(`\n🔍 Search Results for "${query}":\n`));
332
+ results.forEach(([key, comp]) => {
333
+ console.log(chalk.bold(` ${key}`));
334
+ console.log(chalk.gray(` ${comp.description}`));
335
+ console.log(chalk.gray(` Category: ${comp.category}\n`));
336
+ });
337
+ }
338
+
339
+ async function configWizard() {
340
+ console.log(chalk.bold.cyan('\n⚙️ Configuration Wizard\n'));
341
+
342
+ const configPath = path.join(process.cwd(), 'config', 'theme.ts');
343
+
344
+ if (!fs.existsSync(configPath)) {
345
+ console.log(chalk.yellow('⚠ No config/theme.ts found'));
346
+ console.log(chalk.gray('Make sure you\'re in a Simple Site project directory\n'));
347
+ return;
348
+ }
349
+
350
+ const answers = await inquirer.prompt([
351
+ {
352
+ type: 'list',
353
+ name: 'action',
354
+ message: 'What would you like to configure?',
355
+ choices: [
356
+ 'Update theme colors',
357
+ 'Change fonts',
358
+ 'Modify company info',
359
+ 'Exit'
360
+ ]
361
+ }
362
+ ]);
363
+
364
+ if (answers.action === 'Exit') {
365
+ return;
366
+ }
367
+
368
+ if (answers.action === 'Update theme colors') {
369
+ const colorAnswers = await inquirer.prompt([
370
+ {
371
+ type: 'input',
372
+ name: 'primary',
373
+ message: 'Primary color (hex):',
374
+ validate: (input) => /^#[0-9A-F]{6}$/i.test(input) || 'Please enter a valid hex color'
375
+ }
376
+ ]);
377
+
378
+ const spinner = ora('Updating theme...').start();
379
+
380
+ try {
381
+ let themeContent = fs.readFileSync(configPath, 'utf-8');
382
+ themeContent = themeContent.replace(
383
+ /(primary:\s*['"])#[0-9A-F]{6}/gi,
384
+ `$1${colorAnswers.primary}`
385
+ );
386
+ fs.writeFileSync(configPath, themeContent);
387
+ spinner.succeed('Theme updated!');
388
+ } catch (error) {
389
+ spinner.fail('Failed to update theme');
390
+ throw error;
391
+ }
392
+ }
393
+
394
+ console.log(chalk.green('\n✓ Configuration complete!\n'));
395
+ }
396
+
397
+ async function migrateI18n(options = {}) {
398
+ const isDryRun = options.dryRun;
399
+
400
+ console.log(chalk.bold.cyan('\n🌐 i18n Migration Tool\n'));
401
+
402
+ if (isDryRun) {
403
+ console.log(chalk.yellow('Running in DRY RUN mode - no files will be modified\n'));
404
+ }
405
+
406
+ // Check if we're in a project directory
407
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
408
+ if (!fs.existsSync(packageJsonPath)) {
409
+ console.log(chalk.red('✖ Not in a project directory'));
410
+ console.log(chalk.gray('Run this command from your project root directory\n'));
411
+ return;
412
+ }
413
+
414
+ // Check if i18n config already exists
415
+ const i18nConfigPath = path.join(process.cwd(), 'src', 'config', 'i18n.ts');
416
+ if (fs.existsSync(i18nConfigPath) && !isDryRun) {
417
+ const { overwrite } = await inquirer.prompt([
418
+ {
419
+ type: 'confirm',
420
+ name: 'overwrite',
421
+ message: 'i18n configuration already exists. Overwrite?',
422
+ default: false
423
+ }
424
+ ]);
425
+
426
+ if (!overwrite) {
427
+ console.log(chalk.yellow('\n⚠ Migration cancelled\n'));
428
+ return;
429
+ }
430
+ }
431
+
432
+ // Prompt for configuration
433
+ const answers = await inquirer.prompt([
434
+ {
435
+ type: 'checkbox',
436
+ name: 'locales',
437
+ message: 'Select languages to support:',
438
+ choices: [
439
+ { name: 'English (en)', value: 'en', checked: true },
440
+ { name: 'French (fr)', value: 'fr', checked: true },
441
+ { name: 'Spanish (es)', value: 'es' },
442
+ { name: 'German (de)', value: 'de' },
443
+ { name: 'Italian (it)', value: 'it' },
444
+ { name: 'Portuguese (pt)', value: 'pt' },
445
+ { name: 'Japanese (ja)', value: 'ja' },
446
+ { name: 'Chinese (zh)', value: 'zh' },
447
+ { name: 'Arabic (ar)', value: 'ar' },
448
+ { name: 'Hebrew (he)', value: 'he' }
449
+ ],
450
+ validate: (input) => input.length > 0 || 'Select at least one language'
451
+ },
452
+ {
453
+ type: 'list',
454
+ name: 'defaultLocale',
455
+ message: 'Select default language:',
456
+ choices: (answers) => answers.locales
457
+ },
458
+ {
459
+ type: 'list',
460
+ name: 'localePrefix',
461
+ message: 'Choose URL prefix mode:',
462
+ choices: [
463
+ {
464
+ name: 'as-needed (default locale has no prefix) - Recommended',
465
+ value: 'as-needed'
466
+ },
467
+ {
468
+ name: 'always (all locales have prefix)',
469
+ value: 'always'
470
+ },
471
+ {
472
+ name: 'never (no prefixes, cookie/header only)',
473
+ value: 'never'
474
+ }
475
+ ],
476
+ default: 'as-needed'
477
+ },
478
+ {
479
+ type: 'confirm',
480
+ name: 'localeDetection',
481
+ message: 'Enable automatic browser language detection?',
482
+ default: true
483
+ }
484
+ ]);
485
+
486
+ // Check if RTL locales were selected
487
+ const rtlLocales = answers.locales.filter(locale => ['ar', 'he', 'fa', 'ur'].includes(locale));
488
+
489
+ const spinner = ora('Creating i18n configuration...').start();
490
+
491
+ try {
492
+ const files = [];
493
+
494
+ // 1. Create i18n configuration file
495
+ const i18nConfig = generateI18nConfig(answers, rtlLocales);
496
+ if (!isDryRun) {
497
+ fs.ensureDirSync(path.dirname(i18nConfigPath));
498
+ fs.writeFileSync(i18nConfigPath, i18nConfig);
499
+ }
500
+ files.push({ path: 'src/config/i18n.ts', status: 'created' });
501
+
502
+ // 2. Create middleware file
503
+ const middlewarePath = path.join(process.cwd(), 'src', 'middleware.ts');
504
+ const middlewareExists = fs.existsSync(middlewarePath);
505
+
506
+ if (middlewareExists) {
507
+ files.push({ path: 'src/middleware.ts', status: 'already exists - please update manually' });
508
+ } else {
509
+ const middlewareCode = generateMiddleware();
510
+ if (!isDryRun) {
511
+ fs.writeFileSync(middlewarePath, middlewareCode);
512
+ }
513
+ files.push({ path: 'src/middleware.ts', status: 'created' });
514
+ }
515
+
516
+ // 3. Check layout file
517
+ const layoutPath = path.join(process.cwd(), 'src', 'app', '[locale]', 'layout.tsx');
518
+ if (fs.existsSync(layoutPath)) {
519
+ files.push({
520
+ path: 'src/app/[locale]/layout.tsx',
521
+ status: 'exists - add setI18nConfig() call manually'
522
+ });
523
+ } else {
524
+ files.push({
525
+ path: 'src/app/[locale]/layout.tsx',
526
+ status: 'not found - create [locale] route directory'
527
+ });
528
+ }
529
+
530
+ spinner.succeed('i18n configuration created!');
531
+
532
+ // Show summary
533
+ console.log(chalk.bold.green('\n✓ Migration complete!\n'));
534
+ console.log(chalk.bold('Files:\n'));
535
+ files.forEach(file => {
536
+ const icon = file.status.includes('created') ? '✓' :
537
+ file.status.includes('exists') ? '●' : '○';
538
+ const color = file.status.includes('created') ? chalk.green :
539
+ file.status.includes('exists') ? chalk.yellow :
540
+ chalk.gray;
541
+ console.log(color(` ${icon} ${file.path}`));
542
+ if (!file.status.includes('created')) {
543
+ console.log(chalk.gray(` ${file.status}`));
544
+ }
545
+ });
546
+
547
+ // Show next steps
548
+ console.log(chalk.bold('\nNext steps:\n'));
549
+ console.log(chalk.gray('1. Review generated files in src/config/i18n.ts'));
550
+
551
+ if (middlewareExists) {
552
+ console.log(chalk.gray('2. Update src/middleware.ts with:'));
553
+ console.log(chalk.gray(' import { createI18nMiddleware } from \'simple-site-framework/lib/i18n\';'));
554
+ console.log(chalk.gray(' import { i18nConfig } from \'./src/config/i18n\';'));
555
+ console.log(chalk.gray(' export default createI18nMiddleware(i18nConfig);'));
556
+ } else {
557
+ console.log(chalk.gray('2. Middleware created automatically ✓'));
558
+ }
559
+
560
+ console.log(chalk.gray('3. Add to src/app/[locale]/layout.tsx:'));
561
+ console.log(chalk.gray(' import { setI18nConfig } from \'simple-site-framework/lib/i18n\';'));
562
+ console.log(chalk.gray(' import { i18nConfig } from \'@/config/i18n\';'));
563
+ console.log(chalk.gray(' setI18nConfig(i18nConfig); // At top level'));
564
+ console.log(chalk.gray('4. See docs/i18n/MIGRATION.md for complete migration guide'));
565
+ console.log('');
566
+
567
+ } catch (error) {
568
+ spinner.fail('Migration failed');
569
+ throw error;
570
+ }
571
+ }
572
+
573
+ function generateI18nConfig(answers, rtlLocales) {
574
+ const { locales, defaultLocale, localePrefix, localeDetection } = answers;
575
+
576
+ // Generate locale names
577
+ const localeNameMap = {
578
+ en: 'English',
579
+ fr: 'Français',
580
+ es: 'Español',
581
+ de: 'Deutsch',
582
+ it: 'Italiano',
583
+ pt: 'Português',
584
+ ja: '日本語',
585
+ zh: '中文',
586
+ ar: 'العربية',
587
+ he: 'עברית'
588
+ };
589
+
590
+ const localeNames = locales.reduce((acc, locale) => {
591
+ acc[locale] = localeNameMap[locale] || locale.toUpperCase();
592
+ return acc;
593
+ }, {});
594
+
595
+ const localeLabels = locales.reduce((acc, locale) => {
596
+ acc[locale] = locale.toUpperCase();
597
+ return acc;
598
+ }, {});
599
+
600
+ const localesArray = JSON.stringify(locales, null, 2).replace(/"/g, "'");
601
+ const localeNamesStr = JSON.stringify(localeNames, null, 4).replace(/"/g, "'");
602
+ const localeLabelsStr = JSON.stringify(localeLabels, null, 4).replace(/"/g, "'");
603
+
604
+ let configContent = `// ABOUTME: i18n configuration
605
+ // ABOUTME: Internationalization settings for the application
606
+
607
+ import type { I18nConfig } from 'simple-site-framework/lib/i18n';
608
+
609
+ export const i18nConfig: I18nConfig = {
610
+ // Supported languages
611
+ locales: ${localesArray},
612
+
613
+ // Default language
614
+ defaultLocale: '${defaultLocale}',
615
+
616
+ // Locale prefix mode:
617
+ // 'as-needed' - default locale has no prefix, others do (recommended)
618
+ // 'always' - all URLs have locale prefix
619
+ // 'never' - no locale prefixes (cookie/header detection only)
620
+ localePrefix: '${localePrefix}',
621
+
622
+ // Enable browser language detection
623
+ localeDetection: ${localeDetection},
624
+
625
+ // Display names for language selector
626
+ localeNames: ${localeNamesStr},
627
+
628
+ // Short labels for compact display
629
+ localeLabels: ${localeLabelsStr},`;
630
+
631
+ if (rtlLocales.length > 0) {
632
+ const rtlArray = JSON.stringify(rtlLocales, null, 2).replace(/"/g, "'");
633
+ configContent += `
634
+
635
+ // Right-to-left languages
636
+ rtlLocales: ${rtlArray},`;
637
+ }
638
+
639
+ configContent += `
640
+ };
641
+ `;
642
+
643
+ return configContent;
644
+ }
645
+
646
+ function generateMiddleware() {
647
+ return `// ABOUTME: Middleware configuration
648
+ // ABOUTME: Handles i18n routing and locale detection
649
+
650
+ import { createI18nMiddleware } from 'simple-site-framework/lib/i18n';
651
+ import { i18nConfig } from './src/config/i18n';
652
+
653
+ export default createI18nMiddleware(i18nConfig);
654
+
655
+ export const config = {
656
+ // Exclude API routes, static assets, etc.
657
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
658
+ };
659
+ `;
660
+ }
661
+
662
+ function capitalize(str) {
663
+ return str.charAt(0).toUpperCase() + str.slice(1);
664
+ }