@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,66 @@
|
|
|
1
|
+
// Root layout. Strategy B (URL-prefix i18n): an INNER <I18nProvider> reads
|
|
2
|
+
// the locale FROM THE URL, so the pre-render pipeline resolves the right
|
|
3
|
+
// catalog per locale-prefixed path (and client-side nav swaps it without a
|
|
4
|
+
// full reload). The language switcher is plain <a> links — no JS needed, so
|
|
5
|
+
// this stays compatible with the pages' `interactive: 'none'`.
|
|
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 doc (e.g. /de/docs/x → /docs/x, 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. Each language
|
|
31
|
+
// is its own crawlable URL, reached by a full navigation.
|
|
32
|
+
return (
|
|
33
|
+
<nav className="topnav">
|
|
34
|
+
<a href={withLocalePrefix('/', locale)}>
|
|
35
|
+
<T id="nav.home" />
|
|
36
|
+
</a>
|
|
37
|
+
<span className="spacer" />
|
|
38
|
+
<span className="lang">
|
|
39
|
+
<span className="label">
|
|
40
|
+
<T id="nav.language" />:
|
|
41
|
+
</span>
|
|
42
|
+
{SUPPORTED_LOCALES.map((code) => (
|
|
43
|
+
<a
|
|
44
|
+
key={code}
|
|
45
|
+
href={withLocalePrefix(barePath, code)}
|
|
46
|
+
aria-current={code === locale ? 'true' : undefined}
|
|
47
|
+
>
|
|
48
|
+
{code.toUpperCase()}
|
|
49
|
+
</a>
|
|
50
|
+
))}
|
|
51
|
+
</span>
|
|
52
|
+
</nav>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default function Layout({ children }: { readonly children: ReactNode }): ReactNode {
|
|
57
|
+
const locale = useUrlLocale()
|
|
58
|
+
return (
|
|
59
|
+
<I18nProvider locale={locale} messages={CATALOGS[locale]} defaultLocale={DEFAULT_LOCALE}>
|
|
60
|
+
<div className="shell">
|
|
61
|
+
<TopNav />
|
|
62
|
+
{children}
|
|
63
|
+
</div>
|
|
64
|
+
</I18nProvider>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "frontend-docs",
|
|
3
|
+
"kind": "web",
|
|
4
|
+
"summary": "Bilingual (en/de) documentation site: catch-all docs router (`docs/[...slug].tsx`), URL-prefix i18n with a [locale] mirror emitting the locale × slug cross-product, dynamic getStaticPaths so every doc page pre-renders per locale at build time.",
|
|
5
|
+
"tags": ["web", "frontend", "docs", "documentation", "static", "i18n", "intl", "localization"]
|
|
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,61 @@
|
|
|
1
|
+
# {{projectName}} / {{appName}}
|
|
2
|
+
|
|
3
|
+
Voltro web app scaffold (template: **frontend-i18n**) — a bilingual
|
|
4
|
+
(English + German) static site using the **URL-prefix** i18n strategy.
|
|
5
|
+
|
|
6
|
+
## Boot
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm --filter @{{projectName}}/{{appName}} dev
|
|
11
|
+
# → http://localhost:{{port}}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## What this shows
|
|
15
|
+
|
|
16
|
+
- **Typed catalogs** — `src/locales/en.ts` is the source of truth;
|
|
17
|
+
`src/locales/de.ts` uses `defineLocale<typeof en>()`, which fails
|
|
18
|
+
`tsc --noEmit` if a key is missing or extra. Translations can't drift.
|
|
19
|
+
- **URL-prefix routing** — the default locale lives at the bare path
|
|
20
|
+
(`/`, `/about`); German is prefixed (`/de`, `/de/about`). Each
|
|
21
|
+
translated page is its own crawlable URL — good for SEO and
|
|
22
|
+
shareable language-specific links.
|
|
23
|
+
- **Per-locale SSG** — `src/pages/[locale]/*.tsx` mirror every page and
|
|
24
|
+
enumerate the non-default locales via `getStaticPaths`. `voltro build`
|
|
25
|
+
emits one pre-rendered HTML file per (page × locale):
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
.framework/dist/index.html ← en (default, bare path)
|
|
29
|
+
.framework/dist/de/index.html ← de mirror
|
|
30
|
+
.framework/dist/about/index.html ← en
|
|
31
|
+
.framework/dist/de/about/index.html ← de mirror
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- **Localised `<title>`** — each page's `meta` is a function of
|
|
35
|
+
`{ locale }`, so every variant's `<title>` / `<meta description>` is
|
|
36
|
+
in the right language in the pre-rendered HTML (a plain `meta` object
|
|
37
|
+
would leave the head English on `/de`).
|
|
38
|
+
- **Client-side language switch** — the nav's URL-driven
|
|
39
|
+
`<I18nProvider>` (in `src/pages/layout.tsx`) swaps catalogs on
|
|
40
|
+
navigation between `/about` and `/de/about` without a full reload.
|
|
41
|
+
|
|
42
|
+
## Add a locale
|
|
43
|
+
|
|
44
|
+
1. Add the code to `SUPPORTED_LOCALES` in `src/lib/locale.ts` AND to
|
|
45
|
+
`locales` in `app.config.ts`.
|
|
46
|
+
2. Add `src/locales/<code>.ts` (mirror `en.ts`; the type-check enforces
|
|
47
|
+
parity).
|
|
48
|
+
3. Add it to `CATALOGS` in `src/lib/locale.ts`.
|
|
49
|
+
|
|
50
|
+
The `[locale]` mirrors pick it up automatically — `getStaticPaths`
|
|
51
|
+
reads `SUPPORTED_LOCALES`.
|
|
52
|
+
|
|
53
|
+
## Add a page
|
|
54
|
+
|
|
55
|
+
Create `src/pages/<name>.tsx` (default locale) **and** a one-line mirror
|
|
56
|
+
`src/pages/[locale]/<name>.tsx` that re-exports it (copy
|
|
57
|
+
`[locale]/about.tsx`). Without the mirror, only the default-locale URL
|
|
58
|
+
is emitted.
|
|
59
|
+
|
|
60
|
+
See AGENTS.md › *Internationalization (`@voltro/i18n`)* for the full
|
|
61
|
+
reference, including the cookie-only strategy product dashboards use.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Web app for the {{projectName}} project — a BILINGUAL static site.
|
|
2
|
+
//
|
|
3
|
+
// Strategy B (URL-prefix i18n): the default locale lives at the bare path
|
|
4
|
+
// (`/`, `/about`) and every other locale is prefixed (`/de`, `/de/about`).
|
|
5
|
+
// Each translated page is its own crawlable URL — better SEO + shareable
|
|
6
|
+
// language-specific links. The other strategy (cookie-only, same URL for
|
|
7
|
+
// every locale) is what product dashboards use; see AGENTS.md › i18n.
|
|
8
|
+
//
|
|
9
|
+
// `locales` here is load-bearing: it tells `voltro build` to pre-render a
|
|
10
|
+
// per-locale HTML variant for every `[locale]/…` mirror page, each wrapped
|
|
11
|
+
// in the right catalog's <I18nProvider>. Every code listed MUST have a
|
|
12
|
+
// matching `src/locales/<code>.ts` or boot fails.
|
|
13
|
+
import { defineEnv, envVar } from '@voltro/env'
|
|
14
|
+
|
|
15
|
+
// Public, build-time env (baked into the bundle; read via `publicEnv`).
|
|
16
|
+
// Never a secret here — a static site has no server to hide one.
|
|
17
|
+
export const env = defineEnv({
|
|
18
|
+
VOLTRO_PUBLIC_SITE_NAME: envVar.string({ access: 'public', default: '{{capProjectName}}' }),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
type: 'web' as const,
|
|
23
|
+
name: '{{capProjectName}}{{capAppName}}',
|
|
24
|
+
port: {{port}},
|
|
25
|
+
// Follow the visitor's OS theme, set before first paint (no flash).
|
|
26
|
+
theme: 'system' as const,
|
|
27
|
+
// The shipped catalogs. `defaultLocale` MUST be one of `locales`.
|
|
28
|
+
// Keep this in sync with SUPPORTED_LOCALES / DEFAULT_LOCALE in
|
|
29
|
+
// src/lib/locale.ts — that file drives the [locale] mirror routing.
|
|
30
|
+
locales: ['en', 'de'] as const,
|
|
31
|
+
defaultLocale: 'en' as const,
|
|
32
|
+
env,
|
|
33
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"test": "voltro test"
|
|
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/web": "0.1.0",
|
|
19
|
+
"react": "^19.0.0",
|
|
20
|
+
"react-dom": "^19.0.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/react": "^19.0.0",
|
|
24
|
+
"@types/react-dom": "^19.0.0",
|
|
25
|
+
"typescript": "^5.7.0",
|
|
26
|
+
"vitest": "^3.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* Plain CSS — no Tailwind, so there's no @source scanner to configure.
|
|
2
|
+
Swap in @voltro/ui-shadcn or Tailwind later if you want; see AGENTS.md. */
|
|
3
|
+
:root {
|
|
4
|
+
--bg: #ffffff;
|
|
5
|
+
--fg: #0a0a0a;
|
|
6
|
+
--muted: #6b7280;
|
|
7
|
+
--accent: #4f46e5;
|
|
8
|
+
--border: #e5e7eb;
|
|
9
|
+
}
|
|
10
|
+
:root.dark {
|
|
11
|
+
--bg: #0a0a0a;
|
|
12
|
+
--fg: #fafafa;
|
|
13
|
+
--muted: #9ca3af;
|
|
14
|
+
--accent: #818cf8;
|
|
15
|
+
--border: #1f2937;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
* { box-sizing: border-box; }
|
|
19
|
+
body {
|
|
20
|
+
margin: 0;
|
|
21
|
+
background: var(--bg);
|
|
22
|
+
color: var(--fg);
|
|
23
|
+
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
24
|
+
line-height: 1.6;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.shell { max-width: 42rem; margin: 0 auto; padding: 2rem 1.25rem; }
|
|
28
|
+
|
|
29
|
+
nav.topnav {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: 1rem;
|
|
33
|
+
padding-bottom: 1.5rem;
|
|
34
|
+
margin-bottom: 1.5rem;
|
|
35
|
+
border-bottom: 1px solid var(--border);
|
|
36
|
+
}
|
|
37
|
+
nav.topnav a { color: var(--fg); text-decoration: none; font-weight: 500; }
|
|
38
|
+
nav.topnav a:hover { color: var(--accent); }
|
|
39
|
+
nav.topnav .spacer { flex: 1; }
|
|
40
|
+
nav.topnav .lang { display: flex; gap: 0.5rem; font-size: 0.875rem; }
|
|
41
|
+
nav.topnav .lang .label { color: var(--muted); }
|
|
42
|
+
nav.topnav .lang a { font-weight: 400; }
|
|
43
|
+
nav.topnav .lang a[aria-current='true'] { color: var(--accent); font-weight: 600; }
|
|
44
|
+
|
|
45
|
+
.muted { color: var(--muted); }
|
|
46
|
+
code { background: color-mix(in oklab, var(--fg) 8%, transparent); padding: 0.1em 0.35em; border-radius: 4px; }
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Ambient declarations.
|
|
2
|
+
//
|
|
3
|
+
// `declare module '*.css'`: TypeScript with `moduleResolution: Bundler`
|
|
4
|
+
// rejects a bare `import './globals.css'` unless the asset module is
|
|
5
|
+
// declared. Vite resolves it at build time; the declaration is type-only.
|
|
6
|
+
declare module '*.css'
|
|
@@ -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/about')).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('/about')).toBe('en')
|
|
43
|
+
expect(localeFromPathname('/fr/about')).toBe('en')
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
describe('withLocalePrefix', () => {
|
|
48
|
+
test('leaves default-locale paths bare', () => {
|
|
49
|
+
expect(withLocalePrefix('/about', 'en')).toBe('/about')
|
|
50
|
+
expect(withLocalePrefix('/', 'en')).toBe('/')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('prefixes non-default locales, mapping / to /<locale>', () => {
|
|
54
|
+
expect(withLocalePrefix('/about', 'de')).toBe('/de/about')
|
|
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/about')).toBe('/about')
|
|
62
|
+
expect(stripLocalePrefix('/de')).toBe('/')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('leaves an unprefixed path unchanged', () => {
|
|
66
|
+
expect(stripLocalePrefix('/about')).toBe('/about')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('round-trips with withLocalePrefix', () => {
|
|
70
|
+
expect(stripLocalePrefix(withLocalePrefix('/about', 'de'))).toBe('/about')
|
|
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.about': 'Über',
|
|
10
|
+
'nav.language': 'Sprache',
|
|
11
|
+
|
|
12
|
+
'home.title': 'Hallo, {name}',
|
|
13
|
+
'home.tagline': 'Eine zweisprachige statische Website, gebaut mit Voltro.',
|
|
14
|
+
'home.body': 'Diese Seite wird pro Sprache einmal vorgerendert. Die englische Version liegt am bloßen Pfad; Deutsch wird unter /de ausgeliefert. Wechsle die Sprache in der Navigation — jede Sprache ist ihre eigene crawlbare URL.',
|
|
15
|
+
'home.activeLocale': 'Du liest die {locale}-Version.',
|
|
16
|
+
|
|
17
|
+
'about.title': 'Über dieses Template',
|
|
18
|
+
'about.body': 'frontend-i18n zeigt die URL-Prefix-i18n-Strategie: typisierte Kataloge in src/locales, ein [locale]-Mirror für jede Seite und sprachspezifische Seitentitel. Übersetzungen driften nie auseinander — nicht-Basis-Kataloge brechen den Type-Check, wenn ein Schlüssel fehlt.',
|
|
19
|
+
|
|
20
|
+
'meta.home.title': 'Start — Voltro i18n',
|
|
21
|
+
'meta.home.description': 'Eine zweisprachige statische Website mit Voltro — Englisch am bloßen Pfad, Deutsch unter /de.',
|
|
22
|
+
'meta.about.title': 'Über — Voltro i18n',
|
|
23
|
+
'meta.about.description': 'Wie das URL-Prefix-i18n-Template verdrahtet ist: typisierte Kataloge, [locale]-Mirrors, sprachspezifisches SSG.',
|
|
24
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
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 (`{name}`, `{count, plural, …}`) identical across
|
|
5
|
+
// locales — react-intl validates them at render time.
|
|
6
|
+
import { defineCatalog } from '@voltro/i18n'
|
|
7
|
+
|
|
8
|
+
export default defineCatalog({
|
|
9
|
+
'nav.home': 'Home',
|
|
10
|
+
'nav.about': 'About',
|
|
11
|
+
'nav.language': 'Language',
|
|
12
|
+
|
|
13
|
+
'home.title': 'Hello, {name}',
|
|
14
|
+
'home.tagline': 'A bilingual static site built with Voltro.',
|
|
15
|
+
'home.body': 'This page is pre-rendered once per locale. The English version lives at the bare path; German is served from /de. Switch the language in the nav — each locale is its own crawlable URL.',
|
|
16
|
+
'home.activeLocale': 'You are reading the {locale} version.',
|
|
17
|
+
|
|
18
|
+
'about.title': 'About this template',
|
|
19
|
+
'about.body': 'frontend-i18n shows the URL-prefix i18n strategy: typed catalogs in src/locales, a [locale] mirror for every page, and per-locale page titles. Translations never drift — non-base catalogs fail the type-check if a key is missing.',
|
|
20
|
+
|
|
21
|
+
'meta.home.title': 'Home — Voltro i18n',
|
|
22
|
+
'meta.home.description': 'A bilingual static site built with Voltro — English at the bare path, German under /de.',
|
|
23
|
+
'meta.about.title': 'About — Voltro i18n',
|
|
24
|
+
'meta.about.description': 'How the URL-prefix i18n template is wired: typed catalogs, [locale] mirrors, per-locale SSG.',
|
|
25
|
+
} as const)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Locale mirror of `/about` → renders `/de/about`. Same pattern as the
|
|
2
|
+
// home mirror: re-export the bare page, enumerate non-default locales.
|
|
3
|
+
import { SUPPORTED_LOCALES, DEFAULT_LOCALE } from '../../lib/locale'
|
|
4
|
+
|
|
5
|
+
export { default } from '../about'
|
|
6
|
+
export { renderMode, meta } from '../about'
|
|
7
|
+
|
|
8
|
+
export const getStaticPaths = async (): Promise<Array<{ params: { locale: string } }>> =>
|
|
9
|
+
SUPPORTED_LOCALES
|
|
10
|
+
.filter((l) => l !== DEFAULT_LOCALE)
|
|
11
|
+
.map((locale) => ({ params: { locale } }))
|
|
@@ -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, 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,50 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// The [locale] mirror pages re-export the bare page and add 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 each mirror re-exports the base page + its meta.
|
|
7
|
+
|
|
8
|
+
import { describe, expect, test, vi } from 'vitest'
|
|
9
|
+
|
|
10
|
+
// The mirrors transitively import the base pages, which import @voltro/i18n.
|
|
11
|
+
// Stub it so importing the modules 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
|
+
useLocale: () => 'en',
|
|
21
|
+
useT: (id: string) => id,
|
|
22
|
+
T: ({ id }: { id: string }) => id,
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
const indexMirror = await import('./index')
|
|
26
|
+
const aboutMirror = await import('./about')
|
|
27
|
+
|
|
28
|
+
describe('home mirror — getStaticPaths', () => {
|
|
29
|
+
test('emits only non-default locales (just /de)', async () => {
|
|
30
|
+
const paths = await indexMirror.getStaticPaths()
|
|
31
|
+
expect(paths).toEqual([{ params: { locale: 'de' } }])
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('re-exports the base page component + its meta function', () => {
|
|
35
|
+
expect(typeof indexMirror.default).toBe('function')
|
|
36
|
+
expect(indexMirror.meta({ locale: 'de' }).title).toBe('Start — Voltro i18n')
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
describe('about mirror — getStaticPaths', () => {
|
|
41
|
+
test('emits only non-default locales (just /de)', async () => {
|
|
42
|
+
const paths = await aboutMirror.getStaticPaths()
|
|
43
|
+
expect(paths).toEqual([{ params: { locale: 'de' } }])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('re-exports the base page component + its meta function', () => {
|
|
47
|
+
expect(typeof aboutMirror.default).toBe('function')
|
|
48
|
+
expect(aboutMirror.meta({ locale: 'en' }).title).toBe('About — Voltro i18n')
|
|
49
|
+
})
|
|
50
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
//
|
|
3
|
+
// About page (`/about` = en, `/de/about` = de). Same shape as the home test:
|
|
4
|
+
// localized meta() read from the real catalog + localized body render via a
|
|
5
|
+
// mocked @voltro/i18n that resolves against the real catalogs.
|
|
6
|
+
|
|
7
|
+
import { afterEach, describe, expect, test, vi } from 'vitest'
|
|
8
|
+
import { act, createElement, type ReactNode } from 'react'
|
|
9
|
+
import { createRoot, type Root } from 'react-dom/client'
|
|
10
|
+
import en from '../locales/en'
|
|
11
|
+
import de from '../locales/de'
|
|
12
|
+
|
|
13
|
+
const catalogs: Record<string, Record<string, string>> = { en, de }
|
|
14
|
+
let currentLocale = 'en'
|
|
15
|
+
|
|
16
|
+
vi.mock('@voltro/i18n', () => ({
|
|
17
|
+
// The locale catalogs call these at module top-level; identity mirrors the
|
|
18
|
+
// real (compile-time-only) implementations so importing them doesn't throw.
|
|
19
|
+
defineCatalog: <T,>(c: T): T => c,
|
|
20
|
+
defineLocale:
|
|
21
|
+
() =>
|
|
22
|
+
<T,>(c: T): T =>
|
|
23
|
+
c,
|
|
24
|
+
T: ({ id }: { id: string }) => catalogs[currentLocale]?.[id] ?? id,
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
const { default: About, meta, renderMode } = await import('./about')
|
|
28
|
+
|
|
29
|
+
;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
|
|
30
|
+
|
|
31
|
+
let container: HTMLDivElement
|
|
32
|
+
let root: Root
|
|
33
|
+
|
|
34
|
+
const render = (node: ReactNode): void => {
|
|
35
|
+
container = document.createElement('div')
|
|
36
|
+
document.body.appendChild(container)
|
|
37
|
+
act(() => {
|
|
38
|
+
root = createRoot(container)
|
|
39
|
+
root.render(node)
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
currentLocale = 'en'
|
|
45
|
+
if (root) act(() => root.unmount())
|
|
46
|
+
container?.remove()
|
|
47
|
+
document.body.innerHTML = ''
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('about — config + localized meta', () => {
|
|
51
|
+
test('is a static page', () => {
|
|
52
|
+
expect(renderMode).toBe('static')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('meta() is localized per locale', () => {
|
|
56
|
+
expect(meta({ locale: 'en' }).title).toBe('About — Voltro i18n')
|
|
57
|
+
expect(meta({ locale: 'de' }).title).toBe('Über — Voltro i18n')
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
describe('about — localized render', () => {
|
|
62
|
+
test('renders the English title + body for locale en', () => {
|
|
63
|
+
currentLocale = 'en'
|
|
64
|
+
render(createElement(About))
|
|
65
|
+
expect(container.querySelector('h1')!.textContent).toBe('About this template')
|
|
66
|
+
expect(container.textContent).toContain('URL-prefix i18n strategy')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('renders the German title + body for locale de', () => {
|
|
70
|
+
currentLocale = 'de'
|
|
71
|
+
render(createElement(About))
|
|
72
|
+
expect(container.querySelector('h1')!.textContent).toBe('Über dieses Template')
|
|
73
|
+
expect(container.textContent).toContain('URL-Prefix-i18n-Strategie')
|
|
74
|
+
})
|
|
75
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// About, mounted at `/about` (default locale) and mirrored at `/de/about`
|
|
2
|
+
// by src/pages/[locale]/about.tsx. Same per-locale `meta` function pattern
|
|
3
|
+
// as the home page.
|
|
4
|
+
|
|
5
|
+
import type { ReactNode } from 'react'
|
|
6
|
+
import type { PageMeta } from '@voltro/web'
|
|
7
|
+
import { T } from '@voltro/i18n'
|
|
8
|
+
import { getCatalog } from '../lib/locale'
|
|
9
|
+
|
|
10
|
+
export const renderMode = 'static' as const
|
|
11
|
+
|
|
12
|
+
export const meta = ({ locale }: { locale: string }): PageMeta => {
|
|
13
|
+
const c = getCatalog(locale)
|
|
14
|
+
return {
|
|
15
|
+
title: c['meta.about.title'],
|
|
16
|
+
description: c['meta.about.description'],
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default function About(): ReactNode {
|
|
21
|
+
return (
|
|
22
|
+
<article>
|
|
23
|
+
<h1>
|
|
24
|
+
<T id="about.title" />
|
|
25
|
+
</h1>
|
|
26
|
+
<p>
|
|
27
|
+
<T id="about.body" />
|
|
28
|
+
</p>
|
|
29
|
+
</article>
|
|
30
|
+
)
|
|
31
|
+
}
|