@voltro/cli 0.1.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/CHANGELOG.md +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +20236 -0
- package/bin/voltro.mjs +43 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +9 -0
- package/dist/commands-CfPH2Wf4.js +18061 -0
- package/dist/frameworkInspectState-CX2250XB.js +86 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +5 -0
- package/dist/inspectState.d.ts +116 -0
- package/dist/inspectState.js +2 -0
- package/dist/startup.d.ts +26 -0
- package/dist/startup.js +2 -0
- package/dist/startupRunner-CRhuUl91.js +71 -0
- package/package.json +88 -0
- package/templates/AGENTS.core.md +258 -0
- package/templates/AGENTS.md +351 -0
- package/templates/agent-docs/_index.md +93 -0
- package/templates/agent-docs/_manifest.json +655 -0
- package/templates/agent-docs/ai.md +1845 -0
- package/templates/agent-docs/authentication.md +1788 -0
- package/templates/agent-docs/caching.md +624 -0
- package/templates/agent-docs/cli.md +1650 -0
- package/templates/agent-docs/configuration.md +295 -0
- package/templates/agent-docs/data.md +2172 -0
- package/templates/agent-docs/database/advancedqueries.md +1583 -0
- package/templates/agent-docs/database/columntypes.md +1200 -0
- package/templates/agent-docs/database/hosting.md +881 -0
- package/templates/agent-docs/database/migrations.md +2938 -0
- package/templates/agent-docs/database/misc.md +270 -0
- package/templates/agent-docs/database/overview.md +108 -0
- package/templates/agent-docs/database/querying.md +1622 -0
- package/templates/agent-docs/database/scaling.md +331 -0
- package/templates/agent-docs/database/schema.md +1458 -0
- package/templates/agent-docs/database/seedsdialects.md +1235 -0
- package/templates/agent-docs/database/transactions.md +285 -0
- package/templates/agent-docs/deployment.md +999 -0
- package/templates/agent-docs/internationalization.md +359 -0
- package/templates/agent-docs/introduction.md +438 -0
- package/templates/agent-docs/multi-tenancy.md +610 -0
- package/templates/agent-docs/observability.md +350 -0
- package/templates/agent-docs/plugins.md +1273 -0
- package/templates/agent-docs/reference.md +978 -0
- package/templates/agent-docs/routing.md +1553 -0
- package/templates/agent-docs/scheduling.md +666 -0
- package/templates/agent-docs/schema-driven-ui.md +607 -0
- package/templates/agent-docs/security.md +42 -0
- package/templates/agent-docs/templates/apibackends.md +3214 -0
- package/templates/agent-docs/templates/appshells.md +2062 -0
- package/templates/agent-docs/templates/custom.md +128 -0
- package/templates/agent-docs/templates/overview.md +122 -0
- package/templates/agent-docs/templates/serverless.md +315 -0
- package/templates/agent-docs/testing.md +376 -0
- package/templates/agent-docs/workflows.md +1351 -0
- package/templates/apps/api-ai/README.md +105 -0
- package/templates/apps/api-ai/actions/summarize.action.server.tsx +32 -0
- package/templates/apps/api-ai/actions/summarize.action.ts +33 -0
- package/templates/apps/api-ai/agents/support.agent.server.tsx +31 -0
- package/templates/apps/api-ai/agents/support.agent.tsx +27 -0
- package/templates/apps/api-ai/app.config.ts +57 -0
- package/templates/apps/api-ai/database/schema.ts +67 -0
- package/templates/apps/api-ai/package.json +27 -0
- package/templates/apps/api-ai/seeds/docs.seed.ts +67 -0
- package/templates/apps/api-ai/template.json +6 -0
- package/templates/apps/api-ai/tests/summarize.test.ts +50 -0
- package/templates/apps/api-ai/tools/searchDocs.tool.tsx +60 -0
- package/templates/apps/api-ai/tsconfig.json +5 -0
- package/templates/apps/api-auth/.env +17 -0
- package/templates/apps/api-auth/README.md +109 -0
- package/templates/apps/api-auth/actions/me.action.server.ts +20 -0
- package/templates/apps/api-auth/actions/me.action.ts +19 -0
- package/templates/apps/api-auth/app.config.ts +59 -0
- package/templates/apps/api-auth/database/schema.ts +26 -0
- package/templates/apps/api-auth/package.json +28 -0
- package/templates/apps/api-auth/template.json +6 -0
- package/templates/apps/api-auth/tests/me.test.ts +37 -0
- package/templates/apps/api-auth/tsconfig.json +5 -0
- package/templates/apps/api-backend/README.md +25 -0
- package/templates/apps/api-backend/app.config.ts +39 -0
- package/templates/apps/api-backend/database/schema.ts +57 -0
- package/templates/apps/api-backend/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend/package.json +28 -0
- package/templates/apps/api-backend/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend/template.json +6 -0
- package/templates/apps/api-backend/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend/tsconfig.json +5 -0
- package/templates/apps/api-backend-deactivation/README.md +49 -0
- package/templates/apps/api-backend-deactivation/actions/users.get.action.server.ts +17 -0
- package/templates/apps/api-backend-deactivation/actions/users.get.action.ts +18 -0
- package/templates/apps/api-backend-deactivation/app.config.ts +20 -0
- package/templates/apps/api-backend-deactivation/database/schema.ts +41 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.server.ts +6 -0
- package/templates/apps/api-backend-deactivation/mutations/users.create.mutation.ts +16 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.server.ts +10 -0
- package/templates/apps/api-backend-deactivation/mutations/users.deactivate.mutation.ts +19 -0
- package/templates/apps/api-backend-deactivation/package.json +28 -0
- package/templates/apps/api-backend-deactivation/template.json +6 -0
- package/templates/apps/api-backend-deactivation/tests/users.deactivate.test.ts +47 -0
- package/templates/apps/api-backend-deactivation/tsconfig.json +5 -0
- package/templates/apps/api-backend-mail/README.md +39 -0
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.server.ts +15 -0
- package/templates/apps/api-backend-mail/actions/sendWelcome.action.ts +21 -0
- package/templates/apps/api-backend-mail/app.config.ts +34 -0
- package/templates/apps/api-backend-mail/database/schema.ts +57 -0
- package/templates/apps/api-backend-mail/emails/welcome.email.tsx +106 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-mail/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-mail/package.json +30 -0
- package/templates/apps/api-backend-mail/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-mail/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-mail/template.json +6 -0
- package/templates/apps/api-backend-mail/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-mail/tsconfig.json +5 -0
- package/templates/apps/api-backend-mariadb/.env.example +35 -0
- package/templates/apps/api-backend-mariadb/README.md +31 -0
- package/templates/apps/api-backend-mariadb/app.config.ts +68 -0
- package/templates/apps/api-backend-mariadb/database/schema.ts +57 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-mariadb/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-mariadb/package.json +30 -0
- package/templates/apps/api-backend-mariadb/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-mariadb/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-mariadb/template.json +6 -0
- package/templates/apps/api-backend-mariadb/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-mariadb/tsconfig.json +5 -0
- package/templates/apps/api-backend-storage/README.md +86 -0
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.server.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadAvatar.action.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.server.ts +21 -0
- package/templates/apps/api-backend-storage/actions/uploadDocument.action.ts +20 -0
- package/templates/apps/api-backend-storage/app.config.ts +38 -0
- package/templates/apps/api-backend-storage/database/schema.ts +57 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.server.ts +19 -0
- package/templates/apps/api-backend-storage/mutations/notes.create.mutation.ts +37 -0
- package/templates/apps/api-backend-storage/package.json +27 -0
- package/templates/apps/api-backend-storage/queries/notes.query.server.ts +14 -0
- package/templates/apps/api-backend-storage/queries/notes.query.ts +20 -0
- package/templates/apps/api-backend-storage/template.json +6 -0
- package/templates/apps/api-backend-storage/tests/notes.create.test.ts +50 -0
- package/templates/apps/api-backend-storage/tsconfig.json +5 -0
- package/templates/apps/api-data-advanced/.env +17 -0
- package/templates/apps/api-data-advanced/README.md +88 -0
- package/templates/apps/api-data-advanced/app.config.ts +56 -0
- package/templates/apps/api-data-advanced/database/actors.entity.ts +14 -0
- package/templates/apps/api-data-advanced/database/authors.entity.ts +30 -0
- package/templates/apps/api-data-advanced/database/authors.relations.ts +15 -0
- package/templates/apps/api-data-advanced/database/books.entity.ts +59 -0
- package/templates/apps/api-data-advanced/database/books.relations.ts +10 -0
- package/templates/apps/api-data-advanced/database/index.ts +23 -0
- package/templates/apps/api-data-advanced/database/tenants.entity.ts +11 -0
- package/templates/apps/api-data-advanced/package.json +28 -0
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.server.ts +18 -0
- package/templates/apps/api-data-advanced/queries/authors.withBooks.query.ts +40 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.server.ts +17 -0
- package/templates/apps/api-data-advanced/queries/books.search.query.ts +39 -0
- package/templates/apps/api-data-advanced/seeds/catalog.seed.ts +101 -0
- package/templates/apps/api-data-advanced/template.json +6 -0
- package/templates/apps/api-data-advanced/tests/queries.test.ts +124 -0
- package/templates/apps/api-data-advanced/tsconfig.json +5 -0
- package/templates/apps/api-durable/README.md +81 -0
- package/templates/apps/api-durable/aggregates/orderStats.aggregate.ts +49 -0
- package/templates/apps/api-durable/app.config.ts +41 -0
- package/templates/apps/api-durable/database/schema.ts +65 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.server.ts +43 -0
- package/templates/apps/api-durable/mutations/orders.approve.mutation.ts +23 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.server.ts +45 -0
- package/templates/apps/api-durable/mutations/orders.place.mutation.ts +40 -0
- package/templates/apps/api-durable/package.json +28 -0
- package/templates/apps/api-durable/schedules/nightlyReport.cron.tsx +37 -0
- package/templates/apps/api-durable/startup/warm.startup.tsx +33 -0
- package/templates/apps/api-durable/subscribers/orderChanges.subscribe.ts +27 -0
- package/templates/apps/api-durable/template.json +6 -0
- package/templates/apps/api-durable/tests/orders.place.test.ts +91 -0
- package/templates/apps/api-durable/triggers/order.placed.trigger.tsx +21 -0
- package/templates/apps/api-durable/tsconfig.json +5 -0
- package/templates/apps/api-durable/workflows/order.fulfill.workflow.server.tsx +103 -0
- package/templates/apps/api-durable/workflows/order.fulfill.workflow.tsx +25 -0
- package/templates/apps/api-feature-flags/README.md +63 -0
- package/templates/apps/api-feature-flags/actions/notes.export.action.server.ts +11 -0
- package/templates/apps/api-feature-flags/actions/notes.export.action.ts +18 -0
- package/templates/apps/api-feature-flags/app.config.ts +41 -0
- package/templates/apps/api-feature-flags/database/schema.ts +37 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.server.ts +29 -0
- package/templates/apps/api-feature-flags/mutations/notes.create.mutation.ts +28 -0
- package/templates/apps/api-feature-flags/package.json +29 -0
- package/templates/apps/api-feature-flags/template.json +6 -0
- package/templates/apps/api-feature-flags/tests/notes.create.test.ts +67 -0
- package/templates/apps/api-feature-flags/tsconfig.json +5 -0
- package/templates/apps/api-governance/.env +4 -0
- package/templates/apps/api-governance/README.md +61 -0
- package/templates/apps/api-governance/actions/profiles.get.action.server.ts +17 -0
- package/templates/apps/api-governance/actions/profiles.get.action.ts +17 -0
- package/templates/apps/api-governance/app.config.ts +35 -0
- package/templates/apps/api-governance/database/schema.ts +41 -0
- package/templates/apps/api-governance/mutations/profiles.create.mutation.server.ts +17 -0
- package/templates/apps/api-governance/mutations/profiles.create.mutation.ts +21 -0
- package/templates/apps/api-governance/package.json +29 -0
- package/templates/apps/api-governance/template.json +6 -0
- package/templates/apps/api-governance/tests/profiles.create.test.ts +59 -0
- package/templates/apps/api-governance/tsconfig.json +5 -0
- package/templates/apps/api-kv/README.md +100 -0
- package/templates/apps/api-kv/actions/sync.pull.action.server.ts +75 -0
- package/templates/apps/api-kv/actions/sync.pull.action.ts +25 -0
- package/templates/apps/api-kv/actions/sync.reset.action.server.ts +25 -0
- package/templates/apps/api-kv/actions/sync.reset.action.ts +22 -0
- package/templates/apps/api-kv/actions/sync.status.action.server.ts +28 -0
- package/templates/apps/api-kv/actions/sync.status.action.ts +17 -0
- package/templates/apps/api-kv/app.config.ts +48 -0
- package/templates/apps/api-kv/database/schema.ts +62 -0
- package/templates/apps/api-kv/package.json +28 -0
- package/templates/apps/api-kv/queries/events.list.query.server.ts +14 -0
- package/templates/apps/api-kv/queries/events.list.query.ts +23 -0
- package/templates/apps/api-kv/template.json +6 -0
- package/templates/apps/api-kv/tests/sync.test.ts +103 -0
- package/templates/apps/api-kv/tsconfig.json +5 -0
- package/templates/apps/api-moderation/README.md +46 -0
- package/templates/apps/api-moderation/app.config.ts +33 -0
- package/templates/apps/api-moderation/database/schema.ts +45 -0
- package/templates/apps/api-moderation/mutations/comments.create.mutation.server.ts +8 -0
- package/templates/apps/api-moderation/mutations/comments.create.mutation.ts +20 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.server.ts +9 -0
- package/templates/apps/api-moderation/mutations/posts.create.mutation.ts +25 -0
- package/templates/apps/api-moderation/package.json +29 -0
- package/templates/apps/api-moderation/template.json +6 -0
- package/templates/apps/api-moderation/tests/posts.create.test.ts +79 -0
- package/templates/apps/api-moderation/tsconfig.json +5 -0
- package/templates/apps/api-observability/README.md +106 -0
- package/templates/apps/api-observability/app.config.ts +32 -0
- package/templates/apps/api-observability/database/schema.ts +38 -0
- package/templates/apps/api-observability/mutations/notes.create.mutation.server.ts +33 -0
- package/templates/apps/api-observability/mutations/notes.create.mutation.ts +15 -0
- package/templates/apps/api-observability/package.json +29 -0
- package/templates/apps/api-observability/queries/notes.list.query.server.ts +7 -0
- package/templates/apps/api-observability/queries/notes.list.query.ts +14 -0
- package/templates/apps/api-observability/template.json +6 -0
- package/templates/apps/api-observability/tests/notes.create.test.ts +36 -0
- package/templates/apps/api-observability/tsconfig.json +5 -0
- package/templates/apps/api-ratelimit/README.md +49 -0
- package/templates/apps/api-ratelimit/app.config.ts +42 -0
- package/templates/apps/api-ratelimit/database/schema.ts +37 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.server.ts +16 -0
- package/templates/apps/api-ratelimit/mutations/notes.create.mutation.ts +34 -0
- package/templates/apps/api-ratelimit/package.json +29 -0
- package/templates/apps/api-ratelimit/template.json +6 -0
- package/templates/apps/api-ratelimit/tests/notes.create.test.ts +69 -0
- package/templates/apps/api-ratelimit/tsconfig.json +5 -0
- package/templates/apps/api-rbac/README.md +59 -0
- package/templates/apps/api-rbac/app.config.ts +48 -0
- package/templates/apps/api-rbac/database/schema.ts +37 -0
- package/templates/apps/api-rbac/mutations/notes.create.mutation.server.ts +22 -0
- package/templates/apps/api-rbac/mutations/notes.create.mutation.ts +26 -0
- package/templates/apps/api-rbac/package.json +29 -0
- package/templates/apps/api-rbac/template.json +6 -0
- package/templates/apps/api-rbac/tests/notes.create.test.ts +80 -0
- package/templates/apps/api-rbac/tsconfig.json +5 -0
- package/templates/apps/api-rest/README.md +85 -0
- package/templates/apps/api-rest/app.config.ts +70 -0
- package/templates/apps/api-rest/database/schema.ts +52 -0
- package/templates/apps/api-rest/lib/product.ts +30 -0
- package/templates/apps/api-rest/package.json +27 -0
- package/templates/apps/api-rest/routes/v1/products.create.route.tsx +45 -0
- package/templates/apps/api-rest/routes/v1/products.delete.route.tsx +25 -0
- package/templates/apps/api-rest/routes/v1/products.get.route.tsx +29 -0
- package/templates/apps/api-rest/routes/v1/products.list.route.tsx +43 -0
- package/templates/apps/api-rest/template.json +6 -0
- package/templates/apps/api-rest/tests/products.create.test.ts +53 -0
- package/templates/apps/api-rest/tsconfig.json +5 -0
- package/templates/apps/api-saas/README.md +106 -0
- package/templates/apps/api-saas/app.config.ts +51 -0
- package/templates/apps/api-saas/database/schema.ts +50 -0
- package/templates/apps/api-saas/mutations/projects.create.mutation.server.ts +52 -0
- package/templates/apps/api-saas/mutations/projects.create.mutation.ts +18 -0
- package/templates/apps/api-saas/package.json +31 -0
- package/templates/apps/api-saas/queries/projects.list.query.server.ts +7 -0
- package/templates/apps/api-saas/queries/projects.list.query.ts +18 -0
- package/templates/apps/api-saas/template.json +6 -0
- package/templates/apps/api-saas/tests/projects.create.test.ts +47 -0
- package/templates/apps/api-saas/tsconfig.json +5 -0
- package/templates/apps/api-search/README.md +68 -0
- package/templates/apps/api-search/app.config.ts +31 -0
- package/templates/apps/api-search/database/schema.ts +47 -0
- package/templates/apps/api-search/lib/search.ts +27 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.server.ts +19 -0
- package/templates/apps/api-search/mutations/articles.create.mutation.ts +35 -0
- package/templates/apps/api-search/package.json +29 -0
- package/templates/apps/api-search/queries/articles.list.query.server.ts +14 -0
- package/templates/apps/api-search/queries/articles.list.query.ts +19 -0
- package/templates/apps/api-search/seeds/articles.seed.ts +41 -0
- package/templates/apps/api-search/startup/searchBackfill.startup.tsx +24 -0
- package/templates/apps/api-search/template.json +6 -0
- package/templates/apps/api-search/tests/articles.create.test.ts +69 -0
- package/templates/apps/api-search/tsconfig.json +5 -0
- package/templates/apps/api-versioning/README.md +51 -0
- package/templates/apps/api-versioning/actions/documents.asOf.action.server.ts +13 -0
- package/templates/apps/api-versioning/actions/documents.asOf.action.ts +14 -0
- package/templates/apps/api-versioning/actions/documents.history.action.server.ts +15 -0
- package/templates/apps/api-versioning/actions/documents.history.action.ts +17 -0
- package/templates/apps/api-versioning/app.config.ts +24 -0
- package/templates/apps/api-versioning/database/schema.ts +38 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.server.ts +8 -0
- package/templates/apps/api-versioning/mutations/documents.create.mutation.ts +21 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.server.ts +9 -0
- package/templates/apps/api-versioning/mutations/documents.update.mutation.ts +21 -0
- package/templates/apps/api-versioning/package.json +29 -0
- package/templates/apps/api-versioning/template.json +6 -0
- package/templates/apps/api-versioning/tests/documents.create.test.ts +37 -0
- package/templates/apps/api-versioning/tsconfig.json +5 -0
- package/templates/apps/api-webhooks/.env +6 -0
- package/templates/apps/api-webhooks/README.md +106 -0
- package/templates/apps/api-webhooks/app.config.ts +16 -0
- package/templates/apps/api-webhooks/database/schema.ts +49 -0
- package/templates/apps/api-webhooks/events/order.completed.webhook.tsx +22 -0
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.server.ts +37 -0
- package/templates/apps/api-webhooks/mutations/orders.fulfill.mutation.ts +15 -0
- package/templates/apps/api-webhooks/package.json +28 -0
- package/templates/apps/api-webhooks/queries/orders.list.query.server.ts +7 -0
- package/templates/apps/api-webhooks/queries/orders.list.query.ts +15 -0
- package/templates/apps/api-webhooks/template.json +6 -0
- package/templates/apps/api-webhooks/tests/orders.fulfill.test.ts +51 -0
- package/templates/apps/api-webhooks/tsconfig.json +5 -0
- package/templates/apps/api-webhooks/webhooks/orders.webhook.tsx +35 -0
- package/templates/apps/changelog/README.md +77 -0
- package/templates/apps/changelog/app.config.ts +27 -0
- package/templates/apps/changelog/content/releases/0.1.0.mdx +19 -0
- package/templates/apps/changelog/content/releases/0.2.0.mdx +28 -0
- package/templates/apps/changelog/package.json +30 -0
- package/templates/apps/changelog/scripts/generate-rss.mjs +38 -0
- package/templates/apps/changelog/src/globals.css +66 -0
- package/templates/apps/changelog/src/globals.d.ts +16 -0
- package/templates/apps/changelog/src/lib/locale.test.ts +72 -0
- package/templates/apps/changelog/src/lib/locale.ts +55 -0
- package/templates/apps/changelog/src/lib/releases.ts +21 -0
- package/templates/apps/changelog/src/locales/de.ts +22 -0
- package/templates/apps/changelog/src/locales/en.ts +30 -0
- package/templates/apps/changelog/src/pages/[locale]/[slug].tsx +24 -0
- package/templates/apps/changelog/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/changelog/src/pages/[locale]/mirrors.test.tsx +95 -0
- package/templates/apps/changelog/src/pages/[slug].test.tsx +127 -0
- package/templates/apps/changelog/src/pages/[slug].tsx +52 -0
- package/templates/apps/changelog/src/pages/index.test.tsx +112 -0
- package/templates/apps/changelog/src/pages/index.tsx +55 -0
- package/templates/apps/changelog/src/pages/layout.tsx +84 -0
- package/templates/apps/changelog/template.json +6 -0
- package/templates/apps/changelog/tsconfig.json +11 -0
- package/templates/apps/edge-functions/README.md +55 -0
- package/templates/apps/edge-functions/functions/aiComplete.serverless.ts +58 -0
- package/templates/apps/edge-functions/functions/currencyConvert.serverless.ts +47 -0
- package/templates/apps/edge-functions/functions/geoGreeting.serverless.ts +44 -0
- package/templates/apps/edge-functions/functions/health.serverless.ts +29 -0
- package/templates/apps/edge-functions/functions/resolveLink.serverless.ts +32 -0
- package/templates/apps/edge-functions/functions/shareLink.serverless.ts +30 -0
- package/templates/apps/edge-functions/functions/slackNotify.serverless.ts +40 -0
- package/templates/apps/edge-functions/functions/verifySignature.serverless.ts +52 -0
- package/templates/apps/edge-functions/package.json +21 -0
- package/templates/apps/edge-functions/template.json +6 -0
- package/templates/apps/edge-functions/tsconfig.json +10 -0
- package/templates/apps/frontend-admin/README.md +40 -0
- package/templates/apps/frontend-admin/app.config.ts +37 -0
- package/templates/apps/frontend-admin/package.json +30 -0
- package/templates/apps/frontend-admin/src/config.ts +8 -0
- package/templates/apps/frontend-admin/src/globals.css +76 -0
- package/templates/apps/frontend-admin/src/globals.d.ts +6 -0
- package/templates/apps/frontend-admin/src/lib/admin.ts +16 -0
- package/templates/apps/frontend-admin/src/lib/auth.ts +24 -0
- package/templates/apps/frontend-admin/src/locales/de.ts +67 -0
- package/templates/apps/frontend-admin/src/locales/en.ts +79 -0
- package/templates/apps/frontend-admin/src/locales/index.ts +15 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/index.test.tsx +55 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/index.tsx +32 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/layout.tsx +29 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/login.test.tsx +73 -0
- package/templates/apps/frontend-admin/src/pages/(marketing)/login.tsx +35 -0
- package/templates/apps/frontend-admin/src/pages/admin/[entity].tsx +121 -0
- package/templates/apps/frontend-admin/src/pages/admin/entity.test.tsx +119 -0
- package/templates/apps/frontend-admin/src/pages/admin/error.tsx +20 -0
- package/templates/apps/frontend-admin/src/pages/admin/fallbacks.test.tsx +68 -0
- package/templates/apps/frontend-admin/src/pages/admin/index.test.tsx +88 -0
- package/templates/apps/frontend-admin/src/pages/admin/index.tsx +65 -0
- package/templates/apps/frontend-admin/src/pages/admin/layout.test.tsx +114 -0
- package/templates/apps/frontend-admin/src/pages/admin/layout.tsx +97 -0
- package/templates/apps/frontend-admin/src/pages/admin/loading.tsx +15 -0
- package/templates/apps/frontend-admin/src/pages/admin/not-found.tsx +15 -0
- package/templates/apps/frontend-admin/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-admin/src/pages/layouts.test.tsx +54 -0
- package/templates/apps/frontend-admin/template.json +6 -0
- package/templates/apps/frontend-admin/tsconfig.json +11 -0
- package/templates/apps/frontend-app/README.md +98 -0
- package/templates/apps/frontend-app/app.config.ts +43 -0
- package/templates/apps/frontend-app/package.json +30 -0
- package/templates/apps/frontend-app/src/locales/de.ts +36 -0
- package/templates/apps/frontend-app/src/locales/en.ts +43 -0
- package/templates/apps/frontend-app/src/locales/index.ts +15 -0
- package/templates/apps/frontend-app/src/pages/index.test.tsx +167 -0
- package/templates/apps/frontend-app/src/pages/index.tsx +136 -0
- package/templates/apps/frontend-app/src/pages/layout.tsx +41 -0
- package/templates/apps/frontend-app/src/pages/schema-ui.test.tsx +99 -0
- package/templates/apps/frontend-app/src/pages/schema-ui.tsx +74 -0
- package/templates/apps/frontend-app/template.json +6 -0
- package/templates/apps/frontend-app/tsconfig.json +11 -0
- package/templates/apps/frontend-blank/README.md +18 -0
- package/templates/apps/frontend-blank/app.config.ts +29 -0
- package/templates/apps/frontend-blank/package.json +29 -0
- package/templates/apps/frontend-blank/src/locales/de.ts +15 -0
- package/templates/apps/frontend-blank/src/locales/en.ts +22 -0
- package/templates/apps/frontend-blank/src/locales/index.ts +15 -0
- package/templates/apps/frontend-blank/src/pages/index.test.tsx +55 -0
- package/templates/apps/frontend-blank/src/pages/index.tsx +27 -0
- package/templates/apps/frontend-blank/src/pages/layout.test.tsx +54 -0
- package/templates/apps/frontend-blank/src/pages/layout.tsx +35 -0
- package/templates/apps/frontend-blank/template.json +6 -0
- package/templates/apps/frontend-blank/tsconfig.json +11 -0
- package/templates/apps/frontend-contact/README.md +65 -0
- package/templates/apps/frontend-contact/app.config.ts +25 -0
- package/templates/apps/frontend-contact/functions/sendMessage.serverless.ts +69 -0
- package/templates/apps/frontend-contact/package.json +33 -0
- package/templates/apps/frontend-contact/src/components/ContactForm.island.test.tsx +142 -0
- package/templates/apps/frontend-contact/src/components/ContactForm.island.tsx +104 -0
- package/templates/apps/frontend-contact/src/config.ts +12 -0
- package/templates/apps/frontend-contact/src/globals.css +84 -0
- package/templates/apps/frontend-contact/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-contact/src/locales/de.ts +26 -0
- package/templates/apps/frontend-contact/src/locales/en.ts +29 -0
- package/templates/apps/frontend-contact/src/pages/[locale]/index.tsx +14 -0
- package/templates/apps/frontend-contact/src/pages/index.test.tsx +70 -0
- package/templates/apps/frontend-contact/src/pages/index.tsx +63 -0
- package/templates/apps/frontend-contact/src/pages/layout.tsx +65 -0
- package/templates/apps/frontend-contact/template.json +6 -0
- package/templates/apps/frontend-contact/tsconfig.json +11 -0
- package/templates/apps/frontend-dashboard/README.md +54 -0
- package/templates/apps/frontend-dashboard/app.config.ts +39 -0
- package/templates/apps/frontend-dashboard/package.json +29 -0
- package/templates/apps/frontend-dashboard/src/config.ts +8 -0
- package/templates/apps/frontend-dashboard/src/globals.css +72 -0
- package/templates/apps/frontend-dashboard/src/globals.d.ts +6 -0
- package/templates/apps/frontend-dashboard/src/lib/auth.ts +27 -0
- package/templates/apps/frontend-dashboard/src/locales/de.ts +49 -0
- package/templates/apps/frontend-dashboard/src/locales/en.ts +60 -0
- package/templates/apps/frontend-dashboard/src/locales/index.ts +15 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.test.tsx +55 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/index.tsx +34 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/layout.tsx +32 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.test.tsx +74 -0
- package/templates/apps/frontend-dashboard/src/pages/(marketing)/login.tsx +40 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/error.tsx +21 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/fallbacks.test.tsx +68 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/index.test.tsx +55 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/index.tsx +38 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.test.tsx +88 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/layout.tsx +69 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/loading.tsx +21 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/not-found.tsx +19 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.test.tsx +69 -0
- package/templates/apps/frontend-dashboard/src/pages/dashboard/settings.tsx +35 -0
- package/templates/apps/frontend-dashboard/src/pages/layout.tsx +12 -0
- package/templates/apps/frontend-dashboard/src/pages/layouts.test.tsx +54 -0
- package/templates/apps/frontend-dashboard/template.json +6 -0
- package/templates/apps/frontend-dashboard/tsconfig.json +11 -0
- package/templates/apps/frontend-docs/README.md +19 -0
- package/templates/apps/frontend-docs/app.config.ts +25 -0
- package/templates/apps/frontend-docs/package.json +29 -0
- package/templates/apps/frontend-docs/src/globals.css +32 -0
- package/templates/apps/frontend-docs/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-docs/src/lib/locale.ts +56 -0
- package/templates/apps/frontend-docs/src/locales/de.ts +24 -0
- package/templates/apps/frontend-docs/src/locales/en.ts +28 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/docs/[...slug].tsx +18 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-docs/src/pages/[locale]/mirrors.test.tsx +67 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug].test.tsx +115 -0
- package/templates/apps/frontend-docs/src/pages/docs/[...slug].tsx +82 -0
- package/templates/apps/frontend-docs/src/pages/index.test.tsx +90 -0
- package/templates/apps/frontend-docs/src/pages/index.tsx +45 -0
- package/templates/apps/frontend-docs/src/pages/layout.test.tsx +84 -0
- package/templates/apps/frontend-docs/src/pages/layout.tsx +66 -0
- package/templates/apps/frontend-docs/template.json +6 -0
- package/templates/apps/frontend-docs/tsconfig.json +11 -0
- package/templates/apps/frontend-i18n/README.md +61 -0
- package/templates/apps/frontend-i18n/app.config.ts +33 -0
- package/templates/apps/frontend-i18n/package.json +28 -0
- package/templates/apps/frontend-i18n/src/globals.css +46 -0
- package/templates/apps/frontend-i18n/src/globals.d.ts +6 -0
- package/templates/apps/frontend-i18n/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-i18n/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-i18n/src/locales/de.ts +24 -0
- package/templates/apps/frontend-i18n/src/locales/en.ts +25 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/about.tsx +11 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-i18n/src/pages/[locale]/mirrors.test.tsx +50 -0
- package/templates/apps/frontend-i18n/src/pages/about.test.tsx +75 -0
- package/templates/apps/frontend-i18n/src/pages/about.tsx +31 -0
- package/templates/apps/frontend-i18n/src/pages/index.test.tsx +102 -0
- package/templates/apps/frontend-i18n/src/pages/index.tsx +43 -0
- package/templates/apps/frontend-i18n/src/pages/layout.test.tsx +86 -0
- package/templates/apps/frontend-i18n/src/pages/layout.tsx +70 -0
- package/templates/apps/frontend-i18n/template.json +6 -0
- package/templates/apps/frontend-i18n/tsconfig.json +11 -0
- package/templates/apps/frontend-landing/README.md +17 -0
- package/templates/apps/frontend-landing/app.config.ts +25 -0
- package/templates/apps/frontend-landing/package.json +29 -0
- package/templates/apps/frontend-landing/src/globals.css +23 -0
- package/templates/apps/frontend-landing/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-landing/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-landing/src/locales/de.ts +24 -0
- package/templates/apps/frontend-landing/src/locales/en.ts +26 -0
- package/templates/apps/frontend-landing/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-landing/src/pages/[locale]/mirrors.test.tsx +37 -0
- package/templates/apps/frontend-landing/src/pages/index.test.tsx +110 -0
- package/templates/apps/frontend-landing/src/pages/index.tsx +75 -0
- package/templates/apps/frontend-landing/src/pages/layout.test.tsx +84 -0
- package/templates/apps/frontend-landing/src/pages/layout.tsx +66 -0
- package/templates/apps/frontend-landing/template.json +6 -0
- package/templates/apps/frontend-landing/tsconfig.json +11 -0
- package/templates/apps/frontend-spa/README.md +45 -0
- package/templates/apps/frontend-spa/app.config.ts +27 -0
- package/templates/apps/frontend-spa/package.json +29 -0
- package/templates/apps/frontend-spa/src/globals.css +84 -0
- package/templates/apps/frontend-spa/src/locales/de.ts +22 -0
- package/templates/apps/frontend-spa/src/locales/en.ts +29 -0
- package/templates/apps/frontend-spa/src/locales/index.ts +15 -0
- package/templates/apps/frontend-spa/src/pages/index.test.tsx +137 -0
- package/templates/apps/frontend-spa/src/pages/index.tsx +123 -0
- package/templates/apps/frontend-spa/src/pages/layout.tsx +27 -0
- package/templates/apps/frontend-spa/template.json +6 -0
- package/templates/apps/frontend-spa/tsconfig.json +11 -0
- package/templates/apps/frontend-ssr/README.md +68 -0
- package/templates/apps/frontend-ssr/app.config.ts +32 -0
- package/templates/apps/frontend-ssr/package.json +29 -0
- package/templates/apps/frontend-ssr/src/globals.css +67 -0
- package/templates/apps/frontend-ssr/src/locales/de.ts +41 -0
- package/templates/apps/frontend-ssr/src/locales/en.ts +54 -0
- package/templates/apps/frontend-ssr/src/locales/index.ts +16 -0
- package/templates/apps/frontend-ssr/src/pages/feed-swr.test.tsx +69 -0
- package/templates/apps/frontend-ssr/src/pages/feed-swr.tsx +54 -0
- package/templates/apps/frontend-ssr/src/pages/feed.test.tsx +73 -0
- package/templates/apps/frontend-ssr/src/pages/feed.tsx +64 -0
- package/templates/apps/frontend-ssr/src/pages/index.test.tsx +89 -0
- package/templates/apps/frontend-ssr/src/pages/index.tsx +72 -0
- package/templates/apps/frontend-ssr/src/pages/layout.tsx +37 -0
- package/templates/apps/frontend-ssr/template.json +6 -0
- package/templates/apps/frontend-ssr/tsconfig.json +11 -0
- package/templates/apps/frontend-ssr-api/README.md +50 -0
- package/templates/apps/frontend-ssr-api/app.config.ts +43 -0
- package/templates/apps/frontend-ssr-api/package.json +30 -0
- package/templates/apps/frontend-ssr-api/src/globals.css +38 -0
- package/templates/apps/frontend-ssr-api/src/globals.d.ts +6 -0
- package/templates/apps/frontend-ssr-api/src/locales/de.ts +20 -0
- package/templates/apps/frontend-ssr-api/src/locales/en.ts +31 -0
- package/templates/apps/frontend-ssr-api/src/locales/index.ts +16 -0
- package/templates/apps/frontend-ssr-api/src/pages/index.test.tsx +105 -0
- package/templates/apps/frontend-ssr-api/src/pages/index.tsx +83 -0
- package/templates/apps/frontend-ssr-api/src/pages/layout.tsx +28 -0
- package/templates/apps/frontend-ssr-api/template.json +6 -0
- package/templates/apps/frontend-ssr-api/tsconfig.json +11 -0
- package/templates/apps/frontend-static-blog/README.md +49 -0
- package/templates/apps/frontend-static-blog/app.config.ts +34 -0
- package/templates/apps/frontend-static-blog/package.json +28 -0
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.test.tsx +65 -0
- package/templates/apps/frontend-static-blog/src/components/ReadingProgress.island.tsx +35 -0
- package/templates/apps/frontend-static-blog/src/content/posts.ts +64 -0
- package/templates/apps/frontend-static-blog/src/globals.css +75 -0
- package/templates/apps/frontend-static-blog/src/lib/locale.test.ts +72 -0
- package/templates/apps/frontend-static-blog/src/lib/locale.ts +55 -0
- package/templates/apps/frontend-static-blog/src/locales/de.ts +19 -0
- package/templates/apps/frontend-static-blog/src/locales/en.ts +26 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/blog/[slug].tsx +20 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/index.tsx +13 -0
- package/templates/apps/frontend-static-blog/src/pages/[locale]/mirrors.test.tsx +62 -0
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug].test.tsx +116 -0
- package/templates/apps/frontend-static-blog/src/pages/blog/[slug].tsx +69 -0
- package/templates/apps/frontend-static-blog/src/pages/index.test.tsx +100 -0
- package/templates/apps/frontend-static-blog/src/pages/index.tsx +58 -0
- package/templates/apps/frontend-static-blog/src/pages/layout.tsx +63 -0
- package/templates/apps/frontend-static-blog/template.json +6 -0
- package/templates/apps/frontend-static-blog/tsconfig.json +11 -0
- package/templates/baselines/bare/.env.example +37 -0
- package/templates/baselines/bare/README.md +47 -0
- package/templates/baselines/bare/baseline.json +31 -0
- package/templates/baselines/bare/deploy/README.md +43 -0
- package/templates/baselines/bare/deploy/voltro.service.example +36 -0
- package/templates/baselines/compose/.env.example +46 -0
- package/templates/baselines/compose/README.md +69 -0
- package/templates/baselines/compose/baseline.json +51 -0
- package/templates/baselines/compose/docker/.dockerignore +38 -0
- package/templates/baselines/compose/docker/api.Dockerfile +57 -0
- package/templates/baselines/compose/docker/dev.Dockerfile +35 -0
- package/templates/baselines/compose/docker/web.Dockerfile +59 -0
- package/templates/baselines/compose/docker-compose.dev.yml +89 -0
- package/templates/baselines/compose/docker-compose.prod.yml +87 -0
- package/templates/baselines/compose/docker-compose.yml +41 -0
- package/templates/baselines/compose-mariadb/.env.example +57 -0
- package/templates/baselines/compose-mariadb/README.md +78 -0
- package/templates/baselines/compose-mariadb/baseline.json +51 -0
- package/templates/baselines/compose-mariadb/docker/.dockerignore +38 -0
- package/templates/baselines/compose-mariadb/docker/api.Dockerfile +57 -0
- package/templates/baselines/compose-mariadb/docker/dev.Dockerfile +35 -0
- package/templates/baselines/compose-mariadb/docker/mariadb-init.sql +6 -0
- package/templates/baselines/compose-mariadb/docker/web.Dockerfile +59 -0
- package/templates/baselines/compose-mariadb/docker-compose.dev.yml +117 -0
- package/templates/baselines/compose-mariadb/docker-compose.prod.yml +114 -0
- package/templates/baselines/compose-mariadb/docker-compose.yml +79 -0
- package/templates/baselines/helm/.env.example +39 -0
- package/templates/baselines/helm/README.md +98 -0
- package/templates/baselines/helm/baseline.json +53 -0
- package/templates/baselines/helm/charts/voltro-app/.helmignore +10 -0
- package/templates/baselines/helm/charts/voltro-app/Chart.yaml +10 -0
- package/templates/baselines/helm/charts/voltro-app/templates/_helpers.tpl +36 -0
- package/templates/baselines/helm/charts/voltro-app/templates/configmap.yaml +13 -0
- package/templates/baselines/helm/charts/voltro-app/templates/deployment-api.yaml +120 -0
- package/templates/baselines/helm/charts/voltro-app/templates/deployment-web.yaml +45 -0
- package/templates/baselines/helm/charts/voltro-app/templates/ingress.yaml +37 -0
- package/templates/baselines/helm/charts/voltro-app/templates/postgres-service.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/templates/postgres-statefulset.yaml +73 -0
- package/templates/baselines/helm/charts/voltro-app/templates/secret.yaml +33 -0
- package/templates/baselines/helm/charts/voltro-app/templates/service-api.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/templates/service-web.yaml +19 -0
- package/templates/baselines/helm/charts/voltro-app/values-dev.yaml +21 -0
- package/templates/baselines/helm/charts/voltro-app/values-prod.yaml +58 -0
- package/templates/baselines/helm/charts/voltro-app/values-staging.yaml +25 -0
- package/templates/baselines/helm/charts/voltro-app/values.yaml +109 -0
- package/templates/baselines/helm/deploy/README.md +94 -0
- package/templates/patches/@effect__cluster@0.59.0.patch +262 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// Home page (`/` = en, `/de` = de). Two things worth testing:
|
|
4
|
+
// 1. `meta({ locale })` is localized — it reads the REAL catalog via
|
|
5
|
+
// getCatalog, so we assert en vs de titles/descriptions directly (no mock).
|
|
6
|
+
// 2. the rendered body shows localized message text. The page pulls copy from
|
|
7
|
+
// @voltro/i18n (<T>, useT, useLocale); we mock those to resolve against the
|
|
8
|
+
// real catalog for a controllable "current locale", so the assertions are
|
|
9
|
+
// genuine localized strings, not placeholder ids.
|
|
10
|
+
|
|
11
|
+
import { afterEach, describe, expect, test, vi } from 'vitest'
|
|
12
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
13
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
14
|
+
import en from '../locales/en'
|
|
15
|
+
import de from '../locales/de'
|
|
16
|
+
|
|
17
|
+
// Controllable "active locale" for the i18n mock.
|
|
18
|
+
const catalogs: Record<string, Record<string, string>> = { en, de }
|
|
19
|
+
let currentLocale = 'en'
|
|
20
|
+
|
|
21
|
+
// Minimal ICU-ish interpolation: replace {key} with values[key]. Enough to
|
|
22
|
+
// assert the page threaded the right values into the right message.
|
|
23
|
+
const fill = (msg: string, values?: Record<string, unknown>): string =>
|
|
24
|
+
values ? msg.replace(/\{(\w+)\}/g, (_, k: string) => String(values[k] ?? `{${k}}`)) : msg
|
|
25
|
+
|
|
26
|
+
vi.mock('@voltro/i18n', () => ({
|
|
27
|
+
// The locale catalogs call these at module top-level; identity mirrors the
|
|
28
|
+
// real (compile-time-only) implementations so importing them doesn't throw.
|
|
29
|
+
defineCatalog: <T,>(c: T): T => c,
|
|
30
|
+
defineLocale:
|
|
31
|
+
() =>
|
|
32
|
+
<T,>(c: T): T =>
|
|
33
|
+
c,
|
|
34
|
+
useLocale: () => currentLocale,
|
|
35
|
+
useT: (id: string, values?: Record<string, unknown>) =>
|
|
36
|
+
fill(catalogs[currentLocale]?.[id] ?? id, values),
|
|
37
|
+
T: ({ id, values }: { id: string; values?: Record<string, unknown> }) =>
|
|
38
|
+
fill(catalogs[currentLocale]?.[id] ?? id, values),
|
|
39
|
+
}))
|
|
40
|
+
|
|
41
|
+
const { default: Index, meta, renderMode } = await import('./index')
|
|
42
|
+
|
|
43
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
44
|
+
|
|
45
|
+
let container: HTMLDivElement
|
|
46
|
+
let root: Root
|
|
47
|
+
|
|
48
|
+
const render = (node: ReactNode): void => {
|
|
49
|
+
container = document.createElement('div')
|
|
50
|
+
document.body.appendChild(container)
|
|
51
|
+
act(() => {
|
|
52
|
+
root = createRoot(container)
|
|
53
|
+
root.render(node)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
currentLocale = 'en'
|
|
59
|
+
if (root) act(() => root.unmount())
|
|
60
|
+
container?.remove()
|
|
61
|
+
document.body.innerHTML = ''
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('home — config + localized meta', () => {
|
|
65
|
+
test('is a static page', () => {
|
|
66
|
+
expect(renderMode).toBe('static')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('meta() returns the English catalog title/description for locale en', () => {
|
|
70
|
+
const m = meta({ locale: 'en' })
|
|
71
|
+
expect(m.title).toBe('Home — Voltro i18n')
|
|
72
|
+
expect(m.description).toContain('bilingual')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
test('meta() returns the German catalog title/description for locale de', () => {
|
|
76
|
+
const m = meta({ locale: 'de' })
|
|
77
|
+
expect(m.title).toBe('Start — Voltro i18n')
|
|
78
|
+
expect(m.description).toContain('zweisprachige')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('meta() falls back to the default (en) catalog for an unknown locale', () => {
|
|
82
|
+
expect(meta({ locale: 'zz' }).title).toBe('Home — Voltro i18n')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
describe('home — localized render', () => {
|
|
87
|
+
test('renders the English greeting + tagline when the active locale is en', () => {
|
|
88
|
+
currentLocale = 'en'
|
|
89
|
+
render(createElement(Index))
|
|
90
|
+
expect(container.querySelector('h1')!.textContent).toBe('Hello, Voltro')
|
|
91
|
+
expect(container.textContent).toContain('bilingual static site')
|
|
92
|
+
expect(container.textContent).toContain('You are reading the EN version.')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('renders the German greeting + tagline when the active locale is de', () => {
|
|
96
|
+
currentLocale = 'de'
|
|
97
|
+
render(createElement(Index))
|
|
98
|
+
expect(container.querySelector('h1')!.textContent).toBe('Hallo, Voltro')
|
|
99
|
+
expect(container.textContent).toContain('zweisprachige statische Website')
|
|
100
|
+
expect(container.textContent).toContain('Du liest die DE-Version.')
|
|
101
|
+
})
|
|
102
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Home, mounted at `/` (default locale) and mirrored at `/de` by
|
|
2
|
+
// src/pages/[locale]/index.tsx. `renderMode = 'static'` pre-renders one
|
|
3
|
+
// HTML file per locale at build time.
|
|
4
|
+
//
|
|
5
|
+
// `meta` is a FUNCTION of `{ locale }` — that's what makes each variant's
|
|
6
|
+
// <title> / <description> localised in the pre-rendered HTML. A plain
|
|
7
|
+
// `meta` object would leave the head English on /de. `locale` is driven
|
|
8
|
+
// by params.locale on the [locale] route, defaultLocale on the bare path.
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from 'react'
|
|
11
|
+
import type { PageMeta } from '@voltro/web'
|
|
12
|
+
import { T, useT, useLocale } from '@voltro/i18n'
|
|
13
|
+
import { getCatalog } from '../lib/locale'
|
|
14
|
+
|
|
15
|
+
export const renderMode = 'static' as const
|
|
16
|
+
|
|
17
|
+
export const meta = ({ locale }: { locale: string }): PageMeta => {
|
|
18
|
+
const c = getCatalog(locale)
|
|
19
|
+
return {
|
|
20
|
+
title: c['meta.home.title'],
|
|
21
|
+
description: c['meta.home.description'],
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default function Index(): ReactNode {
|
|
26
|
+
const locale = useLocale()
|
|
27
|
+
// Imperative form for an attribute-ish string; <T> for JSX.
|
|
28
|
+
const greeting = useT('home.title', { name: 'Voltro' })
|
|
29
|
+
return (
|
|
30
|
+
<article>
|
|
31
|
+
<h1>{greeting}</h1>
|
|
32
|
+
<p className="muted">
|
|
33
|
+
<T id="home.tagline" />
|
|
34
|
+
</p>
|
|
35
|
+
<p>
|
|
36
|
+
<T id="home.body" />
|
|
37
|
+
</p>
|
|
38
|
+
<p>
|
|
39
|
+
<T id="home.activeLocale" values={{ locale: locale.toUpperCase() }} />
|
|
40
|
+
</p>
|
|
41
|
+
</article>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// Root layout — renders the top nav whose Home/About links + language switcher
|
|
4
|
+
// are locale-prefixed off the CURRENT url (useUrlLocale → useLocation). We mock
|
|
5
|
+
// useLocation (@voltro/web) to drive the active locale, and mock @voltro/i18n so
|
|
6
|
+
// <I18nProvider> is a pass-through and <T> resolves against the real catalogs.
|
|
7
|
+
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
|
|
9
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
10
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
11
|
+
import en from '../locales/en'
|
|
12
|
+
import de from '../locales/de'
|
|
13
|
+
|
|
14
|
+
const location = vi.fn<() => string>()
|
|
15
|
+
vi.mock('@voltro/web', () => ({ useLocation: () => location() }))
|
|
16
|
+
|
|
17
|
+
const catalogs: Record<string, Record<string, string>> = { en, de }
|
|
18
|
+
const localeFor = (path: string): string => (/^\/de(?:\/|$)/.test(path) ? 'de' : 'en')
|
|
19
|
+
|
|
20
|
+
vi.mock('@voltro/i18n', () => ({
|
|
21
|
+
// The locale catalogs call these at module top-level; identity mirrors the
|
|
22
|
+
// real (compile-time-only) implementations so importing them doesn't throw.
|
|
23
|
+
defineCatalog: <T,>(c: T): T => c,
|
|
24
|
+
defineLocale:
|
|
25
|
+
() =>
|
|
26
|
+
<T,>(c: T): T =>
|
|
27
|
+
c,
|
|
28
|
+
I18nProvider: ({ children }: { children?: ReactNode }) => children,
|
|
29
|
+
// The nav uses <T> for link labels; resolve against the catalog of the
|
|
30
|
+
// locale implied by the current path (matches what useUrlLocale computes).
|
|
31
|
+
T: ({ id }: { id: string }) => catalogs[localeFor(location())]?.[id] ?? id,
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
const { default: Layout } = await import('./layout')
|
|
35
|
+
|
|
36
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
37
|
+
|
|
38
|
+
let container: HTMLDivElement
|
|
39
|
+
let root: Root
|
|
40
|
+
|
|
41
|
+
const render = (node: ReactNode): void => {
|
|
42
|
+
container = document.createElement('div')
|
|
43
|
+
document.body.appendChild(container)
|
|
44
|
+
act(() => {
|
|
45
|
+
root = createRoot(container)
|
|
46
|
+
root.render(node)
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const hrefs = (): Array<string | null> =>
|
|
51
|
+
Array.from(container.querySelectorAll('a')).map((a) => a.getAttribute('href'))
|
|
52
|
+
|
|
53
|
+
beforeEach(() => location.mockReset())
|
|
54
|
+
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
if (root) act(() => root.unmount())
|
|
57
|
+
container?.remove()
|
|
58
|
+
document.body.innerHTML = ''
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('i18n layout — English (bare path)', () => {
|
|
62
|
+
test('renders bare Home/About links and an English nav label', () => {
|
|
63
|
+
location.mockReturnValue('/about')
|
|
64
|
+
render(createElement(Layout, { children: createElement('p', null, 'page') }))
|
|
65
|
+
const h = hrefs()
|
|
66
|
+
expect(h).toContain('/')
|
|
67
|
+
expect(h).toContain('/about')
|
|
68
|
+
// language switcher keeps you on the same bare page, re-prefixed per locale
|
|
69
|
+
expect(h).toContain('/de/about')
|
|
70
|
+
expect(container.textContent).toContain('Home')
|
|
71
|
+
expect(container.querySelector('main')!.textContent).toContain('page')
|
|
72
|
+
})
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
describe('i18n layout — German (/de prefix)', () => {
|
|
76
|
+
test('prefixes the nav links with /de and renders German labels', () => {
|
|
77
|
+
location.mockReturnValue('/de/about')
|
|
78
|
+
render(createElement(Layout, { children: null }))
|
|
79
|
+
const h = hrefs()
|
|
80
|
+
expect(h).toContain('/de')
|
|
81
|
+
expect(h).toContain('/de/about')
|
|
82
|
+
// switching back to en strips the prefix
|
|
83
|
+
expect(h).toContain('/about')
|
|
84
|
+
expect(container.textContent).toContain('Start')
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Root layout. The framework's auto-wired <I18nProvider> is COOKIE-driven
|
|
2
|
+
// (Strategy A). For URL-prefix routing (Strategy B) we add an INNER provider
|
|
3
|
+
// that reads the locale FROM THE URL, so client-side navigation between
|
|
4
|
+
// /about and /de/about swaps the catalog without a full reload. At build
|
|
5
|
+
// time it resolves to the same locale the SSG pipeline pre-rendered.
|
|
6
|
+
//
|
|
7
|
+
// `<html>`/`<head>`/`<body>` are owned by the framework shell — don't render
|
|
8
|
+
// them here. Page <title> + meta come from each page's `meta` export.
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from 'react'
|
|
11
|
+
import { I18nProvider, T } from '@voltro/i18n'
|
|
12
|
+
import { useLocation } from '@voltro/web'
|
|
13
|
+
import {
|
|
14
|
+
CATALOGS,
|
|
15
|
+
DEFAULT_LOCALE,
|
|
16
|
+
SUPPORTED_LOCALES,
|
|
17
|
+
stripLocalePrefix,
|
|
18
|
+
useUrlLocale,
|
|
19
|
+
withLocalePrefix,
|
|
20
|
+
} from '../lib/locale'
|
|
21
|
+
import '../globals.css'
|
|
22
|
+
|
|
23
|
+
function TopNav(): ReactNode {
|
|
24
|
+
const locale = useUrlLocale()
|
|
25
|
+
// The path WITHOUT its locale prefix — so the language switch keeps you
|
|
26
|
+
// on the same page (e.g. /de/about → /about, then re-prefix per locale).
|
|
27
|
+
const barePath = stripLocalePrefix(useLocation())
|
|
28
|
+
|
|
29
|
+
// Plain <a> (not <Link>): these hrefs are COMPUTED per locale, and
|
|
30
|
+
// <Link to> wants a statically-known `VoltroUrl` literal. The Router's
|
|
31
|
+
// global click interceptor SPA-navigates internal <a> anyway, so the
|
|
32
|
+
// inner provider still swaps catalogs without a full reload.
|
|
33
|
+
return (
|
|
34
|
+
<nav className="topnav">
|
|
35
|
+
<a href={withLocalePrefix('/', locale)}>
|
|
36
|
+
<T id="nav.home" />
|
|
37
|
+
</a>
|
|
38
|
+
<a href={withLocalePrefix('/about', locale)}>
|
|
39
|
+
<T id="nav.about" />
|
|
40
|
+
</a>
|
|
41
|
+
<span className="spacer" />
|
|
42
|
+
<span className="lang">
|
|
43
|
+
<span className="label">
|
|
44
|
+
<T id="nav.language" />:
|
|
45
|
+
</span>
|
|
46
|
+
{SUPPORTED_LOCALES.map((code) => (
|
|
47
|
+
<a
|
|
48
|
+
key={code}
|
|
49
|
+
href={withLocalePrefix(barePath, code)}
|
|
50
|
+
aria-current={code === locale ? 'true' : undefined}
|
|
51
|
+
>
|
|
52
|
+
{code.toUpperCase()}
|
|
53
|
+
</a>
|
|
54
|
+
))}
|
|
55
|
+
</span>
|
|
56
|
+
</nav>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function Layout({ children }: { readonly children: ReactNode }): ReactNode {
|
|
61
|
+
const locale = useUrlLocale()
|
|
62
|
+
return (
|
|
63
|
+
<I18nProvider locale={locale} messages={CATALOGS[locale]} defaultLocale={DEFAULT_LOCALE}>
|
|
64
|
+
<div className="shell">
|
|
65
|
+
<TopNav />
|
|
66
|
+
<main>{children}</main>
|
|
67
|
+
</div>
|
|
68
|
+
</I18nProvider>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "frontend-i18n",
|
|
3
|
+
"kind": "web",
|
|
4
|
+
"summary": "Bilingual (en/de) static site with URL-prefix i18n — per-locale SSG, [locale] mirror pages, and type-checked translation catalogs. `voltro build` emits /… and /de/… HTML.",
|
|
5
|
+
"tags": ["web", "frontend", "i18n", "intl", "localization", "ssg", "static"]
|
|
6
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"jsx": "react-jsx",
|
|
5
|
+
"lib": ["ES2024", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler"
|
|
8
|
+
},
|
|
9
|
+
"include": ["src/**/*"],
|
|
10
|
+
"exclude": ["node_modules", "dist", ".framework"]
|
|
11
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Marketing landing page (template: **frontend-landing**).
|
|
4
|
+
|
|
5
|
+
The page exports `renderMode = 'static'` + `interactive = 'none'`, so
|
|
6
|
+
`voltro build` pre-renders it to HTML and `voltro start` serves the
|
|
7
|
+
file directly — zero framework JS on the wire.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm install
|
|
11
|
+
pnpm --filter @{{projectName}}/{{appName}} dev
|
|
12
|
+
# → http://localhost:5173
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
When you need a contact form / sign-up flow, switch
|
|
16
|
+
`interactive: 'islands'` on the page and mark the interactive
|
|
17
|
+
component with the `*.island.tsx` suffix so only that bundle ships.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// `port` is allocated by `voltro create-project` / `voltro add-app` from
|
|
2
|
+
// the project's portRange (in project.json).
|
|
3
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
4
|
+
|
|
5
|
+
// Typed PUBLIC environment — public vars are validated + baked into the browser
|
|
6
|
+
// bundle at build time and read via `publicEnv` from '@voltro/env/public'
|
|
7
|
+
// (typed, autocompleted). NEVER put a secret here (it would ship to every
|
|
8
|
+
// visitor) — declare secrets on the api instead. Run `voltro env` for the manifest.
|
|
9
|
+
export const env = defineEnv({
|
|
10
|
+
VOLTRO_PUBLIC_APP_NAME: envVar.string({ access: 'public', default: '{{capProjectName}}' }),
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export default {
|
|
14
|
+
type: 'web' as const,
|
|
15
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
16
|
+
port: {{port}},
|
|
17
|
+
// The shipped catalogs (Strategy B: URL-prefix i18n). `defaultLocale`
|
|
18
|
+
// lives at the bare path (`/`); every other locale is prefixed (`/de`).
|
|
19
|
+
// `voltro build` pre-renders one HTML file per locale. Keep this in sync
|
|
20
|
+
// with SUPPORTED_LOCALES / DEFAULT_LOCALE in src/lib/locale.ts — that file
|
|
21
|
+
// drives the [locale] mirror routing + the language switcher.
|
|
22
|
+
locales: ['en', 'de'] as const,
|
|
23
|
+
defaultLocale: 'en' as const,
|
|
24
|
+
env,
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{projectName}}/{{appName}}",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "voltro dev .",
|
|
8
|
+
"build": "voltro build .",
|
|
9
|
+
"start": "voltro start .",
|
|
10
|
+
"test": "voltro test",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@voltro/cli": "0.1.0",
|
|
15
|
+
"@voltro/client": "0.1.0",
|
|
16
|
+
"@voltro/env": "0.1.0",
|
|
17
|
+
"@voltro/i18n": "0.1.0",
|
|
18
|
+
"@voltro/ui-shadcn": "0.1.0",
|
|
19
|
+
"@voltro/web": "0.1.0",
|
|
20
|
+
"react": "^19.0.0",
|
|
21
|
+
"react-dom": "^19.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/react": "^19.0.0",
|
|
25
|
+
"@types/react-dom": "^19.0.0",
|
|
26
|
+
"typescript": "^5.7.0",
|
|
27
|
+
"vitest": "^3.0.0"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* Landing template — design tokens + Tailwind. `@voltro/ui-shadcn/tokens.css`
|
|
2
|
+
* is the kit's entry point: imports tailwindcss, tw-animate-css, declares
|
|
3
|
+
* the shadcn palette, and registers the @theme mapping. Brand-specific
|
|
4
|
+
* overrides go below. */
|
|
5
|
+
|
|
6
|
+
@import "@voltro/ui-shadcn/tokens.css";
|
|
7
|
+
|
|
8
|
+
/* Scan THIS app's source so utility classes used only here (not in
|
|
9
|
+
* the kit) get generated. Every consumer of @voltro/ui-shadcn needs
|
|
10
|
+
* its own @source — once the kit declares one, Tailwind v4's
|
|
11
|
+
* vite-plugin auto-detect stops firing. */
|
|
12
|
+
@source "./**/*.{tsx,ts,jsx,js}";
|
|
13
|
+
|
|
14
|
+
html, body { min-height: 100vh; }
|
|
15
|
+
|
|
16
|
+
/* Gradient title — use on hero <h1> via `className="gradient-title"`. */
|
|
17
|
+
.gradient-title {
|
|
18
|
+
background: linear-gradient(180deg, var(--foreground) 0%, oklch(0.7 0.18 290) 100%);
|
|
19
|
+
-webkit-background-clip: text;
|
|
20
|
+
background-clip: text;
|
|
21
|
+
-webkit-text-fill-color: transparent;
|
|
22
|
+
color: transparent;
|
|
23
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// The URL-prefix i18n helpers — the load-bearing pure functions that drive the
|
|
2
|
+
// [locale] mirror routing + the language switcher. `useUrlLocale` (the one hook
|
|
3
|
+
// in this module) reads useLocation from @voltro/web; the rest are pure string
|
|
4
|
+
// functions we can exercise directly.
|
|
5
|
+
|
|
6
|
+
import { describe, expect, test } from 'vitest'
|
|
7
|
+
import {
|
|
8
|
+
SUPPORTED_LOCALES,
|
|
9
|
+
DEFAULT_LOCALE,
|
|
10
|
+
getCatalog,
|
|
11
|
+
localeFromPathname,
|
|
12
|
+
withLocalePrefix,
|
|
13
|
+
stripLocalePrefix,
|
|
14
|
+
} from './locale'
|
|
15
|
+
|
|
16
|
+
describe('locale constants', () => {
|
|
17
|
+
test('supports en + de with en as the default', () => {
|
|
18
|
+
expect(SUPPORTED_LOCALES).toEqual(['en', 'de'])
|
|
19
|
+
expect(DEFAULT_LOCALE).toBe('en')
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
describe('getCatalog', () => {
|
|
24
|
+
test('returns the matching catalog for a supported locale', () => {
|
|
25
|
+
expect(getCatalog('de')['nav.home']).toBe('Start')
|
|
26
|
+
expect(getCatalog('en')['nav.home']).toBe('Home')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('falls back to the default catalog for an unsupported locale', () => {
|
|
30
|
+
expect(getCatalog('zz')['nav.home']).toBe('Home')
|
|
31
|
+
})
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('localeFromPathname', () => {
|
|
35
|
+
test('extracts a prefixed locale', () => {
|
|
36
|
+
expect(localeFromPathname('/de')).toBe('de')
|
|
37
|
+
expect(localeFromPathname('/de/pricing')).toBe('de')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('defaults to en for the bare path or an unknown prefix', () => {
|
|
41
|
+
expect(localeFromPathname('/')).toBe('en')
|
|
42
|
+
expect(localeFromPathname('/pricing')).toBe('en')
|
|
43
|
+
expect(localeFromPathname('/fr/pricing')).toBe('en')
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('withLocalePrefix', () => {
|
|
48
|
+
test('leaves default-locale paths bare', () => {
|
|
49
|
+
expect(withLocalePrefix('/pricing', 'en')).toBe('/pricing')
|
|
50
|
+
expect(withLocalePrefix('/', 'en')).toBe('/')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('prefixes non-default locales, mapping / to /<locale>', () => {
|
|
54
|
+
expect(withLocalePrefix('/pricing', 'de')).toBe('/de/pricing')
|
|
55
|
+
expect(withLocalePrefix('/', 'de')).toBe('/de')
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe('stripLocalePrefix', () => {
|
|
60
|
+
test('removes a locale prefix, mapping /<locale> back to /', () => {
|
|
61
|
+
expect(stripLocalePrefix('/de/pricing')).toBe('/pricing')
|
|
62
|
+
expect(stripLocalePrefix('/de')).toBe('/')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('leaves an unprefixed path unchanged', () => {
|
|
66
|
+
expect(stripLocalePrefix('/pricing')).toBe('/pricing')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('round-trips with withLocalePrefix', () => {
|
|
70
|
+
expect(stripLocalePrefix(withLocalePrefix('/pricing', 'de'))).toBe('/pricing')
|
|
71
|
+
})
|
|
72
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// The single source of truth for this app's locales + the URL-prefix
|
|
2
|
+
// helpers. `app.config.ts` lists the same codes for the build pipeline;
|
|
3
|
+
// the [locale] mirror pages + the layout's <I18nProvider> read THIS file.
|
|
4
|
+
//
|
|
5
|
+
// Keep SUPPORTED_LOCALES / DEFAULT_LOCALE in sync with `locales` /
|
|
6
|
+
// `defaultLocale` in app.config.ts.
|
|
7
|
+
|
|
8
|
+
import { useLocation } from '@voltro/web'
|
|
9
|
+
import en from '../locales/en'
|
|
10
|
+
import de from '../locales/de'
|
|
11
|
+
|
|
12
|
+
export const SUPPORTED_LOCALES = ['en', 'de'] as const
|
|
13
|
+
export const DEFAULT_LOCALE = 'en'
|
|
14
|
+
|
|
15
|
+
export type Locale = (typeof SUPPORTED_LOCALES)[number]
|
|
16
|
+
|
|
17
|
+
// The catalogs, keyed by code. The layout picks one to feed the inner
|
|
18
|
+
// <I18nProvider>; `meta` (build-time, per locale) reads one via getCatalog.
|
|
19
|
+
export const CATALOGS = { en, de } as const
|
|
20
|
+
|
|
21
|
+
// Every catalog mirrors `en`'s keys (defineLocale enforces it), so a
|
|
22
|
+
// plain string-record over en's keys is the common type both satisfy —
|
|
23
|
+
// what `meta` reads at build time (`c['meta.home.title']` → string).
|
|
24
|
+
export type Messages = Record<keyof typeof en, string>
|
|
25
|
+
|
|
26
|
+
const isSupported = (v: string): v is Locale =>
|
|
27
|
+
(SUPPORTED_LOCALES as ReadonlyArray<string>).includes(v)
|
|
28
|
+
|
|
29
|
+
export const getCatalog = (locale: string): Messages =>
|
|
30
|
+
isSupported(locale) ? CATALOGS[locale] : CATALOGS[DEFAULT_LOCALE]
|
|
31
|
+
|
|
32
|
+
// `/de/about` → 'de'; `/about` → 'en' (default lives at the bare path).
|
|
33
|
+
export const localeFromPathname = (pathname: string): Locale => {
|
|
34
|
+
const match = /^\/([a-z]{2})(?:\/|$)/.exec(pathname)
|
|
35
|
+
return match && isSupported(match[1]!) ? (match[1] as Locale) : DEFAULT_LOCALE
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Reactive: useLocation re-renders on client-side nav, so the layout's
|
|
39
|
+
// provider swaps catalogs when you move between /about and /de/about
|
|
40
|
+
// without a full reload.
|
|
41
|
+
export const useUrlLocale = (): Locale => localeFromPathname(useLocation())
|
|
42
|
+
|
|
43
|
+
// '/about' + 'de' → '/de/about'; the default locale stays bare ('/about').
|
|
44
|
+
export const withLocalePrefix = (path: string, locale: string): string => {
|
|
45
|
+
if (locale === DEFAULT_LOCALE) return path
|
|
46
|
+
const p = path.startsWith('/') ? path : `/${path}`
|
|
47
|
+
return p === '/' ? `/${locale}` : `/${locale}${p}`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// '/de/about' → '/about'; '/about' → '/about'. The inverse of the above,
|
|
51
|
+
// used to keep the active path when switching language.
|
|
52
|
+
export const stripLocalePrefix = (pathname: string): string => {
|
|
53
|
+
const match = /^\/([a-z]{2})(\/.*|$)/.exec(pathname)
|
|
54
|
+
return match && isSupported(match[1]!) ? match[2] || '/' : pathname
|
|
55
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// German catalog. MUST mirror en.ts key-for-key — `defineLocale<typeof en>()`
|
|
2
|
+
// returns a curried function that fails `tsc --noEmit` on a missing or extra
|
|
3
|
+
// key, so translations can never silently drift out of sync.
|
|
4
|
+
import { defineLocale } from '@voltro/i18n'
|
|
5
|
+
import en from './en'
|
|
6
|
+
|
|
7
|
+
export default defineLocale<typeof en>()({
|
|
8
|
+
'nav.home': 'Start',
|
|
9
|
+
'nav.language': 'Sprache',
|
|
10
|
+
|
|
11
|
+
'hero.tagline': 'Ersetze diesen Hero-Text durch dein Nutzenversprechen.',
|
|
12
|
+
'hero.cta': 'Mehr erfahren →',
|
|
13
|
+
|
|
14
|
+
'features.heading': 'Funktionen',
|
|
15
|
+
'features.reactive': '<strong>Reaktive Subscriptions.</strong> Server-Status streamt per WebSocket zu den Clients.',
|
|
16
|
+
'features.tenant': '<strong>Mandantengetrennt per Voreinstellung.</strong> Die Runtime verhindert mandantenübergreifende Lese- und Schreibzugriffe automatisch.',
|
|
17
|
+
'features.conventions': '<strong>Dateikonventionen statt Konfiguration.</strong> Lege eine <code>*.route.tsx</code> an und erhalte eine streamende Query.',
|
|
18
|
+
|
|
19
|
+
'cta.heading': 'Bereit loszulegen?',
|
|
20
|
+
'cta.body': 'Bearbeite <code>src/pages/index.tsx</code>, um sie anzupassen.',
|
|
21
|
+
|
|
22
|
+
'meta.home.title': 'Start',
|
|
23
|
+
'meta.home.description': 'Eine Marketing-Landingpage, gebaut mit Voltro — vorgerendertes statisches HTML, kein JavaScript auf der Leitung.',
|
|
24
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Base catalog — the source of truth. Every other locale (de.ts) mirrors
|
|
2
|
+
// these keys EXACTLY; `defineLocale<typeof en>()` enforces that at `tsc`.
|
|
3
|
+
//
|
|
4
|
+
// Keep ICU placeholders + rich-text tags (`<strong>`, `<code>`) identical
|
|
5
|
+
// across locales — react-intl validates them at render time, and every tag
|
|
6
|
+
// needs a matching value function where the message is rendered with <T>.
|
|
7
|
+
import { defineCatalog } from '@voltro/i18n'
|
|
8
|
+
|
|
9
|
+
export default defineCatalog({
|
|
10
|
+
'nav.home': 'Home',
|
|
11
|
+
'nav.language': 'Language',
|
|
12
|
+
|
|
13
|
+
'hero.tagline': 'Replace this hero copy with your value prop.',
|
|
14
|
+
'hero.cta': 'Learn more →',
|
|
15
|
+
|
|
16
|
+
'features.heading': 'Features',
|
|
17
|
+
'features.reactive': '<strong>Reactive subscriptions.</strong> Server state streams to clients via WebSocket.',
|
|
18
|
+
'features.tenant': '<strong>Tenant-scoped by default.</strong> The runtime guards cross-tenant reads + writes automatically.',
|
|
19
|
+
'features.conventions': '<strong>File conventions, not config.</strong> Drop a <code>*.route.tsx</code>, get a streaming query.',
|
|
20
|
+
|
|
21
|
+
'cta.heading': 'Ready to start?',
|
|
22
|
+
'cta.body': 'Edit <code>src/pages/index.tsx</code> to make it yours.',
|
|
23
|
+
|
|
24
|
+
'meta.home.title': 'Home',
|
|
25
|
+
'meta.home.description': 'A marketing landing page built with Voltro — pre-rendered static HTML, zero JavaScript on the wire.',
|
|
26
|
+
} as const)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Locale mirror of `/` → renders `/de`. Re-exports the bare page verbatim
|
|
2
|
+
// (the component reads its locale from the URL via the layout's provider);
|
|
3
|
+
// `getStaticPaths` tells `voltro build` which locale-prefixed paths to emit.
|
|
4
|
+
// The default locale is NOT listed here — it lives at the bare `/`.
|
|
5
|
+
import { SUPPORTED_LOCALES, DEFAULT_LOCALE } from '../../lib/locale'
|
|
6
|
+
|
|
7
|
+
export { default } from '../index'
|
|
8
|
+
export { renderMode, interactive, meta } from '../index'
|
|
9
|
+
|
|
10
|
+
export const getStaticPaths = async (): Promise<Array<{ params: { locale: string } }>> =>
|
|
11
|
+
SUPPORTED_LOCALES
|
|
12
|
+
.filter((l) => l !== DEFAULT_LOCALE)
|
|
13
|
+
.map((locale) => ({ params: { locale } }))
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// The [locale] mirror page re-exports the bare page and adds getStaticPaths,
|
|
4
|
+
// which tells `voltro build` which locale-prefixed paths to emit. The default
|
|
5
|
+
// locale (en) is NOT listed — it lives at the bare path — so only /de is
|
|
6
|
+
// produced. We also confirm the mirror re-exports the base page + its meta.
|
|
7
|
+
|
|
8
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
9
|
+
|
|
10
|
+
// The mirror transitively imports the base page, which imports @voltro/i18n.
|
|
11
|
+
// Stub it so importing the module doesn't require the real i18n runtime.
|
|
12
|
+
vi.mock('@voltro/i18n', () => ({
|
|
13
|
+
// The locale catalogs call these at module top-level; identity mirrors the
|
|
14
|
+
// real (compile-time-only) implementations so importing them doesn't throw.
|
|
15
|
+
defineCatalog: <T,>(c: T): T => c,
|
|
16
|
+
defineLocale:
|
|
17
|
+
() =>
|
|
18
|
+
<T,>(c: T): T =>
|
|
19
|
+
c,
|
|
20
|
+
T: ({ id }: { id: string }) => id,
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
const indexMirror = await import('./index')
|
|
24
|
+
|
|
25
|
+
describe('home mirror — getStaticPaths', () => {
|
|
26
|
+
test('emits only non-default locales (just /de)', async () => {
|
|
27
|
+
const paths = await indexMirror.getStaticPaths()
|
|
28
|
+
expect(paths).toEqual([{ params: { locale: 'de' } }])
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('re-exports the base page component + its meta function', () => {
|
|
32
|
+
expect(typeof indexMirror.default).toBe('function')
|
|
33
|
+
expect(indexMirror.meta({ locale: 'de' }).title).toBe('Start')
|
|
34
|
+
expect(indexMirror.renderMode).toBe('static')
|
|
35
|
+
expect(indexMirror.interactive).toBe('none')
|
|
36
|
+
})
|
|
37
|
+
})
|