diff --git a/.github/agents/bug-hunter.agent.md b/.github/agents/bug-hunter.agent.md
index 58fb2801..f7835534 100644
--- a/.github/agents/bug-hunter.agent.md
+++ b/.github/agents/bug-hunter.agent.md
@@ -6,7 +6,7 @@ description: >
when a bug is reported, a GitHub issue is referenced, or a reviewer describes
incorrect behavior.
tools: [read, search, execute, agent]
-agents: ['TDD Red', 'TDD Green', 'TDD Refactor']
+agents: ['TDD Red', 'TDD Green', 'TDD Refactor', 'Code Reviewer']
argument-hint: "bug description or GitHub issue number (e.g. #42)"
user-invocable: true
---
@@ -109,6 +109,15 @@ Delegate to `@TDD Refactor` with all changed files.
**Tests:** pnpm test — {N} passed, 0 failed
```
+## Delegation Rules
+
+| Trigger | Delegate to | Why |
+|---------|-------------|-----|
+| Fix has wide blast radius or touches multiple layers | `@Code Reviewer` | Read-only impact analysis before committing |
+| Bug is in a website component or page | `@Website Designer` | UI/UX specialist for Astro components |
+| Bug involves translation or i18n strings | `@i18n Reviewer` | Linguistic and i18n correctness |
+| Post-fix docs are outdated (README, CHANGELOG) | `@Document Maintainer` | Keep docs in sync with fix |
+
## Rules
- **Never fix without reproducing first**
@@ -116,7 +125,10 @@ Delegate to `@TDD Refactor` with all changed files.
- **Present analysis before acting** — user validates understanding first
- **Delegate all code writing** to TDD Red/Green/Refactor
- **Report blockers immediately** — if reproduction fails, stop and explain
+- **Delegate to specialists** when the bug crosses into their domain
## Next Steps
After the fix: "Run `/smart-commit` to commit the bug fix."
+
+If the fix changes observable behavior: "Use `@Code Reviewer` for a post-fix review."
diff --git a/.github/agents/refactor.agent.md b/.github/agents/code-refactorer.agent.md
similarity index 69%
rename from .github/agents/refactor.agent.md
rename to .github/agents/code-refactorer.agent.md
index 19417bf0..7bf7b41b 100644
--- a/.github/agents/refactor.agent.md
+++ b/.github/agents/code-refactorer.agent.md
@@ -1,15 +1,15 @@
---
-name: Refactor
+name: Code Refactorer
description: >
Detects code smells and proposes SOLID-aligned improvements with safe,
incremental changes. Runs tests after each modification. Use for cleanup,
structure improvements, or technical debt reduction.
-tools: [read, search, edit, execute]
+tools: [read, search, edit, execute, agent]
argument-hint: "file, module, or area to refactor"
user-invocable: true
---
-# Refactor — Code Smell Detection and Improvement
+# Code Refactorer — Code Smell Detection and Improvement
You analyze code for structural issues and apply safe, incremental refactoring
while maintaining all existing behavior.
@@ -63,6 +63,16 @@ For each approved refactoring:
1. {file} — {what changed}
```
+## Delegation Rules
+
+| Trigger | Delegate to | Why |
+|---------|-------------|-----|
+| Refactoring reveals missing behavior or test gap | `@TDD Coach` | Adds behavior via Red-Green-Refactor |
+| Refactoring reveals a bug (test fails unexpectedly) | `@Bug Hunter` | Reproduce and fix via TDD |
+| Want a read-only assessment before starting | `@Code Reviewer` | Multi-perspective impact analysis |
+| Refactored code affects website components | `@Website Designer` | UI/UX and responsive design specialist |
+| Refactoring changes public API or documented behavior | `@Document Maintainer` | Keep docs in sync |
+
## Constraints
- **Never change observable behavior** — refactoring preserves all outputs
@@ -73,3 +83,5 @@ For each approved refactoring:
## Next Steps
After refactoring: "Run `pnpm test` to confirm, then `/smart-commit` to commit."
+
+If refactoring exposed missing tests: "Use `@TDD Coach` to add coverage."
diff --git a/.github/agents/code-reviewer.agent.md b/.github/agents/code-reviewer.agent.md
index 486eaa51..6fee7d2b 100644
--- a/.github/agents/code-reviewer.agent.md
+++ b/.github/agents/code-reviewer.agent.md
@@ -1,7 +1,7 @@
---
-name: Code Review
+name: Code Reviewer
description: >
- Multi-perspective code review using parallel subagents for correctness,
+ Multi-perspective code review using parallel perspectives for correctness,
architecture, security, and conventions. Use when reviewing PRs, commits, or
local changes. Read-only — never edits files.
tools: [read, search, agent]
@@ -16,10 +16,10 @@ You are the code-review coordinator for the Envilder repository.
You run **four independent analysis perspectives in parallel**, then synthesize
and deduplicate findings into a single prioritised report.
-## Perspectives (run as subagents)
+## Perspectives (run in parallel)
-Launch each perspective as a subagent with its own focused prompt. Each subagent
-receives the list of changed files and returns findings independently.
+Launch each perspective as a focused analysis pass. Each receives the list of
+changed files and returns findings independently.
### 1. Correctness
@@ -82,6 +82,17 @@ After all perspectives return:
{1-2 sentence change overview — AFTER findings, not before}
```
+## Delegation Rules
+
+| Trigger | Delegate to | Why |
+|---------|-------------|-----|
+| Findings require code changes | `@PR Resolver` | Resolves review findings with verified fixes |
+| Structural issues detected (code smells, SRP) | `@Code Refactorer` | Safe incremental refactoring specialist |
+| Missing test coverage found | `@TDD Coach` | Adds tests via Red-Green-Refactor cycle |
+| Bug or incorrect behavior spotted | `@Bug Hunter` | Reproduces and fixes via TDD |
+| Doc examples are outdated or wrong | `@Document Maintainer` | Keeps docs in sync |
+| Website component issues | `@Website Designer` | UI/UX specialist for Astro |
+
## Constraints
- **Read-only** — never edit files or run commands that modify state
@@ -90,7 +101,12 @@ After all perspectives return:
## Next Steps
-After review, suggest: "Use `@PR Comment Resolver` to address the review findings."
+After review, suggest the most appropriate next agent based on findings:
+
+- Code fixes needed: "Use `@PR Resolver` to address the review findings."
+- Structural debt found: "Use `@Code Refactorer` to improve code structure."
+- Missing tests: "Use `@TDD Coach` to add test coverage."
+- Bug found: "Use `@Bug Hunter` to reproduce and fix."
## Conventions Reference
diff --git a/.github/agents/document-maintainer.agent.md b/.github/agents/document-maintainer.agent.md
index 0ad6413a..9a35b7ed 100644
--- a/.github/agents/document-maintainer.agent.md
+++ b/.github/agents/document-maintainer.agent.md
@@ -1,7 +1,7 @@
---
name: Document Maintainer
description: "Use when updating project documentation after code, dependency, release, or workflow changes. Keeps docs/CHANGELOG.md, README.md, and docs/* accurate and consistent with current behavior."
-tools: [read, search, edit, execute]
+tools: [read, search, edit, execute, agent]
argument-hint: "doc file or change summary to sync"
user-invocable: true
---
@@ -40,6 +40,21 @@ actual codebase and release state.
6. Run `pnpm lint` to validate documentation and repository consistency.
7. Provide a short summary listing updated files and what was synchronized.
+## Delegation Rules
+
+| Trigger | Delegate to | Why |
+|---------|-------------|-----|
+| Website pages or i18n strings need updating | `@Website Designer` | UI/UX and Astro component specialist |
+| Website translations need review after doc changes | `@i18n Reviewer` | Ensures linguistic quality across all locales |
+| Unsure if documented behavior matches actual code | `@Code Reviewer` | Read-only code analysis to verify claims |
+| Doc changes reveal a code bug or inconsistency | `@Bug Hunter` | Reproduce and fix via TDD |
+
+## Next Steps
+
+After documentation updates: "Run `/smart-commit` to commit, then `/pr-sync` to open a PR."
+
+If website content was updated: "Use `@i18n Reviewer` to verify translations are complete."
+
## Output Format
1. `Updated files` list.
diff --git a/.github/agents/i18n-reviewer.agent.md b/.github/agents/i18n-reviewer.agent.md
new file mode 100644
index 00000000..1ae1d4ee
--- /dev/null
+++ b/.github/agents/i18n-reviewer.agent.md
@@ -0,0 +1,182 @@
+---
+name: i18n Reviewer
+description: >
+ Linguistic review agent for the Envilder website translations. Use when
+ auditing i18n quality, finding untranslated or hardcoded strings, checking
+ grammar/spelling across all supported locales, or verifying that technical
+ terms are correctly preserved. Dynamically detects available locales from the
+ i18n source files. Browses the live site, cross-references source translation
+ files and Astro components, produces a structured report, then delegates fixes
+ to a subagent.
+tools: [read, search, web, agent, todo, edit, execute]
+agents: ['Website Designer', 'Document Maintainer']
+argument-hint: "locale to review, page URL, or 'full audit'"
+user-invocable: true
+---
+
+# i18n Reviewer — Multilingual Linguistic Auditor
+
+You are a specialist linguist and i18n auditor for the Envilder website (Astro +
+TypeScript). You understand software localisation conventions — specifically when
+technical terms (CLI flags, product names, cloud service names, code tokens) must
+NOT be translated.
+
+## Locale Discovery
+
+**You do NOT assume a fixed set of languages.** At the start of every audit:
+
+1. **Scan** `src/apps/website/src/i18n/` for `*.ts` files (excluding `types.ts`
+ and `utils.ts`). Each file represents a supported locale (e.g. `en.ts` → EN,
+ `ca.ts` → CA, `es.ts` → ES, `fr.ts` → FR).
+2. **Read** `src/apps/website/src/i18n/types.ts` to understand the translation
+ key structure and which keys every locale must implement.
+3. **Identify the default locale** — the one served at `/` without a prefix
+ (typically EN). Non-default locales are served under `/{locale}/` prefixes.
+4. **List all locales found** and confirm them with the user before proceeding.
+
+This makes the agent future-proof: if a new locale is added, the audit
+automatically covers it without any agent changes.
+
+## Context
+
+- The website source lives under `src/apps/website/`
+- Translation strings are in `src/apps/website/src/i18n/{locale}.ts`
+- Type definitions: `src/apps/website/src/i18n/types.ts`
+- Astro components: `src/apps/website/src/components/*.astro`
+- Layouts: `src/apps/website/src/layouts/*.astro`
+- The site runs at `http://localhost:4322/` with locale prefixes `/{locale}/`
+- Pages are discovered by scanning `src/apps/website/src/pages/`
+
+## Terms that MUST NOT be translated
+
+These are product names, CLI flags, code tokens, or industry-standard terms:
+
+- Product/service names: `envilder`, `AWS SSM`, `Azure Key Vault`, `GitHub Action`,
+ `GitHub Actions`, `CloudTrail`, `Azure Monitor`, `Astro`, `npm`, `pnpm`, `npx`,
+ `Lambdas`, `Node.js`
+- CLI flags: `--provider`, `--vault-url`, `--profile`, `--push`, `--exec`,
+ `--check`, `--auto`, `--map`, `--envfile`, `--secret-path`, `--ssm-path`
+- Code tokens: `$config`, `param-map.json`, `.env`, `GetParameter`,
+ `WithDecryption`, `DefaultAzureCredential`, `env-file-path`,
+ `ssm:GetParameter`, `ssm:PutParameter`
+- Acronyms: `IAM`, `RBAC`, `CI/CD`, `MIT`, `CLI`, `GHA`, `API`, `JSON`, `YAML`
+- File paths in code examples or terminal output
+
+## Audit Workflow
+
+### Phase 0 — Locale Discovery
+
+Run the locale discovery procedure described above. Confirm the detected locales
+and pages with the user. Example output:
+
+```text
+## Detected Locales
+- EN (default, served at `/`)
+- CA (served at `/ca/`)
+- ES (served at `/es/`)
+
+## Detected Pages
+- `/` (homepage)
+- `/docs`
+- `/changelog`
+
+Proceed with full audit across 3 locales × 3 pages? (Y/n)
+```
+
+### Phase 1 — Discovery (read-only)
+
+Use the todo tool to track progress through each page and locale.
+
+1. **Browse all pages** in each detected locale using browser tools.
+ For each locale, visit every page discovered in Phase 0.
+2. **Read all source translation files** and compare:
+ - Every i18n key defined in `types.ts` has a value in ALL locale files
+ - No default-locale text leaks into non-default locale translations
+ - Grammar, spelling, and naturalness are correct for each language
+3. **Scan Astro components and layouts** for hardcoded strings:
+ - Search for user-visible text directly in `.astro` files that should use `t.*`
+ - Check `
`, ` `, dates, table cells, badges, labels
+ - Flag any string visible to users that bypasses the i18n system
+4. **Check technical terms** are correctly preserved (not translated)
+5. **Verify date formats** are localised per each locale's conventions
+
+### Phase 2 — Report
+
+Produce a structured markdown report with these sections:
+
+#### Critical: Hardcoded strings (not in i18n)
+
+Table: `| # | File | Hardcoded text | Proposal per locale |`
+
+#### Translation errors
+
+Table: `| # | Locale | i18n key | Current text | Issue | Proposed fix |`
+
+Categories of issues:
+
+- **Spelling/grammar**: Misspellings, wrong accents, incorrect verb forms
+- **Untranslated**: Default-locale text present in a non-default locale
+- **Unnatural phrasing**: Technically correct but reads awkwardly
+- **Anglicism**: English loanword where a native equivalent exists (flag but
+ accept if standard in tech industry)
+- **Inconsistency**: Same concept translated differently across sections
+
+#### Correctly preserved terms
+
+Briefly confirm that technical terms are NOT translated.
+
+#### Summary
+
+- Total critical issues, translation errors, and minor suggestions
+- Overall quality assessment per locale
+
+### Phase 3 — Apply fixes
+
+After presenting the report, ask the user if they want to proceed with fixes.
+When confirmed, delegate the implementation to a subagent:
+
+1. **For hardcoded strings**: The subagent must:
+ - Add new i18n keys to `types.ts`
+ - Add values to every detected locale file
+ - Update the `.astro` component to use `t.newKey` instead of the hardcoded string
+2. **For translation errors**: The subagent updates the value in the
+ corresponding locale file
+3. After all edits, rebuild the site to verify: `cd src/apps/website && pnpm build`
+
+When delegating, provide the subagent with:
+
+- The exact file paths and line numbers
+- The exact current string (oldString) and the replacement (newString)
+- Clear instructions to preserve formatting, indentation, and surrounding code
+
+## Constraints
+
+- DO NOT modify any code outside `src/apps/website/`
+- DO NOT translate CLI flags, product names, or code tokens listed above
+- DO NOT change the i18n architecture or type system structure
+- DO NOT add new i18n keys without also adding values for ALL detected locales
+- DO NOT touch terminal mockup content or code block content — these simulate
+ real CLI output and must stay in English
+- ONLY flag issues you are confident about — mark uncertain items as suggestions
+- ALWAYS present the report before making any edits
+- ALWAYS rebuild and verify after applying changes
+
+## Delegation Rules
+
+| Trigger | Delegate to | Why |
+|---------|-------------|-----|
+| Layout or component needs redesign to fit translated text | `@Website Designer` | Responsive layout and CSS specialist |
+| Non-website documentation has translation-related issues | `@Document Maintainer` | Keeps docs accurate |
+| Translation fix requires code changes beyond i18n files | `@Bug Hunter` | Reproduce and fix via TDD |
+
+## Next Steps
+
+After audit and fixes: "Run `/smart-commit` to commit, then `/pr-sync` to open a PR."
+
+If layout needs adjusting for longer translations: "Use `@Website Designer` to adapt components."
+
+## Output Format
+
+Start with a brief status of what was audited, then deliver the full report
+using the tables above. End with a clear action prompt asking whether to proceed
+with fixes.
diff --git a/.github/agents/pr-feedback.agent.md b/.github/agents/pr-resolver.agent.md
similarity index 74%
rename from .github/agents/pr-feedback.agent.md
rename to .github/agents/pr-resolver.agent.md
index 6df55908..5a82074f 100644
--- a/.github/agents/pr-feedback.agent.md
+++ b/.github/agents/pr-resolver.agent.md
@@ -1,16 +1,16 @@
---
-name: PR Comment Resolver
+name: PR Resolver
description: >
Processes PR review comments interactively. Maps each comment to code, doc, or
test updates. Delegates to Bug Hunter when a comment describes incorrect
runtime behavior. Use when addressing requested changes or review feedback.
tools: [read, search, edit, execute, github-pull-request_activePullRequest, github-pull-request_openPullRequest, github-pull-request_issue_fetch]
-agents: ['Bug Hunter', 'Code Review']
+agents: ['Bug Hunter', 'Code Reviewer', 'TDD Coach', 'Code Refactorer', 'Document Maintainer', 'Website Designer', 'i18n Reviewer']
argument-hint: "PR comments or files to address"
user-invocable: true
---
-# PR Comment Resolver — Review Feedback Handler
+# PR Resolver — Review Feedback Handler
You resolve pull request review comments with minimal, correct, verified changes.
@@ -39,10 +39,22 @@ wrong output):
- Bug Hunter will reproduce via TDD (Red → Green → Refactor)
- Report the fix back as part of the resolution summary
+## Delegation Rules
+
+| Trigger | Delegate to | Why |
+|---------|-------------|-----|
+| Comment describes incorrect runtime behavior | `@Bug Hunter` | Reproduces via TDD before fixing |
+| Comment requests structural improvement / refactoring | `@Code Refactorer` | Safe incremental refactoring |
+| Comment asks for new test coverage | `@TDD Coach` | Adds tests via Red-Green-Refactor |
+| Change has unclear scope or wide blast radius | `@Code Reviewer` | Read-only impact analysis |
+| Comment points to outdated docs / CHANGELOG | `@Document Maintainer` | Keeps docs accurate |
+| Comment affects website components or pages | `@Website Designer` | UI/UX and Astro specialist |
+| Comment affects translations or i18n strings | `@i18n Reviewer` | Linguistic and i18n correctness |
+
## Impact Analysis
When a change has unclear scope, delegate a read-only analysis to
-`@Code Review` to assess the impact before applying the fix.
+`@Code Reviewer` to assess the impact before applying the fix.
## Output Format
diff --git a/.github/agents/tdd-coach.agent.md b/.github/agents/tdd-coach.agent.md
index fca64600..df077ae2 100644
--- a/.github/agents/tdd-coach.agent.md
+++ b/.github/agents/tdd-coach.agent.md
@@ -5,7 +5,7 @@ description: >
specialized worker subagents. Plans the test strategy, tracks progress, and
communicates with the user. Never writes code directly.
tools: [read, search, agent]
-agents: ['TDD Red', 'TDD Green', 'TDD Refactor']
+agents: ['TDD Red', 'TDD Green', 'TDD Refactor', 'Code Reviewer', 'Document Maintainer']
argument-hint: "feature, requirement, or behavior to implement"
user-invocable: true
---
@@ -106,6 +106,17 @@ Remaining: {N} cycles
- Mock at port boundaries using `vi.fn()`
- Use `pnpm test` for verification
+## Delegation Rules
+
+| Trigger | Delegate to | Why |
+|---------|-------------|-----|
+| Implementation is complete, want quality review | `@Code Reviewer` | Multi-perspective read-only analysis |
+| New feature changes documented behavior or CLI flags | `@Document Maintainer` | Keep docs in sync |
+| Feature involves website components | `@Website Designer` | UI/UX and Astro specialist |
+| Feature adds/changes i18n strings | `@i18n Reviewer` | Linguistic and i18n correctness |
+
## Next Steps
After all cycles complete: "Run `/smart-commit` to commit, then `/pr-sync` to open a PR."
+
+If implementation is non-trivial: "Use `@Code Reviewer` for a post-implementation review."
diff --git a/.github/agents/website-designer.agent.md b/.github/agents/website-designer.agent.md
new file mode 100644
index 00000000..0d5ee7ca
--- /dev/null
+++ b/.github/agents/website-designer.agent.md
@@ -0,0 +1,311 @@
+---
+name: Website Designer
+description: >
+ UI/UX specialist for the Envilder website. Use when creating or updating
+ responsive web pages, components, sections, or styles. Ensures full
+ responsiveness across mobile, tablet, and desktop. Integrates with the
+ existing i18n system and dual-theme support (retro/light).
+ Communicates Envilder's value proposition to developers, CTOs, and technical
+ leaders. Builds pages using the Astro + pure CSS design system already
+ established in the project.
+tools: [read, edit, search, execute, web, agent, todo, vscode/*, playwright/*]
+argument-hint: "page, section, or component to create/improve"
+agents: ['i18n Reviewer', 'Document Maintainer', 'Code Reviewer', 'Explore']
+user-invocable: true
+---
+
+# Website Designer — Responsive UI/UX Specialist for Envilder
+
+You are a senior UI/UX engineer and front-end specialist for the Envilder
+documentation website. You design and build pages that are **100% responsive**,
+**fully integrated with the i18n system and theme switcher**, and crafted to
+communicate Envilder's value to two audiences: **developers** who will use the
+tool daily and **CTOs / technical leaders** who need to understand the strategic
+benefits.
+
+## Project Context
+
+### What is Envilder?
+
+Envilder is a TypeScript CLI tool and GitHub Action that securely centralizes
+environment variables from AWS SSM Parameter Store or Azure Key Vault. It solves
+three key problems for engineering teams:
+
+1. **Security**: Secrets never live in `.env` files, git repos, or CI logs —
+ they stay in the cloud vault and are fetched at runtime.
+2. **Consistency**: One `param-map.json` file is the single source of truth for
+ all environments (dev, staging, production).
+3. **Developer Experience**: A single command (`npx envilder --map=map.json
+ --envfile=.env`) replaces manual secret copying, reducing onboarding friction
+ from hours to seconds.
+
+**Key selling points for CTOs**:
+
+- Zero secrets in source control (compliance-ready)
+- Multi-cloud support (AWS SSM + Azure Key Vault) without vendor lock-in
+- GitHub Action integration for CI/CD pipelines with no code changes
+- Open-source with MIT license — no licensing costs
+- Hexagonal architecture — easy to extend with new providers
+
+**Key selling points for developers**:
+
+- One command replaces manual secret management
+- Works with existing `.env` workflows — zero migration cost
+- Supports `--exec` mode to inject secrets without writing files
+- Type-safe configuration via `param-map.json`
+- Works locally, in CI, and in production
+
+### Tech Stack
+
+| Layer | Technology |
+|----------|-------------------------------------|
+| Framework | Astro 5.8 (static output) |
+| Styling | Pure CSS design system (no Tailwind)|
+| Fonts | Press Start 2P (pixel), Inter (body), JetBrains Mono (code) |
+| Themes | Retro (Game Boy green) + Light |
+| i18n | Custom TS system (see `src/apps/website/src/i18n/`) |
+| Hosting | Static site on AWS (CDK infra) |
+
+### File Locations
+
+| What | Where |
+|----------------------|------------------------------------------------|
+| Pages | `src/apps/website/src/pages/` |
+| Components | `src/apps/website/src/components/*.astro` |
+| Layout | `src/apps/website/src/layouts/BaseLayout.astro` |
+| Global CSS | `src/apps/website/src/styles/global.css` |
+| Translations | `src/apps/website/src/i18n/*.ts` (one file per locale) |
+| Translation types | `src/apps/website/src/i18n/types.ts` |
+| i18n utilities | `src/apps/website/src/i18n/utils.ts` |
+| Astro config | `src/apps/website/astro.config.mjs` |
+| Website package.json | `src/apps/website/package.json` |
+
+## Design System Rules
+
+### Themes
+
+The site uses `data-theme` attribute on ``. All colors MUST use CSS
+variables — never hardcode hex values in components.
+
+**Retro theme** (default): Game Boy green palette.
+**Light theme**: Warm neutral palette.
+
+```css
+/* Always use variables, never hardcode */
+color: var(--color-text); /* ✓ */
+background: var(--color-bg); /* ✓ */
+color: #8bac0f; /* ✗ NEVER */
+```
+
+Both themes define the same variable names. If you add new variables, define
+them in BOTH `:root` (retro) and `[data-theme="light"]` blocks in `global.css`.
+
+### Responsive Breakpoints
+
+Follow the mobile-first approach already established:
+
+| Breakpoint | Target | Pattern |
+|-----------------|-------------|----------------------------------------|
+| Default | Mobile | Single column, compact spacing |
+| `min-width: 640px` | Tablet | 2-column grids, expanded spacing |
+| `min-width: 1024px` | Desktop| 3-4 column grids, full layout |
+
+Use existing grid utilities: `.grid-2`, `.grid-3`, `.grid-4`.
+Use `clamp()` for fluid typography. Never use fixed `px` font sizes.
+
+### Spacing & Layout
+
+Use the spacing scale: `--space-xs` through `--space-4xl`.
+Container max-width: `--max-width` (1200px).
+
+### Component Patterns
+
+Use existing CSS classes for visual consistency:
+
+- `.pixel-card` — bordered cards with pixel corner notches
+- `.pixel-icon` — emoji with pixelated filter
+- `.pixel-shadow` — 4px offset retro shadow
+- `.badge` — small label badges
+- `.pixel-divider` — section separator
+- `.scanlines` — CRT overlay effect (use sparingly)
+- `.section` — standard section wrapper with vertical padding
+
+### Typography
+
+- Section titles: `Press Start 2P` (via `.pixel-card h3` or custom class)
+- Body text: `Inter` (default)
+- Code/terminal: `JetBrains Mono`
+
+## i18n Integration
+
+Every user-visible string MUST go through the i18n system. Never hardcode text.
+
+### Adding new translations
+
+1. **Define the type** in `src/apps/website/src/i18n/types.ts`
+2. **Add strings** to every locale file in `src/apps/website/src/i18n/` (one `*.ts` per locale)
+3. **Use in component** via the `t` object passed as prop:
+
+ ```astro
+ ---
+ import { useTranslations } from '../i18n/utils';
+ const { lang = 'en' } = Astro.props;
+ const t = useTranslations(lang);
+ ---
+ {t.section.title}
+ ```
+
+4. **Localized pages**: Create one page per locale. The default locale lives
+ at the root (`src/apps/website/src/pages/new-page.astro`); every other
+ locale gets a subdirectory (`src/apps/website/src/pages//new-page.astro`).
+ Check `astro.config.mjs` → `i18n.locales` to discover all active locales.
+
+### Terms that MUST NOT be translated
+
+Product names, CLI flags, code tokens, and acronyms stay in English:
+`envilder`, `AWS SSM`, `Azure Key Vault`, `GitHub Action`, `param-map.json`,
+`.env`, `--map`, `--envfile`, `--exec`, `--provider`, `--push`, `CI/CD`, `IAM`,
+`RBAC`, `CLI`, `API`, `JSON`, `YAML`, `Node.js`, `pnpm`, `npx`.
+
+## Audience-Aware Content Guidelines
+
+### For developers (technical depth)
+
+- Show real CLI commands and `param-map.json` examples
+- Explain `--exec` mode, push mode, and GitHub Action inputs
+- Use terminal mockups (`TerminalMockup.astro`) for live demos
+- Keep language direct and concise
+
+### For CTOs / technical leaders (strategic value)
+
+- Lead with business outcomes: compliance, reduced risk, faster onboarding
+- Use comparison tables (before/after, with/without Envilder)
+- Highlight multi-cloud flexibility and vendor independence
+- Quantify impact: "onboard in 1 command instead of 12 manual steps"
+- Include trust signals: open-source, MIT license, hexagonal architecture
+
+## Dev Server
+
+Before making any changes, **start the Astro dev server** so every edit is
+reflected instantly in the browser:
+
+```bash
+cd src/apps/website && pnpm dev
+```
+
+This runs in the background on `http://localhost:4322/`. Keep it running
+throughout the entire session. After starting it, navigate the browser to
+`http://localhost:4322/` to verify it is ready.
+
+> **IMPORTANT**: Do NOT skip this step. The dev server enables hot-reload — you
+> will see your changes in the browser seconds after saving a file. Use it as
+> your primary feedback loop instead of running full builds after every change.
+
+If the dev server is already running (check terminal output), reuse it.
+
+## Visual Validation with Playwright
+
+You have access to the **MCP Playwright** browser tools. Use them to visually
+validate every change you make — never ship a component without checking it in
+the browser.
+
+### Validation Breakpoints
+
+After every meaningful change (new component, CSS update, layout modification),
+validate at all three breakpoints:
+
+| Breakpoint | Width × Height | What to check |
+|------------|---------------|---------------|
+| Mobile | 375 × 812 | Single column, readable text, no overflow |
+| Tablet | 768 × 1024 | 2-column grids, proper spacing |
+| Desktop | 1440 × 900 | Full layout, max-width containment |
+
+### Validation Procedure
+
+For each breakpoint:
+
+1. **Resize** the browser to the target viewport.
+2. **Navigate** to the page being edited (or reload if already there).
+3. **Take a snapshot** to verify the accessibility tree and element structure.
+4. **Take a screenshot** to verify the visual result.
+5. **Toggle theme**: Click the theme switcher and repeat snapshot + screenshot
+ to verify both retro and light themes.
+
+### Playwright Tool Cheat Sheet
+
+| Action | Tool | Example |
+|--------|------|---------|
+| Navigate to page | `browser_navigate` | `http://localhost:4322/` |
+| Resize viewport | `browser_resize` | `{ width: 375, height: 812 }` |
+| Accessibility snapshot | `browser_snapshot` | Verify structure & text content |
+| Visual screenshot | `browser_take_screenshot` | Verify layout & styling |
+| Click element | `browser_click` | Toggle theme switcher |
+| Full-page screenshot | `browser_take_screenshot` | `{ fullPage: true }` |
+
+### When to Validate
+
+- **After creating a new component**: Full 3-breakpoint validation
+- **After CSS changes**: Full 3-breakpoint validation
+- **After i18n changes**: Navigate to each locale and verify text renders
+- **After layout modifications**: Full 3-breakpoint validation
+- **Before marking work as complete**: Final full validation pass
+
+## Workflow
+
+1. **Start dev server**: Run `cd src/apps/website && pnpm dev` in the
+ background (skip if already running).
+2. **Open browser**: Navigate to `http://localhost:4322/` using Playwright.
+3. **Read first**: Always read the relevant existing files before making changes.
+ Understand current component structure, CSS classes, and i18n keys.
+4. **Plan with todos**: Break the work into trackable steps.
+5. **Build mobile-first**: Start with the mobile layout, then add tablet/desktop
+ media queries.
+6. **Validate with Playwright**: After each meaningful edit, run the 3-breakpoint
+ validation procedure. Check both themes at each breakpoint.
+7. **Theme-proof everything**: Verify both retro and light themes using Playwright
+ screenshots — all colors must use CSS variables.
+8. **i18n-proof everything**: Add translation keys to every active locale. After
+ creating/editing components, delegate to the **i18n Reviewer** agent to
+ verify translations.
+9. **Final validation**: Run the full 3-breakpoint validation once more as a
+ final pass before marking work as complete.
+10. **Build check**: Run `pnpm build:website` and check for Astro build errors.
+
+## Delegation Rules
+
+| Trigger | Delegate to | Why |
+|---------|-------------|-----|
+| After any component edit with user-visible text | `@i18n Reviewer` | Verify translations are complete and correct |
+| Website changes affect documented features or CLI usage | `@Document Maintainer` | Keep docs in sync |
+| Website code needs quality review | `@Code Reviewer` | Multi-perspective read-only analysis |
+| Website JS/TS logic has a bug | `@Bug Hunter` | Reproduce and fix via TDD |
+| CSS or layout needs structural cleanup | `@Code Refactorer` | Safe incremental improvements |
+
+## Next Steps
+
+After page/component is built: "Use `@i18n Reviewer` to audit translations."
+
+After all work complete: "Run `/smart-commit` to commit, then `/pr-sync` to open a PR."
+
+## Constraints
+
+- DO NOT install new CSS frameworks or UI libraries — use the existing pure CSS
+ design system
+- DO NOT hardcode colors — always use CSS variables from `global.css`
+- DO NOT hardcode user-visible text — always use the i18n system
+- DO NOT use fixed pixel font sizes — use `clamp()` or relative units
+- DO NOT break existing responsive layouts when adding new sections
+- DO NOT add JavaScript frameworks (React, Vue, etc.) — use Astro components
+ with `
diff --git a/src/apps/website/src/components/ProblemSolution.astro b/src/apps/website/src/components/ProblemSolution.astro
new file mode 100644
index 00000000..9790d694
--- /dev/null
+++ b/src/apps/website/src/components/ProblemSolution.astro
@@ -0,0 +1,103 @@
+---
+import { useTranslations } from '../i18n/utils';
+
+interface Props {
+ lang?: string;
+}
+
+const { lang = 'en' } = Astro.props;
+const t = useTranslations(lang);
+---
+
+
+
+
+
{t.problemSolution.title}{t.problemSolution.titleAccent} {t.problemSolution.titleSuffix}
+
+ {t.problemSolution.subtitle}
+
+
+
+
+ {t.problemSolution.problems.map((p) => (
+
+
{p.icon}
+
{p.title}
+
{p.description}
+
+ ))}
+
+
+
+ {t.problemSolution.arrowText}
+
+
+
+ {t.problemSolution.solutions.map((s) => (
+
+
{s.icon}
+
{s.title}
+
{s.description}
+
+ ))}
+
+
+
+
+
diff --git a/src/apps/website/src/components/Providers.astro b/src/apps/website/src/components/Providers.astro
new file mode 100644
index 00000000..83f806af
--- /dev/null
+++ b/src/apps/website/src/components/Providers.astro
@@ -0,0 +1,222 @@
+---
+import { useTranslations } from '../i18n/utils';
+import CodeBlock from './CodeBlock.astro';
+
+interface Props {
+ lang?: string;
+}
+
+const { lang = 'en' } = Astro.props;
+const t = useTranslations(lang);
+---
+
+
+
+
+
{t.providers.title}{t.providers.titleAccent}
+
+ {t.providers.subtitle}
+
+
+
+
+
+
+
+
+{`{
+ "$config": {
+ "provider": "aws",
+ "profile": "prod-account"
+ },
+ "DB_PASSWORD": "/my-app/prod/db-password",
+ "API_KEY": "/my-app/prod/api-key"
+}`}
+
+
+
+
+ $ envilder --map=param-map.json --envfile=.env
+
+
+
+
+ {t.providers.awsFeatures.map((f) => (
+ ✔ {f}
+ ))}
+
+
+
+
+
+
+
+{`{
+ "$config": {
+ "provider": "azure",
+ "vaultUrl": "https://my-vault.vault.azure.net"
+ },
+ "DB_PASSWORD": "my-app-prod-db-password",
+ "API_KEY": "my-app-prod-api-key"
+}`}
+
+
+
+
+ $ envilder --provider=azure --vault-url=https://my-vault.vault.azure.net --map=param-map.json --envfile=.env
+
+
+
+
+ {t.providers.azureFeatures.map((f) => (
+ ✔ {f}
+ ))}
+
+
+
+
+
+
+
+{`{
+ "$config": {
+ "provider": "gcp",
+ "projectId": "my-project-id"
+ },
+ "DB_PASSWORD": "my-app-prod-db-password",
+ "API_KEY": "my-app-prod-api-key"
+}`}
+
+
+
+
+ $ envilder --provider=gcp --map=param-map.json --envfile=.env
+
+
+
+
+ {t.providers.gcpFeatures.map((f) => (
+ ✔ {f}
+ ))}
+
+
+
+
+
+
+
+
diff --git a/src/apps/website/src/components/Roadmap.astro b/src/apps/website/src/components/Roadmap.astro
new file mode 100644
index 00000000..4062f10c
--- /dev/null
+++ b/src/apps/website/src/components/Roadmap.astro
@@ -0,0 +1,159 @@
+---
+import { useTranslations } from '../i18n/utils';
+
+interface Props {
+ lang?: string;
+}
+
+const { lang = 'en' } = Astro.props;
+const t = useTranslations(lang);
+---
+
+
+
+
+
{t.roadmap.title}{t.roadmap.titleAccent}
+
+ {t.roadmap.subtitle}
+
+
+
+
+ {t.roadmap.items.map((item, i) => {
+ const next = t.roadmap.items[i + 1];
+ const lineActive = item.status === 'done' && next?.status === 'done';
+ return (
+
+ );
+ })}
+
+
+
+
+
diff --git a/src/apps/website/src/components/TerminalMockup.astro b/src/apps/website/src/components/TerminalMockup.astro
new file mode 100644
index 00000000..8ad0442c
--- /dev/null
+++ b/src/apps/website/src/components/TerminalMockup.astro
@@ -0,0 +1,122 @@
+---
+export interface Props {
+ title?: string;
+}
+
+const { title = 'bash' } = Astro.props;
+---
+
+
+
+
diff --git a/src/apps/website/src/components/ThemeSwitcher.astro b/src/apps/website/src/components/ThemeSwitcher.astro
new file mode 100644
index 00000000..9626a1f5
--- /dev/null
+++ b/src/apps/website/src/components/ThemeSwitcher.astro
@@ -0,0 +1,112 @@
+---
+import { useTranslations } from '../i18n/utils';
+
+interface Props {
+ lang?: string;
+}
+
+const { lang = 'en' } = Astro.props;
+const t = useTranslations(lang);
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/apps/website/src/components/TrustBadges.astro b/src/apps/website/src/components/TrustBadges.astro
new file mode 100644
index 00000000..00e455a0
--- /dev/null
+++ b/src/apps/website/src/components/TrustBadges.astro
@@ -0,0 +1,105 @@
+---
+import { useTranslations } from '../i18n/utils';
+
+interface Props {
+ lang?: string;
+}
+
+const { lang = 'en' } = Astro.props;
+const t = useTranslations(lang);
+---
+
+
+
+
+
+
+
+
AWS SSM
+
+
+
·
+
+
+
+
Azure Key Vault
+
+
+
·
+
+
+
+
GitHub Actions
+
+
+
·
+
+
+
+
npm
+
+
+
+
+
+
+
diff --git a/src/apps/website/src/env.d.ts b/src/apps/website/src/env.d.ts
new file mode 100644
index 00000000..41fad5b5
--- /dev/null
+++ b/src/apps/website/src/env.d.ts
@@ -0,0 +1 @@
+declare const __APP_VERSION__: string;
diff --git a/src/apps/website/src/i18n/ca.ts b/src/apps/website/src/i18n/ca.ts
new file mode 100644
index 00000000..8b5dbf23
--- /dev/null
+++ b/src/apps/website/src/i18n/ca.ts
@@ -0,0 +1,620 @@
+import { releaseMetadata } from './releaseMetadata';
+import type { Translations } from './types';
+
+export const ca: Translations = {
+ homeMeta: {
+ title: 'Envilder — Centralitza els teus secrets. Una comanda.',
+ description:
+ "Una eina CLI i GitHub Action que centralitza de forma segura les variables d'entorn des d'AWS SSM, Azure Key Vault o GCP Secret Manager com a font de veritat única.",
+ },
+ nav: {
+ features: 'Funcionalitats',
+ howItWorks: 'Com funciona',
+ providers: 'Proveïdors',
+ githubAction: 'GitHub Action',
+ changelog: 'Canvis',
+ docs: 'Docs',
+ getStarted: 'Comença',
+ },
+ theme: {
+ retro: 'Retro',
+ light: 'Clar',
+ },
+ hero: {
+ openSource: 'Codi obert · MIT',
+ title1: 'Els teus secrets.',
+ title2: 'Una comanda.',
+ titleAccent: 'Cada entorn.',
+ description:
+ "Una eina CLI i GitHub Action que centralitza de forma segura les teves variables d'entorn des de",
+ descAws: 'AWS SSM',
+ descAzure: 'Azure Key Vault',
+ descGcp: 'GCP Secret Manager',
+ descOr: 'o',
+ descComma: ',',
+ descSuffix:
+ 'com a font de veritat única. Adéu a copiar i enganxar secrets.',
+ getStarted: '▶ Comença',
+ viewOnGithub: '★ Veure a GitHub',
+ terminalComment1: '# 1. Defineix el mapeig',
+ terminalComment2: '# 2. Descarrega secrets → genera .env',
+ terminalFetched1: ' Obtingut DB_PASSWORD → ···pass',
+ terminalFetched2: ' Obtingut API_KEY → ···key',
+ terminalWritten: " Fitxer d'entorn escrit a .env",
+ },
+ trust: {
+ label: 'COMPATIBLE AMB',
+ },
+ problemSolution: {
+ title: 'El ',
+ titleAccent: 'problema',
+ titleSuffix: ' amb fitxers .env',
+ subtitle:
+ "Gestionar secrets manualment no escala. És insegur, propens a errors i crea fricció per a tot l'equip.",
+ problems: [
+ {
+ icon: '💀',
+ title: 'Desincronització entre entorns',
+ description:
+ 'Dev, staging i prod tenen secrets diferents. Els desplegaments fallen. Ningú sap quin .env és el correcte.',
+ },
+ {
+ icon: '📨',
+ title: 'Secrets compartits per Slack/email',
+ description:
+ 'Claus API enviades en text pla per xat. Sense traçabilitat. Sense rotació. Un incident de seguretat esperant a passar.',
+ },
+ {
+ icon: '🐌',
+ title: 'Onboarding i rotacions lentes',
+ description:
+ "Un nou membre s'uneix a l'equip? Copia i enganxa un .env de la màquina d'algú. Algú rota? Espera que tothom actualitzi manualment.",
+ },
+ ],
+ arrowText: '▼ envilder ho soluciona ▼',
+ solutions: [
+ {
+ icon: '🛡️',
+ title: 'Font de veritat al núvol',
+ description:
+ 'Tots els secrets viuen a AWS SSM o Azure Key Vault. IAM/RBAC controla qui pot llegir què. Cada accés queda registrat.',
+ },
+ {
+ icon: '⚡',
+ title: 'Una comanda, sempre sincronitzat',
+ description:
+ 'Executa envilder i el teu .env es regenera des de la font de veritat. Idempotent. Instantani. Sense marge per al desfasament.',
+ },
+ {
+ icon: '🤖',
+ title: 'Automatitzat en CI/CD',
+ description:
+ 'Utilitza la GitHub Action per obtenir secrets en el moment del desplegament. Sense secrets als repos. Sense passos manuals als pipelines.',
+ },
+ ],
+ },
+ howItWorks: {
+ title: 'Com ',
+ titleAccent: 'funciona',
+ subtitle: 'Tres passos. De secrets dispersos a una única font de veritat.',
+ steps: [
+ {
+ title: 'Crea un fitxer de mapeig',
+ description:
+ "Mapeja els noms de les teves variables d'entorn a les seves rutes de secrets a AWS SSM o Azure Key Vault.",
+ },
+ {
+ title: 'Executa una comanda',
+ description:
+ 'Envilder obté cada secret del teu proveïdor al núvol i els escriu en un fitxer .env local. Idempotent i instantani.',
+ },
+ {
+ title: 'El teu .env està llest',
+ description:
+ "Un fitxer d'entorn net i actualitzat — generat des de la font de veritat. Utilitza'l localment o injecta'l en CI/CD amb la GitHub Action.",
+ },
+ ],
+ terminalFetched1: '✔ Obtingut DB_PASSWORD → ···word',
+ terminalFetched2: '✔ Obtingut API_KEY → ···key',
+ terminalFetched3: '✔ Obtingut SECRET_TOKEN → ···oken',
+ terminalWritten: "✔ Fitxer d'entorn escrit a .env",
+ },
+ features: {
+ title: 'Fet per a ',
+ titleAccent: 'equips reals',
+ subtitle:
+ "Tot el que necessites per gestionar secrets d'entorn de forma segura i a escala.",
+ features: [
+ {
+ icon: '☁️',
+ title: 'Multi-Proveïdor',
+ description:
+ 'AWS SSM, Azure Key Vault i GCP Secret Manager (pròximament). Tria amb --provider o $config al fitxer de mapeig.',
+ },
+ {
+ icon: '🔄',
+ title: 'Sincronització bidireccional',
+ description:
+ "Obté secrets a fitxers .env o puja valors .env al teu proveïdor al núvol. Suport complet d'anada i tornada.",
+ },
+ {
+ icon: '⚙️',
+ title: 'GitHub Action',
+ description:
+ 'Action per als teus workflows CI/CD. Obté secrets en el moment del desplegament sense intervenció manual.',
+ },
+ {
+ icon: '🔒',
+ title: 'Accés IAM i RBAC',
+ description:
+ "Aprofita el control d'accés natiu del núvol. Les polítiques IAM d'AWS o RBAC d'Azure defineixen qui llegeix què, per entorn.",
+ },
+ {
+ icon: '📊',
+ title: 'Totalment auditable',
+ description:
+ 'Cada lectura i escriptura queda registrada a AWS CloudTrail o Azure Monitor. Traçabilitat completa de qui ha accedit a què i quan.',
+ },
+ {
+ icon: '🔁',
+ title: 'Sincronització idempotent',
+ description:
+ "Només s'actualitza el que hi ha al teu mapeig. Res més es toca. Executa'l deu vegades — mateix resultat, zero efectes secundaris.",
+ },
+ {
+ icon: '🧱',
+ title: 'Zero infraestructura',
+ description:
+ 'Construït sobre serveis natius del núvol. Sense Lambdas, sense servidors, sense infraestructura extra per gestionar o pagar.',
+ },
+ {
+ icon: '👤',
+ title: 'Suport de perfils AWS',
+ description:
+ 'Configuració multi-compte? Utilitza --profile per canviar entre perfils AWS CLI. Perfecte per a entorns multi-etapa.',
+ },
+ {
+ icon: '🚀',
+ title: 'Mode Exec',
+ description:
+ 'Injecta secrets directament en un procés fill sense escriure a disc. Zero fitxers .env, zero risc de fuites.',
+ badge: 'Pròximament',
+ },
+ ],
+ },
+ demo: {
+ title: "Mira'l en ",
+ titleAccent: 'acció',
+ subtitle:
+ 'Mira com Envilder simplifica la gestió de secrets en menys de 2 minuts.',
+ cliDemo: 'Demo CLI — Obtenir Secrets',
+ ghaWorkflow: 'Workflow de GitHub Action',
+ comingSoon: 'Properament',
+ },
+ providers: {
+ title: 'El teu núvol. ',
+ titleAccent: 'La teva elecció.',
+ subtitle:
+ 'Envilder funciona amb AWS SSM Parameter Store, Azure Key Vault i GCP Secret Manager (pròximament). Configura en línia o amb flags CLI.',
+ awsTitle: 'AWS SSM Parameter Store',
+ awsDefault: 'Proveïdor per defecte',
+ awsFeatures: [
+ 'Suport de GetParameter amb WithDecryption',
+ 'Suport de perfil AWS per a multi-compte',
+ "Control d'accés basat en polítiques IAM",
+ "Registre d'auditoria CloudTrail",
+ ],
+ azureTitle: 'Azure Key Vault',
+ azureBadge: 'Nou a v0.8',
+ azureFeatures: [
+ 'Auto-normalitza noms de secrets (barres → guions)',
+ 'Autenticació DefaultAzureCredential',
+ "Control d'accés Azure RBAC",
+ "Registre d'auditoria Azure Monitor",
+ ],
+ gcpTitle: 'GCP Secret Manager',
+ gcpBadge: 'Pròximament',
+ gcpFeatures: [
+ 'Integració amb Google Cloud Secret Manager',
+ 'Application Default Credentials (ADC)',
+ "Control d'accés basat en IAM",
+ 'Cloud Audit Logs',
+ ],
+ configPriorityTitle: 'Prioritat de configuració',
+ priorityHigh: 'Flags CLI / Inputs GHA',
+ priorityMid: '$config al fitxer de mapeig',
+ priorityLow: 'Per defecte (AWS)',
+ },
+ gha: {
+ title: 'GitHub Action',
+ subtitle:
+ 'Obté secrets en el moment del desplegament. Afegeix-lo a qualsevol workflow en minuts.',
+ awsSsm: '☁️ AWS SSM',
+ azureKeyVault: '🔑 Azure Key Vault',
+ actionInputs: "Inputs de l'Action",
+ thInput: 'Input',
+ thRequired: 'Requerit',
+ thDefault: 'Per defecte',
+ thDescription: 'Descripció',
+ inputMapDesc:
+ "Ruta al fitxer JSON que mapeja variables d'entorn a rutes de secrets",
+ inputEnvDesc: 'Ruta al fitxer .env a generar',
+ inputProviderDesc: 'Proveïdor al núvol: aws o azure (per defecte: aws)',
+ inputVaultDesc: "URL d'Azure Key Vault",
+ output: 'Output:',
+ outputDesc: 'Ruta al fitxer .env generat',
+ yes: 'Sí',
+ no: 'No',
+ },
+ changelog: {
+ title: 'Què hi ha de ',
+ titleAccent: 'nou',
+ subtitle:
+ "Novetats de l'última versió. El suport multi-proveïdor ja és aquí.",
+ releaseTitle: 'Suport Multi-Proveïdor',
+ releaseDate: new Date(
+ `${releaseMetadata.releaseDate}T00:00:00`,
+ ).toLocaleDateString('ca-ES', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ }),
+ highlights: [
+ {
+ icon: '✨',
+ text: 'Secció $config als fitxers de mapeig — declara proveïdor i detalls de connexió en línia',
+ },
+ {
+ icon: '✨',
+ text: "Suport d'Azure Key Vault — paritat completa amb AWS SSM",
+ },
+ { icon: '✨', text: 'Flags CLI --vault-url i --provider' },
+ {
+ icon: '✨',
+ text: 'Normalització automàtica de noms de secrets per Azure (barres → guions)',
+ },
+ {
+ icon: '⚠️',
+ text: "Canvi incompatible: --ssm-path reanomenat a --secret-path (l'antic flag encara funciona com a àlies obsolet)",
+ },
+ ],
+ fullChangelog: '📋 Historial complet',
+ viewReleases: 'Veure totes les versions a GitHub →',
+ },
+ roadmap: {
+ title: 'Què ve ',
+ titleAccent: 'ara',
+ subtitle: 'Envilder es desenvolupa activament. Aquí és cap on anem.',
+ upNext: 'Pròximament',
+ items: [
+ {
+ status: 'done',
+ label: '✅',
+ title: 'Descarregar secrets a .env',
+ description:
+ "Mapeja noms de variables d'entorn a rutes de secrets al núvol via JSON i genera fitxers .env automàticament",
+ },
+ {
+ status: 'done',
+ label: '✅',
+ title: 'Mode push (--push)',
+ description:
+ 'Puja valors .env o secrets individuals al proveïdor al núvol',
+ },
+ {
+ status: 'done',
+ label: '✅',
+ title: 'GitHub Action',
+ description: 'Utilitza Envilder en workflows CI/CD de forma nativa',
+ },
+ {
+ status: 'done',
+ label: '✅',
+ title: 'Multi-proveïdor (AWS + Azure)',
+ description: "Suport d'AWS SSM Parameter Store i Azure Key Vault",
+ },
+ {
+ status: 'done',
+ label: '📖',
+ title: 'Web de documentació',
+ description:
+ 'Web de docs dedicada amb guies, exemples i referència API',
+ },
+ {
+ status: 'next',
+ label: '⚡',
+ title: 'Mode exec (--exec)',
+ description: 'Injecta secrets en un procés fill sense escriure a disc',
+ },
+ {
+ status: 'planned',
+ label: '☁️',
+ title: 'GCP Secret Manager',
+ description: 'Tercer proveïdor cloud — completa el trident multi-núvol',
+ },
+ {
+ status: 'planned',
+ label: '🔐',
+ title: 'AWS Secrets Manager',
+ description: 'Suport de secrets JSON junt amb SSM Parameter Store',
+ },
+ {
+ status: 'planned',
+ label: '✔️',
+ title: 'Mode check/sync (--check)',
+ description:
+ 'Valida secrets al núvol vs .env local — falla CI si estan desincronitzats',
+ },
+ ],
+ },
+ getStarted: {
+ title: 'Comença ',
+ titleAccent: 'ara',
+ subtitle: "En funcionament en menys d'un minut.",
+ prerequisites: 'Prerequisits',
+ prereqNode: 'Node.js v20+',
+ prereqAws: 'AWS CLI configurat',
+ prereqAzure: 'Azure CLI configurat',
+ prereqIam: 'Permisos IAM:',
+ prereqAwsNote: 'per AWS SSM',
+ prereqAzureNote: 'per Azure Key Vault',
+ install: 'Instal·lar',
+ quickStart: 'Inici ràpid',
+ step1:
+ "Crea un param-map.json que mapegi variables d'entorn a rutes de secrets",
+ step2: 'Executa envilder --map=param-map.json --envfile=.env',
+ step3: 'El teu fitxer .env està llest ✔',
+ terminalTitle: 'Inici ràpid',
+ commentInstall: '# Instal·lar globalment',
+ commentCreate: '# Crear fitxer de mapeig',
+ commentPull: '# Obtenir secrets',
+ commentPush: '# Pujar un secret',
+ doneMessage: ' Fet! Fitxer .env generat.',
+ pushSuccess: ' Secret pujat correctament.',
+ },
+ footer: {
+ tagline:
+ "Centralitza de forma segura les teves variables d'entorn des d'AWS SSM, Azure Key Vault o GCP Secret Manager.",
+ project: 'Projecte',
+ documentation: 'Documentació',
+ community: 'Comunitat',
+ linkGithub: 'GitHub',
+ linkNpm: 'npm',
+ linkChangelog: 'Canvis',
+ linkRoadmap: 'Full de ruta',
+ linkGettingStarted: 'Comença',
+ linkPullCommand: 'Comanda Pull',
+ linkPushCommand: 'Comanda Push',
+ linkGithubAction: 'GitHub Action',
+ linkIssues: 'Incidències',
+ linkDiscussions: 'Discussions',
+ linkSecurity: 'Seguretat',
+ linkSponsor: 'Patrocina',
+ license: 'Llicència MIT',
+ copyright: 'Fet amb Astro. Codi obert a GitHub.',
+ builtWith: 'Fet amb Astro. Codi obert a GitHub.',
+ },
+ changelogPage: {
+ title: 'Historial de canvis — Envilder',
+ backToHome: "← Tornar a l'inici",
+ fullChangelog: 'Historial de ',
+ changelogAccent: 'canvis',
+ intro: 'Historial complet de versions. Vegeu també',
+ githubReleases: 'Versions a GitHub',
+ versions: 'Versions',
+ backToTop: 'Tornar a dalt',
+ },
+ docs: {
+ title: 'Documentació — Envilder',
+ backToHome: "← Tornar a l'inici",
+ pageTitle: 'Documentació',
+ intro: 'Tot el que necessites per començar amb Envilder.',
+ sidebarGettingStarted: 'Primers passos',
+ sidebarRequirements: 'Requisits',
+ sidebarInstallation: 'Instal·lació',
+ sidebarCredentials: 'Credencials del núvol',
+ sidebarPermissions: 'Permisos IAM',
+ sidebarCli: 'CLI',
+ sidebarMappingFile: 'Fitxer de mapeig',
+ sidebarPullCommand: 'Comanda pull',
+ sidebarPushCommand: 'Comanda push',
+ sidebarPushSingle: 'Push individual',
+ sidebarGha: 'GitHub Action',
+ sidebarGhaSetup: 'Configuració',
+ sidebarGhaBasic: 'Exemple bàsic',
+ sidebarGhaMultiEnv: 'Multi-entorn',
+ sidebarGhaAzure: 'Exemple Azure',
+ sidebarGhaInputs: 'Inputs i outputs',
+ sidebarReference: 'Referència',
+ sidebarConfigPriority: 'Prioritat de config',
+ sidebarAzureSetup: 'Configuració Azure',
+ overviewTitle: 'Què és Envilder?',
+ overviewDesc:
+ "Envilder és una eina CLI i GitHub Action que descarrega variables d'entorn d'un magatzem de secrets al núvol (AWS SSM Parameter Store o Azure Key Vault) i les escriu en un fitxer .env local — o les puja de tornada. Definiu un simple mapeig JSON entre noms de variables i rutes de secrets, i Envilder fa la resta.",
+ overviewProblem:
+ 'Sense Envilder, els equips copien secrets a mà, els guarden en fitxers .env en text pla al repositori, o mantenen scripts de shell fràgils per cada entorn. Això porta a credencials filtrades, configuracions inconsistents i incorporacions lentes.',
+ overviewSolution:
+ "Amb Envilder, un fitxer param-map.json és la font única de veritat. Els secrets no surten del magatzem fins al moment d'execució, cada entorn utilitza el mateix mapeig, i un nou desenvolupador està operatiu amb una sola comanda.",
+ reqTitle: 'Requisits',
+ reqNode: 'Node.js v20+',
+ reqAws: 'AWS CLI',
+ reqAzure: 'Azure CLI',
+ reqAwsNote: 'per AWS SSM',
+ reqAzureNote: 'per Azure Key Vault',
+ reqDownload: 'Descarregar',
+ reqInstallGuide: "Guia d'instal·lació",
+ installTitle: 'Instal·lació',
+ credTitle: 'Credencials del núvol',
+ credAwsTitle: 'AWS (per defecte)',
+ credAwsDesc:
+ 'Envilder utilitza les teves credencials AWS CLI. Configura el perfil per defecte:',
+ credAwsProfile: 'O utilitza un perfil amb nom:',
+ credAzureTitle: 'Azure Key Vault',
+ credAzureDesc:
+ 'Envilder utilitza Azure Default Credentials. Inicia sessió amb:',
+ credAzureVault:
+ "Proporciona l'URL del vault via $config al fitxer de mapeig o el flag --vault-url.",
+ permTitle: 'Permisos IAM',
+ permAwsTitle: 'AWS',
+ permAwsDesc: 'El teu usuari o rol IAM necessita:',
+ permOperation: 'Operació',
+ permPermission: 'Permís',
+ permPull: 'Pull',
+ permPush: 'Push',
+ permPolicyExample: 'Exemple de política IAM:',
+ permAzureTitle: 'Azure',
+ permAzureRbac: 'Recomanat — assigna Key Vault Secrets Officer via RBAC:',
+ permAzurePullNote:
+ 'Per accés només de lectura, Key Vault Secrets User és suficient.',
+ mapTitle: 'Fitxer de mapeig',
+ mapIntro:
+ "El fitxer de mapeig (param-map.json) és el nucli d'Envilder. És un fitxer JSON que mapeja noms de variables d'entorn (claus) a rutes de secrets (valors) al teu proveïdor al núvol.",
+ mapCalloutStructure: 'Estructura:',
+ mapCalloutKey:
+ "Cada clau es converteix en un nom de variable d'entorn al teu fitxer .env.",
+ mapCalloutValue:
+ 'Cada valor és la ruta on viu el secret al teu proveïdor al núvol.',
+ mapBasicTitle: 'Format bàsic (AWS SSM — per defecte)',
+ mapBasicDesc:
+ 'Quan no hi ha secció $config, Envilder utilitza AWS SSM Parameter Store per defecte. Els valors han de ser rutes de paràmetres SSM vàlides (normalment començant amb /):',
+ mapBasicGenerates: 'Això genera:',
+ mapConfigTitle: 'La secció $config',
+ mapConfigDesc:
+ 'Afegeix una clau $config al teu fitxer de mapeig per declarar quin proveïdor al núvol utilitzar i la seva configuració. Envilder llegeix $config per la configuració i tracta totes les altres claus com a mapeigs de secrets.',
+ mapConfigOptionsTitle: 'Opcions de $config',
+ mapThKey: 'Clau',
+ mapThType: 'Tipus',
+ mapThDefault: 'Per defecte',
+ mapThDescription: 'Descripció',
+ mapProviderDesc: 'Proveïdor al núvol a utilitzar',
+ mapVaultUrlDesc:
+ 'URL d\'Azure Key Vault (requerit quan el proveïdor és "azure")',
+ mapProfileDesc:
+ 'Perfil AWS CLI per a configuracions multi-compte (només AWS)',
+ mapAwsProfileTitle: 'AWS SSM amb perfil',
+ mapAwsProfileDesc:
+ 'Per utilitzar un perfil AWS CLI específic (útil per a configuracions multi-compte), afegeix profile a $config:',
+ mapAwsProfileExplain:
+ 'Això indica a Envilder que utilitzi el perfil prod-account del teu fitxer ~/.aws/credentials en lloc del perfil per defecte.',
+ mapAzureTitle: 'Azure Key Vault',
+ mapAzureDesc:
+ 'Per Azure Key Vault, estableix provider a "azure" i proporciona el vaultUrl:',
+ mapAzureWarningTitle: 'Convenció de noms Azure:',
+ mapAzureWarningDesc:
+ 'Els noms de secrets de Key Vault només permeten caràcters alfanumèrics i guions. Envilder normalitza automàticament els noms — barres i guions baixos es converteixen en guions (p. ex., /myapp/db/password → myapp-db-password).',
+ mapDifferencesTitle: 'Diferències clau per proveïdor',
+ mapThEmpty: '',
+ mapThAwsSsm: 'AWS SSM',
+ mapThAzureKv: 'Azure Key Vault',
+ mapSecretPathFormat: 'Format de ruta de secret',
+ mapAwsPathFormat: 'Rutes de paràmetres amb barres',
+ mapAzurePathFormat: 'Noms amb guions',
+ mapRequiredConfig: '$config requerit',
+ mapAwsRequiredConfig: 'Cap (AWS és per defecte)',
+ mapAzureRequiredConfig: 'provider + vaultUrl',
+ mapOptionalConfig: '$config opcional',
+ mapAuthentication: 'Autenticació',
+ mapAwsAuth: 'Credencials AWS CLI',
+ mapAzureAuth: 'Azure Default Credentials',
+ mapMultiEnvTitle: 'Múltiples entorns',
+ mapMultiEnvDesc:
+ "Un patró comú és tenir un fitxer de mapeig per entorn. L'estructura és la mateixa, només canvien les rutes dels secrets:",
+ mapMultiEnvThenPull: 'Després obté el correcte:',
+ mapOverrideTitle: 'Sobreescriure $config amb flags CLI',
+ mapOverrideDesc:
+ "Els flags CLI sempre tenen prioritat sobre els valors de $config. Això et permet establir valors per defecte al fitxer i sobreescriure'ls per invocació:",
+ mapOverrideComment1: '# Utilitza $config del fitxer de mapeig tal qual',
+ mapOverrideComment2:
+ '# Sobreescriu proveïdor i URL del vault, ignorant $config',
+ mapOverrideComment3: '# Sobreescriu només el perfil AWS',
+ mapPriorityNote:
+ 'Ordre de prioritat: flags CLI / inputs GHA → $config al fitxer de mapeig → per defecte (AWS).',
+ pullTitle: 'Comanda pull',
+ pullDesc:
+ 'Descarrega secrets del teu proveïdor al núvol i genera un fitxer .env local.',
+ pullOptions: 'Opcions',
+ pullExamples: 'Exemples',
+ pullOutput: 'Sortida',
+ optionHeader: 'Opció',
+ pullOptMap: 'Ruta al fitxer JSON de mapeig',
+ pullOptEnv: 'Ruta on escriure el .env',
+ pullOptProvider: 'aws (per defecte) o azure',
+ pullOptVault: "URL d'Azure Key Vault",
+ pullOptProfile: 'Perfil AWS CLI a utilitzar',
+ pullCommentDefault: '# Per defecte (AWS SSM)',
+ pullCommentProfile: '# Amb perfil AWS',
+ pullCommentAzureConfig: '# Azure via $config al fitxer de mapeig',
+ pullCommentAzureFlags: '# Azure via flags CLI',
+ pullOutputTitle: 'Sortida',
+ pushTitle: 'Comanda push',
+ pushDesc:
+ "Puja variables d'entorn d'un fitxer .env local al teu proveïdor al núvol utilitzant un fitxer de mapeig.",
+ pushOptions: 'Opcions',
+ pushExamples: 'Exemples',
+ pushOptPush: 'Activa el mode push (requerit)',
+ pushOptEnv: 'Ruta al teu fitxer .env local',
+ pushOptMap: 'Ruta al JSON de mapeig de paràmetres',
+ pushOptProvider: 'aws (per defecte) o azure',
+ pushOptVault: "URL d'Azure Key Vault",
+ pushOptProfile: 'Perfil AWS CLI (només AWS)',
+ pushCommentAws: '# Pujar a AWS SSM',
+ pushCommentProfile: '# Amb perfil AWS',
+ pushCommentAzureConfig: '# Azure via $config al fitxer de mapeig',
+ pushCommentAzureFlags: '# Azure via flags CLI',
+ pushSingleTitle: 'Pujar variable individual',
+ pushSingleDesc:
+ "Puja una variable d'entorn individual directament sense cap fitxer.",
+ pushSingleOptions: 'Opcions',
+ pushSingleOptPush: 'Activa el mode push (requerit)',
+ pushSingleOptKey: "Nom de la variable d'entorn",
+ pushSingleOptValue: 'Valor a emmagatzemar',
+ pushSingleOptPath: 'Ruta completa del secret al teu proveïdor al núvol',
+ pushSingleOptProvider: 'aws (per defecte) o azure',
+ pushSingleOptVault: "URL d'Azure Key Vault",
+ pushSingleOptProfile: 'Perfil AWS CLI (només AWS)',
+ ghaSetupTitle: 'Configuració de GitHub Action',
+ ghaSetupDesc:
+ "La GitHub Action d'Envilder obté secrets d'AWS SSM o Azure Key Vault en fitxers .env durant el teu workflow CI/CD. No cal compilar — l'action està pre-construïda i llesta per utilitzar des de GitHub Marketplace.",
+ ghaPrerequisites: 'Prerequisits',
+ ghaPrereqAws:
+ 'AWS: Configura credencials amb aws-actions/configure-aws-credentials',
+ ghaPrereqAzure: 'Azure: Configura credencials amb azure/login',
+ ghaPrereqMap: 'Un param-map.json al teu repositori',
+ ghaPullOnly: 'La GitHub Action només suporta el mode pull (sense push).',
+ ghaBasicTitle: 'Exemple bàsic de workflow',
+ ghaMultiEnvTitle: 'Workflow multi-entorn',
+ ghaAzureTitle: "Workflow d'Azure Key Vault",
+ ghaInputsTitle: "Inputs i outputs de l'Action",
+ ghaInputsSubtitle: 'Inputs',
+ ghaOutputsSubtitle: 'Outputs',
+ ghaInputRequired: 'Requerit',
+ ghaInputDefault: 'Per defecte',
+ ghaInputDesc: 'Descripció',
+ ghaOutputEnvPath: 'Ruta al fitxer .env generat',
+ ghaThInput: 'Input',
+ ghaThRequired: 'Requerit',
+ ghaThOutput: 'Output',
+ ghaYes: 'Sí',
+ ghaNo: 'No',
+ ghaInputMap: 'Ruta al fitxer JSON de mapeig',
+ ghaInputEnv: 'Ruta al fitxer .env a generar',
+ ghaInputProvider: 'aws o azure',
+ ghaInputVault: "URL d'Azure Key Vault",
+ configPriorityTitle: 'Prioritat de configuració',
+ configPriorityDesc:
+ 'Quan hi ha múltiples fonts de configuració, Envilder les resol en aquest ordre (el més alt guanya):',
+ configPriority1: 'Flags CLI / inputs GHA',
+ configPriority2: '$config al fitxer de mapeig',
+ configPriority3: 'Per defecte (AWS)',
+ configPriorityExplain:
+ 'Això vol dir que --provider=azure a la CLI sobreescriurà "provider": "aws" a $config.',
+ azureSetupTitle: "Configuració d'Azure Key Vault",
+ azureSetupCheck: "Comprova quin model d'accés utilitza el teu vault:",
+ azureRbacTrue: 'true → Azure RBAC (recomanat)',
+ azureRbacFalse: 'false / null → Vault Access Policy (clàssic)',
+ azureOptionA: 'Opció A — Azure RBAC (recomanat)',
+ azureOptionB: 'Opció B — Vault Access Policy',
+ azureAccessNote:
+ 'Per accés només de lectura, get list és suficient. Afegeix set per push.',
+ },
+};
diff --git a/src/apps/website/src/i18n/en.ts b/src/apps/website/src/i18n/en.ts
new file mode 100644
index 00000000..38b2e2ca
--- /dev/null
+++ b/src/apps/website/src/i18n/en.ts
@@ -0,0 +1,612 @@
+import { releaseMetadata } from './releaseMetadata';
+import type { Translations } from './types';
+
+export const en: Translations = {
+ homeMeta: {
+ title: 'Envilder — Centralize your secrets. One command.',
+ description:
+ 'A CLI tool and GitHub Action that securely centralizes environment variables from AWS SSM, Azure Key Vault, or GCP Secret Manager as a single source of truth.',
+ },
+ nav: {
+ features: 'Features',
+ howItWorks: 'How it works',
+ providers: 'Providers',
+ githubAction: 'GitHub Action',
+ changelog: 'Changelog',
+ docs: 'Docs',
+ getStarted: 'Get Started',
+ },
+ theme: {
+ retro: 'Retro',
+ light: 'Light',
+ },
+ hero: {
+ openSource: 'Open Source · MIT',
+ title1: 'Your secrets.',
+ title2: 'One command.',
+ titleAccent: 'Every environment.',
+ description:
+ 'A CLI tool and GitHub Action that securely centralizes your environment variables from',
+ descAws: 'AWS SSM',
+ descAzure: 'Azure Key Vault',
+ descGcp: 'GCP Secret Manager',
+ descOr: 'or',
+ descComma: ',',
+ descSuffix: 'as a single source of truth. No more copy-pasting secrets.',
+ getStarted: '▶ Get Started',
+ viewOnGithub: '★ View on GitHub',
+ terminalComment1: '# 1. Define your mapping',
+ terminalComment2: '# 2. Pull secrets → generate .env',
+ terminalFetched1: ' Fetched DB_PASSWORD → ···pass',
+ terminalFetched2: ' Fetched API_KEY → ···key',
+ terminalWritten: ' Environment file written to .env',
+ },
+ trust: {
+ label: 'WORKS WITH',
+ },
+ problemSolution: {
+ title: 'The ',
+ titleAccent: 'problem',
+ titleSuffix: ' with .env files',
+ subtitle:
+ "Managing secrets manually doesn't scale. It's insecure, error-prone, and creates friction for your entire team.",
+ problems: [
+ {
+ icon: '💀',
+ title: 'Desync between environments',
+ description:
+ 'Dev, staging, and prod have different secrets. Deployments fail. Nobody knows which .env is correct.',
+ },
+ {
+ icon: '📨',
+ title: 'Secrets shared via Slack/email',
+ description:
+ 'API keys sent in plain text over chat. No audit trail. No rotation. A security incident waiting to happen.',
+ },
+ {
+ icon: '🐌',
+ title: 'Slow onboarding & rotations',
+ description:
+ "New team member joins? Copy-paste a .env from somebody's machine. Someone rotates? Hope everyone updates manually.",
+ },
+ ],
+ arrowText: '▼ envilder fixes this ▼',
+ solutions: [
+ {
+ icon: '🛡️',
+ title: 'Cloud-native source of truth',
+ description:
+ 'All secrets live in AWS SSM or Azure Key Vault. IAM/RBAC controls who can read what. Every access is logged.',
+ },
+ {
+ icon: '⚡',
+ title: 'One command, always in sync',
+ description:
+ 'Run envilder and your .env is regenerated from the source of truth. Idempotent. Instant. No room for drift.',
+ },
+ {
+ icon: '🤖',
+ title: 'Automated in CI/CD',
+ description:
+ 'Use the GitHub Action to pull secrets at deploy time. No secrets stored in repos. No manual steps in pipelines.',
+ },
+ ],
+ },
+ howItWorks: {
+ title: 'How it ',
+ titleAccent: 'works',
+ subtitle:
+ 'Three steps. From scattered secrets to a single source of truth.',
+ steps: [
+ {
+ title: 'Create a mapping file',
+ description:
+ 'Map your environment variable names to their secret paths in AWS SSM or Azure Key Vault.',
+ },
+ {
+ title: 'Run one command',
+ description:
+ 'Envilder pulls each secret from your cloud provider and writes them to a local .env file. Idempotent and instant.',
+ },
+ {
+ title: 'Your .env is ready',
+ description:
+ 'A clean, up-to-date environment file — generated from the source of truth. Use it locally or inject it in CI/CD with the GitHub Action.',
+ },
+ ],
+ terminalFetched1: '✔ Fetched DB_PASSWORD → ···word',
+ terminalFetched2: '✔ Fetched API_KEY → ···key',
+ terminalFetched3: '✔ Fetched SECRET_TOKEN → ···oken',
+ terminalWritten: '✔ Environment file written to .env',
+ },
+ features: {
+ title: 'Built for ',
+ titleAccent: 'real teams',
+ subtitle:
+ 'Everything you need to manage environment secrets securely and at scale.',
+ features: [
+ {
+ icon: '☁️',
+ title: 'Multi-Provider',
+ description:
+ 'AWS SSM, Azure Key Vault, and GCP Secret Manager (coming soon). Choose with --provider or $config in your map file.',
+ },
+ {
+ icon: '🔄',
+ title: 'Bidirectional Sync',
+ description:
+ 'Pull secrets to .env files or push .env values back to your cloud provider. Full round-trip support.',
+ },
+ {
+ icon: '⚙️',
+ title: 'GitHub Action',
+ description:
+ 'Drop-in Action for your CI/CD workflows. Pull secrets at deploy time with zero manual intervention.',
+ },
+ {
+ icon: '🔒',
+ title: 'IAM & RBAC Access',
+ description:
+ 'Leverage native cloud access control. AWS IAM policies or Azure RBAC define who reads what, per environment.',
+ },
+ {
+ icon: '📊',
+ title: 'Fully Auditable',
+ description:
+ 'Every read and write is logged in AWS CloudTrail or Azure Monitor. Complete trace of who accessed what and when.',
+ },
+ {
+ icon: '🔁',
+ title: 'Idempotent Sync',
+ description:
+ "Only what's in your mapping gets updated. Nothing else is touched. Run it ten times — same result, zero side effects.",
+ },
+ {
+ icon: '🧱',
+ title: 'Zero Infrastructure',
+ description:
+ 'Built on native cloud services. No Lambdas, no servers, no extra infrastructure to manage or pay for.',
+ },
+ {
+ icon: '👤',
+ title: 'AWS Profile Support',
+ description:
+ 'Multi-account setups? Use --profile to switch between AWS CLI profiles. Perfect for multi-stage environments.',
+ },
+ {
+ icon: '🚀',
+ title: 'Exec Mode',
+ description:
+ 'Inject secrets directly into a child process without writing to disk. Zero .env files, zero risk of leaks.',
+ badge: 'Coming soon',
+ },
+ ],
+ },
+ demo: {
+ title: 'See it in ',
+ titleAccent: 'action',
+ subtitle:
+ 'Watch how Envilder simplifies secret management in under 2 minutes.',
+ cliDemo: 'CLI Demo — Pull Secrets',
+ ghaWorkflow: 'GitHub Action Workflow',
+ comingSoon: 'Coming soon',
+ },
+ providers: {
+ title: 'Your cloud. ',
+ titleAccent: 'Your choice.',
+ subtitle:
+ 'Envilder works with AWS SSM Parameter Store, Azure Key Vault, and GCP Secret Manager (coming soon). Configure inline or via CLI flags.',
+ awsTitle: 'AWS SSM Parameter Store',
+ awsDefault: 'Default provider',
+ awsFeatures: [
+ 'Supports GetParameter with WithDecryption',
+ 'AWS Profile support for multi-account',
+ 'IAM policy-based access control',
+ 'CloudTrail audit logging',
+ ],
+ azureTitle: 'Azure Key Vault',
+ azureBadge: 'New in v0.8',
+ azureFeatures: [
+ 'Auto-normalizes secret names (slashes → hyphens)',
+ 'DefaultAzureCredential authentication',
+ 'Azure RBAC access control',
+ 'Azure Monitor audit logging',
+ ],
+ gcpTitle: 'GCP Secret Manager',
+ gcpBadge: 'Coming soon',
+ gcpFeatures: [
+ 'Google Cloud Secret Manager integration',
+ 'Application Default Credentials (ADC)',
+ 'IAM-based access control',
+ 'Cloud Audit Logs',
+ ],
+ configPriorityTitle: 'Configuration priority',
+ priorityHigh: 'CLI flags / GHA inputs',
+ priorityMid: '$config in map file',
+ priorityLow: 'Defaults (AWS)',
+ },
+ gha: {
+ title: 'GitHub Action',
+ subtitle:
+ 'Pull secrets at deploy time. Drop it into any workflow in minutes.',
+ awsSsm: '☁️ AWS SSM',
+ azureKeyVault: '🔑 Azure Key Vault',
+ actionInputs: 'Action inputs',
+ thInput: 'Input',
+ thRequired: 'Required',
+ thDefault: 'Default',
+ thDescription: 'Description',
+ inputMapDesc: 'Path to JSON file mapping env vars to secret paths',
+ inputEnvDesc: 'Path to .env file to generate',
+ inputProviderDesc: 'Cloud provider: aws or azure (default: aws)',
+ inputVaultDesc: 'Azure Key Vault URL',
+ output: 'Output:',
+ outputDesc: 'Path to the generated .env file',
+ yes: 'Yes',
+ no: 'No',
+ },
+ changelog: {
+ title: "What's ",
+ titleAccent: 'new',
+ subtitle: 'Latest release highlights. Multi-provider support is here.',
+ releaseTitle: 'Multi-Provider Support',
+ releaseDate: new Date(
+ `${releaseMetadata.releaseDate}T00:00:00`,
+ ).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ }),
+ highlights: [
+ {
+ icon: '✨',
+ text: '$config section in map files — declare provider and connection details inline',
+ },
+ {
+ icon: '✨',
+ text: 'Azure Key Vault support — full parity with AWS SSM',
+ },
+ { icon: '✨', text: '--vault-url and --provider CLI flags' },
+ {
+ icon: '✨',
+ text: 'Automatic secret name normalization for Azure (slashes → hyphens)',
+ },
+ {
+ icon: '⚠️',
+ text: 'Breaking: --ssm-path renamed to --secret-path (old flag still works as deprecated alias)',
+ },
+ ],
+ fullChangelog: '📋 Full Changelog',
+ viewReleases: 'View all releases on GitHub →',
+ },
+ roadmap: {
+ title: "What's ",
+ titleAccent: 'next',
+ subtitle: "Envilder is actively developed. Here's where we're headed.",
+ upNext: 'Up next',
+ items: [
+ {
+ status: 'done',
+ label: '✅',
+ title: 'Pull secrets to .env',
+ description:
+ 'Map env var names to cloud secret paths via JSON and generate .env files automatically',
+ },
+ {
+ status: 'done',
+ label: '✅',
+ title: 'Push mode (--push)',
+ description: 'Upload .env values or single secrets to cloud provider',
+ },
+ {
+ status: 'done',
+ label: '✅',
+ title: 'GitHub Action',
+ description: 'Use Envilder in CI/CD workflows natively',
+ },
+ {
+ status: 'done',
+ label: '✅',
+ title: 'Multi-provider (AWS + Azure)',
+ description: 'AWS SSM Parameter Store and Azure Key Vault support',
+ },
+ {
+ status: 'done',
+ label: '📖',
+ title: 'Documentation website',
+ description: 'Dedicated docs site with guides, examples, API reference',
+ },
+ {
+ status: 'next',
+ label: '⚡',
+ title: 'Exec mode (--exec)',
+ description:
+ 'Inject secrets into child process without writing to disk',
+ },
+ {
+ status: 'planned',
+ label: '☁️',
+ title: 'GCP Secret Manager',
+ description: 'Third cloud provider — completes the multi-cloud trident',
+ },
+ {
+ status: 'planned',
+ label: '🔐',
+ title: 'AWS Secrets Manager',
+ description:
+ 'Support JSON-structured secrets alongside SSM Parameter Store',
+ },
+ {
+ status: 'planned',
+ label: '✔️',
+ title: 'Check/sync mode (--check)',
+ description:
+ 'Validate cloud secrets vs local .env — fail CI if out-of-sync',
+ },
+ ],
+ },
+ getStarted: {
+ title: 'Get ',
+ titleAccent: 'started',
+ subtitle: 'Up and running in under a minute.',
+ prerequisites: 'Prerequisites',
+ prereqNode: 'Node.js v20+',
+ prereqAws: 'AWS CLI configured',
+ prereqAzure: 'Azure CLI configured',
+ prereqIam: 'IAM permissions:',
+ prereqAwsNote: 'for AWS SSM',
+ prereqAzureNote: 'for Azure Key Vault',
+ install: 'Install',
+ quickStart: 'Quick start',
+ step1: 'Create a param-map.json mapping env vars to secret paths',
+ step2: 'Run envilder --map=param-map.json --envfile=.env',
+ step3: 'Your .env file is ready ✔',
+ terminalTitle: 'Quick start',
+ commentInstall: '# Install globally',
+ commentCreate: '# Create mapping file',
+ commentPull: '# Pull secrets',
+ commentPush: '# Push a secret',
+ doneMessage: ' Done! .env file generated.',
+ pushSuccess: ' Secret pushed successfully.',
+ },
+ footer: {
+ tagline:
+ 'Securely centralize your environment variables from AWS SSM, Azure Key Vault, or GCP Secret Manager.',
+ project: 'Project',
+ documentation: 'Documentation',
+ community: 'Community',
+ linkGithub: 'GitHub',
+ linkNpm: 'npm',
+ linkChangelog: 'Changelog',
+ linkRoadmap: 'Roadmap',
+ linkGettingStarted: 'Getting Started',
+ linkPullCommand: 'Pull Command',
+ linkPushCommand: 'Push Command',
+ linkGithubAction: 'GitHub Action',
+ linkIssues: 'Issues',
+ linkDiscussions: 'Discussions',
+ linkSecurity: 'Security',
+ linkSponsor: 'Sponsor',
+ license: 'MIT License',
+ copyright: 'Built with Astro. Open source on GitHub.',
+ builtWith: 'Built with Astro. Open source on GitHub.',
+ },
+ changelogPage: {
+ title: 'Changelog — Envilder',
+ backToHome: '← Back to home',
+ fullChangelog: 'Full ',
+ changelogAccent: 'Changelog',
+ intro: 'Complete release history. See also',
+ githubReleases: 'GitHub Releases',
+ versions: 'Versions',
+ backToTop: 'Back to top',
+ },
+ docs: {
+ title: 'Documentation — Envilder',
+ backToHome: '← Back to home',
+ pageTitle: 'Documentation',
+ intro: 'Everything you need to get started with Envilder.',
+ sidebarGettingStarted: 'Getting started',
+ sidebarRequirements: 'Requirements',
+ sidebarInstallation: 'Installation',
+ sidebarCredentials: 'Cloud credentials',
+ sidebarPermissions: 'IAM permissions',
+ sidebarCli: 'CLI',
+ sidebarMappingFile: 'Mapping file',
+ sidebarPullCommand: 'Pull command',
+ sidebarPushCommand: 'Push command',
+ sidebarPushSingle: 'Push single',
+ sidebarGha: 'GitHub Action',
+ sidebarGhaSetup: 'Setup',
+ sidebarGhaBasic: 'Basic example',
+ sidebarGhaMultiEnv: 'Multi-environment',
+ sidebarGhaAzure: 'Azure example',
+ sidebarGhaInputs: 'Inputs & outputs',
+ sidebarReference: 'Reference',
+ sidebarConfigPriority: 'Config priority',
+ sidebarAzureSetup: 'Azure setup',
+ overviewTitle: 'What is Envilder?',
+ overviewDesc:
+ 'Envilder is a CLI tool and GitHub Action that pulls environment variables from a cloud vault (AWS SSM Parameter Store or Azure Key Vault) and writes them to a local .env file — or pushes them back. You define a simple JSON mapping between variable names and secret paths, and Envilder does the rest.',
+ overviewProblem:
+ 'Without Envilder, teams copy secrets by hand, store them in plaintext .env files committed to repos, or maintain fragile shell scripts per environment. This leads to leaked credentials, inconsistent configurations, and slow onboarding.',
+ overviewSolution:
+ 'With Envilder, one param-map.json file is the single source of truth. Secrets never leave the vault until runtime, every environment uses the same mapping, and a new developer is up and running in one command.',
+ reqTitle: 'Requirements',
+ reqNode: 'Node.js v20+',
+ reqAws: 'AWS CLI',
+ reqAzure: 'Azure CLI',
+ reqAwsNote: 'for AWS SSM',
+ reqAzureNote: 'for Azure Key Vault',
+ reqDownload: 'Download',
+ reqInstallGuide: 'Install guide',
+ installTitle: 'Installation',
+ credTitle: 'Cloud credentials',
+ credAwsTitle: 'AWS (default)',
+ credAwsDesc:
+ 'Envilder uses your AWS CLI credentials. Set up the default profile:',
+ credAwsProfile: 'Or use a named profile:',
+ credAzureTitle: 'Azure Key Vault',
+ credAzureDesc: 'Envilder uses Azure Default Credentials. Log in with:',
+ credAzureVault:
+ 'Provide the vault URL via $config in your map file or the --vault-url flag.',
+ permTitle: 'IAM permissions',
+ permAwsTitle: 'AWS',
+ permAwsDesc: 'Your IAM user or role needs:',
+ permOperation: 'Operation',
+ permPermission: 'Permission',
+ permPull: 'Pull',
+ permPush: 'Push',
+ permPolicyExample: 'Example IAM policy:',
+ permAzureTitle: 'Azure',
+ permAzureRbac: 'Recommended — assign Key Vault Secrets Officer via RBAC:',
+ permAzurePullNote:
+ 'For pull-only access, Key Vault Secrets User is sufficient.',
+ mapTitle: 'Mapping file',
+ mapIntro:
+ "The mapping file (param-map.json) is the core of Envilder. It's a JSON file that maps environment variable names (keys) to secret paths (values) in your cloud provider.",
+ mapCalloutStructure: 'Structure:',
+ mapCalloutKey: 'Each key becomes an env var name in your .env file.',
+ mapCalloutValue:
+ 'Each value is the path where the secret lives in your cloud provider.',
+ mapBasicTitle: 'Basic format (AWS SSM — default)',
+ mapBasicDesc:
+ 'When no $config section is present, Envilder defaults to AWS SSM Parameter Store. Values must be valid SSM parameter paths (typically starting with /):',
+ mapBasicGenerates: 'This generates:',
+ mapConfigTitle: 'The $config section',
+ mapConfigDesc:
+ 'Add a $config key to your mapping file to declare which cloud provider to use and its settings. Envilder reads $config for configuration, and treats all other keys as secret mappings.',
+ mapConfigOptionsTitle: '$config options',
+ mapThKey: 'Key',
+ mapThType: 'Type',
+ mapThDefault: 'Default',
+ mapThDescription: 'Description',
+ mapProviderDesc: 'Cloud provider to use',
+ mapVaultUrlDesc: 'Azure Key Vault URL (required when provider is "azure")',
+ mapProfileDesc: 'AWS CLI profile for multi-account setups (AWS only)',
+ mapAwsProfileTitle: 'AWS SSM with profile',
+ mapAwsProfileDesc:
+ 'To use a specific AWS CLI profile (useful for multi-account setups), add profile to $config:',
+ mapAwsProfileExplain:
+ 'This tells Envilder to use the prod-account profile from your ~/.aws/credentials file instead of the default profile.',
+ mapAzureTitle: 'Azure Key Vault',
+ mapAzureDesc:
+ 'For Azure Key Vault, set provider to "azure" and provide the vaultUrl:',
+ mapAzureWarningTitle: 'Azure naming convention:',
+ mapAzureWarningDesc:
+ 'Key Vault secret names only allow alphanumeric characters and hyphens. Envilder automatically normalizes names — slashes and underscores become hyphens (e.g., /myapp/db/password → myapp-db-password).',
+ mapDifferencesTitle: 'Key differences by provider',
+ mapThEmpty: '',
+ mapThAwsSsm: 'AWS SSM',
+ mapThAzureKv: 'Azure Key Vault',
+ mapSecretPathFormat: 'Secret path format',
+ mapAwsPathFormat: 'Parameter paths with slashes',
+ mapAzurePathFormat: 'Hyphenated names',
+ mapRequiredConfig: 'Required $config',
+ mapAwsRequiredConfig: 'None (AWS is the default)',
+ mapAzureRequiredConfig: 'provider + vaultUrl',
+ mapOptionalConfig: 'Optional $config',
+ mapAuthentication: 'Authentication',
+ mapAwsAuth: 'AWS CLI credentials',
+ mapAzureAuth: 'Azure Default Credentials',
+ mapMultiEnvTitle: 'Multiple environments',
+ mapMultiEnvDesc:
+ 'A common pattern is having one mapping file per environment. The structure is the same, only the secret paths change:',
+ mapMultiEnvThenPull: 'Then pull the right one:',
+ mapOverrideTitle: 'Overriding $config with CLI flags',
+ mapOverrideDesc:
+ 'CLI flags always take priority over $config values. This lets you set defaults in the file and override per invocation:',
+ mapOverrideComment1: '# Uses $config from the map file as-is',
+ mapOverrideComment2: '# Overrides provider and vault URL, ignoring $config',
+ mapOverrideComment3: '# Overrides just the AWS profile',
+ mapPriorityNote:
+ 'Priority order: CLI flags / GHA inputs → $config in map file → defaults (AWS).',
+ pullTitle: 'Pull command',
+ pullDesc:
+ 'Download secrets from your cloud provider and generate a local .env file.',
+ pullOptions: 'Options',
+ pullExamples: 'Examples',
+ pullOutput: 'Output',
+ optionHeader: 'Option',
+ pullOptMap: 'Path to JSON mapping file',
+ pullOptEnv: 'Path to write .env',
+ pullOptProvider: 'aws (default) or azure',
+ pullOptVault: 'Azure Key Vault URL',
+ pullOptProfile: 'AWS CLI profile to use',
+ pullCommentDefault: '# Default (AWS SSM)',
+ pullCommentProfile: '# With AWS profile',
+ pullCommentAzureConfig: '# Azure via $config in map file',
+ pullCommentAzureFlags: '# Azure via CLI flags',
+ pullOutputTitle: 'Output',
+ pushTitle: 'Push command',
+ pushDesc:
+ 'Upload environment variables from a local .env file to your cloud provider using a mapping file.',
+ pushOptions: 'Options',
+ pushExamples: 'Examples',
+ pushOptPush: 'Enable push mode (required)',
+ pushOptEnv: 'Path to your local .env file',
+ pushOptMap: 'Path to parameter mapping JSON',
+ pushOptProvider: 'aws (default) or azure',
+ pushOptVault: 'Azure Key Vault URL',
+ pushOptProfile: 'AWS CLI profile (AWS only)',
+ pushCommentAws: '# Push to AWS SSM',
+ pushCommentProfile: '# With AWS profile',
+ pushCommentAzureConfig: '# Azure via $config in map file',
+ pushCommentAzureFlags: '# Azure via CLI flags',
+ pushSingleTitle: 'Push single variable',
+ pushSingleDesc:
+ 'Push a single environment variable directly without any files.',
+ pushSingleOptions: 'Options',
+ pushSingleOptPush: 'Enable push mode (required)',
+ pushSingleOptKey: 'Environment variable name',
+ pushSingleOptValue: 'Value to store',
+ pushSingleOptPath: 'Full secret path in your cloud provider',
+ pushSingleOptProvider: 'aws (default) or azure',
+ pushSingleOptVault: 'Azure Key Vault URL',
+ pushSingleOptProfile: 'AWS CLI profile (AWS only)',
+ ghaSetupTitle: 'GitHub Action setup',
+ ghaSetupDesc:
+ 'The Envilder GitHub Action pulls secrets from AWS SSM or Azure Key Vault into .env files during your CI/CD workflow. No build step needed — the action is pre-built and ready to use from GitHub Marketplace.',
+ ghaPrerequisites: 'Prerequisites',
+ ghaPrereqAws:
+ 'AWS: Configure credentials with aws-actions/configure-aws-credentials',
+ ghaPrereqAzure: 'Azure: Configure credentials with azure/login',
+ ghaPrereqMap: 'A param-map.json committed to your repository',
+ ghaPullOnly: 'The GitHub Action only supports pull mode (no push).',
+ ghaBasicTitle: 'Basic workflow example',
+ ghaMultiEnvTitle: 'Multi-environment workflow',
+ ghaAzureTitle: 'Azure Key Vault workflow',
+ ghaInputsTitle: 'Action inputs & outputs',
+ ghaInputsSubtitle: 'Inputs',
+ ghaOutputsSubtitle: 'Outputs',
+ ghaInputRequired: 'Required',
+ ghaInputDefault: 'Default',
+ ghaInputDesc: 'Description',
+ ghaOutputEnvPath: 'Path to the generated .env file',
+ ghaThInput: 'Input',
+ ghaThRequired: 'Required',
+ ghaThOutput: 'Output',
+ ghaYes: 'Yes',
+ ghaNo: 'No',
+ ghaInputMap: 'Path to JSON mapping file',
+ ghaInputEnv: 'Path to the .env file to generate',
+ ghaInputProvider: 'aws or azure',
+ ghaInputVault: 'Azure Key Vault URL',
+ configPriorityTitle: 'Configuration priority',
+ configPriorityDesc:
+ 'When multiple configuration sources are present, Envilder resolves them in this order (highest wins):',
+ configPriority1: 'CLI flags / GHA inputs',
+ configPriority2: '$config in map file',
+ configPriority3: 'Defaults (AWS)',
+ configPriorityExplain:
+ 'This means --provider=azure on the CLI will override "provider": "aws" in $config.',
+ azureSetupTitle: 'Azure Key Vault setup',
+ azureSetupCheck: 'Check which access model your vault uses:',
+ azureRbacTrue: 'true → Azure RBAC (recommended)',
+ azureRbacFalse: 'false / null → Vault Access Policy (classic)',
+ azureOptionA: 'Option A — Azure RBAC (recommended)',
+ azureOptionB: 'Option B — Vault Access Policy',
+ azureAccessNote:
+ 'For pull-only access, get list is enough. Add set for push.',
+ },
+};
diff --git a/src/apps/website/src/i18n/es.ts b/src/apps/website/src/i18n/es.ts
new file mode 100644
index 00000000..2393442e
--- /dev/null
+++ b/src/apps/website/src/i18n/es.ts
@@ -0,0 +1,619 @@
+import { releaseMetadata } from './releaseMetadata';
+import type { Translations } from './types';
+
+export const es: Translations = {
+ homeMeta: {
+ title: 'Envilder — Centraliza tus secretos. Un comando.',
+ description:
+ 'Una herramienta CLI y GitHub Action que centraliza de forma segura las variables de entorno desde AWS SSM, Azure Key Vault o GCP Secret Manager como fuente única de verdad.',
+ },
+ nav: {
+ features: 'Funcionalidades',
+ howItWorks: 'Cómo funciona',
+ providers: 'Proveedores',
+ githubAction: 'GitHub Action',
+ changelog: 'Cambios',
+ docs: 'Docs',
+ getStarted: 'Empezar',
+ },
+ theme: {
+ retro: 'Retro',
+ light: 'Claro',
+ },
+ hero: {
+ openSource: 'Código abierto · MIT',
+ title1: 'Tus secretos.',
+ title2: 'Un comando.',
+ titleAccent: 'Cada entorno.',
+ description:
+ 'Una herramienta CLI y GitHub Action que centraliza de forma segura tus variables de entorno desde',
+ descAws: 'AWS SSM',
+ descAzure: 'Azure Key Vault',
+ descGcp: 'GCP Secret Manager',
+ descOr: 'o',
+ descComma: ',',
+ descSuffix:
+ 'como fuente única de verdad. Se acabó copiar y pegar secretos.',
+ getStarted: '▶ Empezar',
+ viewOnGithub: '★ Ver en GitHub',
+ terminalComment1: '# 1. Define tu mapeo',
+ terminalComment2: '# 2. Descarga secretos → genera .env',
+ terminalFetched1: ' Obtenido DB_PASSWORD → ···pass',
+ terminalFetched2: ' Obtenido API_KEY → ···key',
+ terminalWritten: ' Archivo de entorno escrito en .env',
+ },
+ trust: {
+ label: 'COMPATIBLE CON',
+ },
+ problemSolution: {
+ title: 'El ',
+ titleAccent: 'problema',
+ titleSuffix: ' con archivos .env',
+ subtitle:
+ 'Gestionar secretos manualmente no escala. Es inseguro, propenso a errores y crea fricción para todo el equipo.',
+ problems: [
+ {
+ icon: '💀',
+ title: 'Desincronización entre entornos',
+ description:
+ 'Dev, staging y prod tienen secretos diferentes. Los despliegues fallan. Nadie sabe qué .env es el correcto.',
+ },
+ {
+ icon: '📨',
+ title: 'Secretos compartidos por Slack/email',
+ description:
+ 'Claves API enviadas en texto plano por chat. Sin trazabilidad. Sin rotación. Un incidente de seguridad esperando a ocurrir.',
+ },
+ {
+ icon: '🐌',
+ title: 'Onboarding y rotaciones lentas',
+ description:
+ '¿Un nuevo miembro se une al equipo? Copia y pega un .env de la máquina de alguien. ¿Alguien rota? Espera que todos actualicen manualmente.',
+ },
+ ],
+ arrowText: '▼ envilder lo soluciona ▼',
+ solutions: [
+ {
+ icon: '🛡️',
+ title: 'Fuente de verdad en la nube',
+ description:
+ 'Todos los secretos viven en AWS SSM o Azure Key Vault. IAM/RBAC controla quién puede leer qué. Cada acceso queda registrado.',
+ },
+ {
+ icon: '⚡',
+ title: 'Un comando, siempre sincronizado',
+ description:
+ 'Ejecuta envilder y tu .env se regenera desde la fuente de verdad. Idempotente. Instantáneo. Sin margen para el desfase.',
+ },
+ {
+ icon: '🤖',
+ title: 'Automatizado en CI/CD',
+ description:
+ 'Usa la GitHub Action para obtener secretos en el momento del despliegue. Sin secretos en los repos. Sin pasos manuales en los pipelines.',
+ },
+ ],
+ },
+ howItWorks: {
+ title: 'Cómo ',
+ titleAccent: 'funciona',
+ subtitle: 'Tres pasos. De secretos dispersos a una única fuente de verdad.',
+ steps: [
+ {
+ title: 'Crea un archivo de mapeo',
+ description:
+ 'Mapea los nombres de tus variables de entorno a sus rutas de secretos en AWS SSM o Azure Key Vault.',
+ },
+ {
+ title: 'Ejecuta un comando',
+ description:
+ 'Envilder obtiene cada secreto de tu proveedor en la nube y los escribe en un archivo .env local. Idempotente e instantáneo.',
+ },
+ {
+ title: 'Tu .env está listo',
+ description:
+ 'Un archivo de entorno limpio y actualizado — generado desde la fuente de verdad. Úsalo localmente o inyéctalo en CI/CD con la GitHub Action.',
+ },
+ ],
+ terminalFetched1: '✔ Obtenido DB_PASSWORD → ···word',
+ terminalFetched2: '✔ Obtenido API_KEY → ···key',
+ terminalFetched3: '✔ Obtenido SECRET_TOKEN → ···oken',
+ terminalWritten: '✔ Archivo de entorno escrito en .env',
+ },
+ features: {
+ title: 'Hecho para ',
+ titleAccent: 'equipos reales',
+ subtitle:
+ 'Todo lo que necesitas para gestionar secretos de entorno de forma segura y a escala.',
+ features: [
+ {
+ icon: '☁️',
+ title: 'Multi-Proveedor',
+ description:
+ 'AWS SSM, Azure Key Vault y GCP Secret Manager (próximamente). Elige con --provider o $config en tu archivo de mapeo.',
+ },
+ {
+ icon: '🔄',
+ title: 'Sincronización bidireccional',
+ description:
+ 'Obtén secretos en archivos .env o sube valores .env a tu proveedor en la nube. Soporte completo de ida y vuelta.',
+ },
+ {
+ icon: '⚙️',
+ title: 'GitHub Action',
+ description:
+ 'Action para tus workflows CI/CD. Obtén secretos en el momento del despliegue sin intervención manual.',
+ },
+ {
+ icon: '🔒',
+ title: 'Acceso IAM y RBAC',
+ description:
+ 'Aprovecha el control de acceso nativo de la nube. Las políticas IAM de AWS o RBAC de Azure definen quién lee qué, por entorno.',
+ },
+ {
+ icon: '📊',
+ title: 'Totalmente auditable',
+ description:
+ 'Cada lectura y escritura queda registrada en AWS CloudTrail o Azure Monitor. Trazabilidad completa de quién accedió a qué y cuándo.',
+ },
+ {
+ icon: '🔁',
+ title: 'Sincronización idempotente',
+ description:
+ 'Solo se actualiza lo que hay en tu mapeo. Nada más se toca. Ejecútalo diez veces — mismo resultado, cero efectos secundarios.',
+ },
+ {
+ icon: '🧱',
+ title: 'Cero infraestructura',
+ description:
+ 'Construido sobre servicios nativos de la nube. Sin Lambdas, sin servidores, sin infraestructura extra que gestionar o pagar.',
+ },
+ {
+ icon: '👤',
+ title: 'Soporte de perfiles AWS',
+ description:
+ '¿Configuración multi-cuenta? Usa --profile para cambiar entre perfiles AWS CLI. Perfecto para entornos multi-etapa.',
+ },
+ {
+ icon: '🚀',
+ title: 'Modo Exec',
+ description:
+ 'Inyecta secretos directamente en un proceso hijo sin escribir a disco. Cero archivos .env, cero riesgo de fugas.',
+ badge: 'Próximamente',
+ },
+ ],
+ },
+ demo: {
+ title: 'Míralo en ',
+ titleAccent: 'acción',
+ subtitle:
+ 'Mira cómo Envilder simplifica la gestión de secretos en menos de 2 minutos.',
+ cliDemo: 'Demo CLI — Obtener Secretos',
+ ghaWorkflow: 'Workflow de GitHub Action',
+ comingSoon: 'Próximamente',
+ },
+ providers: {
+ title: 'Tu nube. ',
+ titleAccent: 'Tu elección.',
+ subtitle:
+ 'Envilder funciona con AWS SSM Parameter Store, Azure Key Vault y GCP Secret Manager (próximamente). Configura en línea o con flags CLI.',
+ awsTitle: 'AWS SSM Parameter Store',
+ awsDefault: 'Proveedor por defecto',
+ awsFeatures: [
+ 'Soporte de GetParameter con WithDecryption',
+ 'Soporte de perfil AWS para multi-cuenta',
+ 'Control de acceso basado en políticas IAM',
+ 'Registro de auditoría CloudTrail',
+ ],
+ azureTitle: 'Azure Key Vault',
+ azureBadge: 'Nuevo en v0.8',
+ azureFeatures: [
+ 'Auto-normaliza nombres de secretos (barras → guiones)',
+ 'Autenticación DefaultAzureCredential',
+ 'Control de acceso Azure RBAC',
+ 'Registro de auditoría Azure Monitor',
+ ],
+ gcpTitle: 'GCP Secret Manager',
+ gcpBadge: 'Próximamente',
+ gcpFeatures: [
+ 'Integración con Google Cloud Secret Manager',
+ 'Application Default Credentials (ADC)',
+ 'Control de acceso basado en IAM',
+ 'Cloud Audit Logs',
+ ],
+ configPriorityTitle: 'Prioridad de configuración',
+ priorityHigh: 'Flags CLI / Inputs GHA',
+ priorityMid: '$config en archivo de mapeo',
+ priorityLow: 'Por defecto (AWS)',
+ },
+ gha: {
+ title: 'GitHub Action',
+ subtitle:
+ 'Obtén secretos en el momento del despliegue. Añádelo a cualquier workflow en minutos.',
+ awsSsm: '☁️ AWS SSM',
+ azureKeyVault: '🔑 Azure Key Vault',
+ actionInputs: 'Inputs de la Action',
+ thInput: 'Input',
+ thRequired: 'Requerido',
+ thDefault: 'Por defecto',
+ thDescription: 'Descripción',
+ inputMapDesc:
+ 'Ruta al archivo JSON que mapea variables de entorno a rutas de secretos',
+ inputEnvDesc: 'Ruta al archivo .env a generar',
+ inputProviderDesc: 'Proveedor en la nube: aws o azure (por defecto: aws)',
+ inputVaultDesc: 'URL de Azure Key Vault',
+ output: 'Output:',
+ outputDesc: 'Ruta al archivo .env generado',
+ yes: 'Sí',
+ no: 'No',
+ },
+ changelog: {
+ title: 'Qué hay de ',
+ titleAccent: 'nuevo',
+ subtitle:
+ 'Novedades de la última versión. El soporte multi-proveedor ya está aquí.',
+ releaseTitle: 'Soporte Multi-Proveedor',
+ releaseDate: new Date(
+ `${releaseMetadata.releaseDate}T00:00:00`,
+ ).toLocaleDateString('es-ES', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ }),
+ highlights: [
+ {
+ icon: '✨',
+ text: 'Sección $config en archivos de mapeo — declara proveedor y detalles de conexión en línea',
+ },
+ {
+ icon: '✨',
+ text: 'Soporte de Azure Key Vault — paridad completa con AWS SSM',
+ },
+ { icon: '✨', text: 'Flags CLI --vault-url y --provider' },
+ {
+ icon: '✨',
+ text: 'Normalización automática de nombres de secretos para Azure (barras → guiones)',
+ },
+ {
+ icon: '⚠️',
+ text: 'Cambio incompatible: --ssm-path renombrado a --secret-path (el antiguo flag sigue funcionando como alias obsoleto)',
+ },
+ ],
+ fullChangelog: '📋 Historial completo',
+ viewReleases: 'Ver todas las versiones en GitHub →',
+ },
+ roadmap: {
+ title: 'Qué viene ',
+ titleAccent: 'ahora',
+ subtitle: 'Envilder se desarrolla activamente. Aquí es adónde vamos.',
+ upNext: 'Próximamente',
+ items: [
+ {
+ status: 'done',
+ label: '✅',
+ title: 'Descargar secretos a .env',
+ description:
+ 'Mapea nombres de variables de entorno a rutas de secretos en la nube vía JSON y genera archivos .env automáticamente',
+ },
+ {
+ status: 'done',
+ label: '✅',
+ title: 'Modo push (--push)',
+ description:
+ 'Sube valores .env o secretos individuales al proveedor en la nube',
+ },
+ {
+ status: 'done',
+ label: '✅',
+ title: 'GitHub Action',
+ description: 'Usa Envilder en workflows CI/CD de forma nativa',
+ },
+ {
+ status: 'done',
+ label: '✅',
+ title: 'Multi-proveedor (AWS + Azure)',
+ description: 'Soporte de AWS SSM Parameter Store y Azure Key Vault',
+ },
+ {
+ status: 'done',
+ label: '📖',
+ title: 'Web de documentación',
+ description:
+ 'Web de docs dedicada con guías, ejemplos y referencia API',
+ },
+ {
+ status: 'next',
+ label: '⚡',
+ title: 'Modo exec (--exec)',
+ description: 'Inyecta secretos en un proceso hijo sin escribir a disco',
+ },
+ {
+ status: 'planned',
+ label: '☁️',
+ title: 'GCP Secret Manager',
+ description: 'Tercer proveedor cloud — completa el tridente multi-nube',
+ },
+ {
+ status: 'planned',
+ label: '🔐',
+ title: 'AWS Secrets Manager',
+ description: 'Soporte de secretos JSON junto a SSM Parameter Store',
+ },
+ {
+ status: 'planned',
+ label: '✔️',
+ title: 'Modo check/sync (--check)',
+ description:
+ 'Valida secretos en la nube vs .env local — falla CI si están desincronizados',
+ },
+ ],
+ },
+ getStarted: {
+ title: 'Empieza ',
+ titleAccent: 'ahora',
+ subtitle: 'En funcionamiento en menos de un minuto.',
+ prerequisites: 'Prerrequisitos',
+ prereqNode: 'Node.js v20+',
+ prereqAws: 'AWS CLI configurado',
+ prereqAzure: 'Azure CLI configurado',
+ prereqIam: 'Permisos IAM:',
+ prereqAwsNote: 'para AWS SSM',
+ prereqAzureNote: 'para Azure Key Vault',
+ install: 'Instalar',
+ quickStart: 'Inicio rápido',
+ step1:
+ 'Crea un param-map.json que mapee variables de entorno a rutas de secretos',
+ step2: 'Ejecuta envilder --map=param-map.json --envfile=.env',
+ step3: 'Tu archivo .env está listo ✔',
+ terminalTitle: 'Inicio rápido',
+ commentInstall: '# Instalar globalmente',
+ commentCreate: '# Crear archivo de mapeo',
+ commentPull: '# Obtener secretos',
+ commentPush: '# Subir un secreto',
+ doneMessage: ' ¡Hecho! Archivo .env generado.',
+ pushSuccess: ' Secreto subido correctamente.',
+ },
+ footer: {
+ tagline:
+ 'Centraliza de forma segura tus variables de entorno desde AWS SSM, Azure Key Vault o GCP Secret Manager.',
+ project: 'Proyecto',
+ documentation: 'Documentación',
+ community: 'Comunidad',
+ linkGithub: 'GitHub',
+ linkNpm: 'npm',
+ linkChangelog: 'Cambios',
+ linkRoadmap: 'Hoja de ruta',
+ linkGettingStarted: 'Empezar',
+ linkPullCommand: 'Comando Pull',
+ linkPushCommand: 'Comando Push',
+ linkGithubAction: 'GitHub Action',
+ linkIssues: 'Incidencias',
+ linkDiscussions: 'Discusiones',
+ linkSecurity: 'Seguridad',
+ linkSponsor: 'Patrocinar',
+ license: 'Licencia MIT',
+ copyright: 'Hecho con Astro. Código abierto en GitHub.',
+ builtWith: 'Hecho con Astro. Código abierto en GitHub.',
+ },
+ changelogPage: {
+ title: 'Historial de cambios — Envilder',
+ backToHome: '← Volver al inicio',
+ fullChangelog: 'Historial de ',
+ changelogAccent: 'cambios',
+ intro: 'Historial completo de versiones. Ver también',
+ githubReleases: 'Versiones en GitHub',
+ versions: 'Versiones',
+ backToTop: 'Volver arriba',
+ },
+ docs: {
+ title: 'Documentación — Envilder',
+ backToHome: '← Volver al inicio',
+ pageTitle: 'Documentación',
+ intro: 'Todo lo que necesitas para empezar con Envilder.',
+ sidebarGettingStarted: 'Primeros pasos',
+ sidebarRequirements: 'Requisitos',
+ sidebarInstallation: 'Instalación',
+ sidebarCredentials: 'Credenciales de nube',
+ sidebarPermissions: 'Permisos IAM',
+ sidebarCli: 'CLI',
+ sidebarMappingFile: 'Archivo de mapeo',
+ sidebarPullCommand: 'Comando pull',
+ sidebarPushCommand: 'Comando push',
+ sidebarPushSingle: 'Push individual',
+ sidebarGha: 'GitHub Action',
+ sidebarGhaSetup: 'Configuración',
+ sidebarGhaBasic: 'Ejemplo básico',
+ sidebarGhaMultiEnv: 'Multi-entorno',
+ sidebarGhaAzure: 'Ejemplo Azure',
+ sidebarGhaInputs: 'Inputs y outputs',
+ sidebarReference: 'Referencia',
+ sidebarConfigPriority: 'Prioridad de config',
+ sidebarAzureSetup: 'Configuración Azure',
+ overviewTitle: '¿Qué es Envilder?',
+ overviewDesc:
+ 'Envilder es una herramienta CLI y GitHub Action que descarga variables de entorno de un almacén de secretos en la nube (AWS SSM Parameter Store o Azure Key Vault) y las escribe en un archivo .env local — o las sube de vuelta. Defines un simple mapeo JSON entre nombres de variables y rutas de secretos, y Envilder hace el resto.',
+ overviewProblem:
+ 'Sin Envilder, los equipos copian secretos a mano, los guardan en archivos .env en texto plano en el repositorio, o mantienen scripts de shell frágiles por cada entorno. Esto lleva a credenciales filtradas, configuraciones inconsistentes e incorporaciones lentas.',
+ overviewSolution:
+ 'Con Envilder, un archivo param-map.json es la fuente única de verdad. Los secretos no salen del almacén hasta el momento de ejecución, cada entorno usa el mismo mapeo, y un nuevo desarrollador está operativo con un solo comando.',
+ reqTitle: 'Requisitos',
+ reqNode: 'Node.js v20+',
+ reqAws: 'AWS CLI',
+ reqAzure: 'Azure CLI',
+ reqAwsNote: 'para AWS SSM',
+ reqAzureNote: 'para Azure Key Vault',
+ reqDownload: 'Descargar',
+ reqInstallGuide: 'Guía de instalación',
+ installTitle: 'Instalación',
+ credTitle: 'Credenciales de nube',
+ credAwsTitle: 'AWS (por defecto)',
+ credAwsDesc:
+ 'Envilder usa tus credenciales AWS CLI. Configura el perfil por defecto:',
+ credAwsProfile: 'O usa un perfil con nombre:',
+ credAzureTitle: 'Azure Key Vault',
+ credAzureDesc: 'Envilder usa Azure Default Credentials. Inicia sesión con:',
+ credAzureVault:
+ 'Proporciona la URL del vault vía $config en tu archivo de mapeo o el flag --vault-url.',
+ permTitle: 'Permisos IAM',
+ permAwsTitle: 'AWS',
+ permAwsDesc: 'Tu usuario o rol IAM necesita:',
+ permOperation: 'Operación',
+ permPermission: 'Permiso',
+ permPull: 'Pull',
+ permPush: 'Push',
+ permPolicyExample: 'Ejemplo de política IAM:',
+ permAzureTitle: 'Azure',
+ permAzureRbac: 'Recomendado — asigna Key Vault Secrets Officer vía RBAC:',
+ permAzurePullNote:
+ 'Para acceso solo de lectura, Key Vault Secrets User es suficiente.',
+ mapTitle: 'Archivo de mapeo',
+ mapIntro:
+ 'El archivo de mapeo (param-map.json) es el núcleo de Envilder. Es un archivo JSON que mapea nombres de variables de entorno (claves) a rutas de secretos (valores) en tu proveedor en la nube.',
+ mapCalloutStructure: 'Estructura:',
+ mapCalloutKey:
+ 'Cada clave se convierte en un nombre de variable de entorno en tu archivo .env.',
+ mapCalloutValue:
+ 'Cada valor es la ruta donde vive el secreto en tu proveedor en la nube.',
+ mapBasicTitle: 'Formato básico (AWS SSM — por defecto)',
+ mapBasicDesc:
+ 'Cuando no hay sección $config, Envilder usa AWS SSM Parameter Store por defecto. Los valores deben ser rutas de parámetros SSM válidas (normalmente comenzando con /):',
+ mapBasicGenerates: 'Esto genera:',
+ mapConfigTitle: 'La sección $config',
+ mapConfigDesc:
+ 'Añade una clave $config a tu archivo de mapeo para declarar qué proveedor en la nube usar y su configuración. Envilder lee $config para la configuración y trata todas las demás claves como mapeos de secretos.',
+ mapConfigOptionsTitle: 'Opciones de $config',
+ mapThKey: 'Clave',
+ mapThType: 'Tipo',
+ mapThDefault: 'Por defecto',
+ mapThDescription: 'Descripción',
+ mapProviderDesc: 'Proveedor en la nube a usar',
+ mapVaultUrlDesc:
+ 'URL de Azure Key Vault (requerido cuando el proveedor es "azure")',
+ mapProfileDesc:
+ 'Perfil AWS CLI para configuraciones multi-cuenta (solo AWS)',
+ mapAwsProfileTitle: 'AWS SSM con perfil',
+ mapAwsProfileDesc:
+ 'Para usar un perfil AWS CLI específico (útil para configuraciones multi-cuenta), añade profile a $config:',
+ mapAwsProfileExplain:
+ 'Esto indica a Envilder que use el perfil prod-account de tu archivo ~/.aws/credentials en lugar del perfil por defecto.',
+ mapAzureTitle: 'Azure Key Vault',
+ mapAzureDesc:
+ 'Para Azure Key Vault, establece provider a "azure" y proporciona el vaultUrl:',
+ mapAzureWarningTitle: 'Convención de nombres Azure:',
+ mapAzureWarningDesc:
+ 'Los nombres de secretos de Key Vault solo permiten caracteres alfanuméricos y guiones. Envilder normaliza automáticamente los nombres — barras y guiones bajos se convierten en guiones (ej., /myapp/db/password → myapp-db-password).',
+ mapDifferencesTitle: 'Diferencias clave por proveedor',
+ mapThEmpty: '',
+ mapThAwsSsm: 'AWS SSM',
+ mapThAzureKv: 'Azure Key Vault',
+ mapSecretPathFormat: 'Formato de ruta de secreto',
+ mapAwsPathFormat: 'Rutas de parámetros con barras',
+ mapAzurePathFormat: 'Nombres con guiones',
+ mapRequiredConfig: '$config requerido',
+ mapAwsRequiredConfig: 'Ninguno (AWS es por defecto)',
+ mapAzureRequiredConfig: 'provider + vaultUrl',
+ mapOptionalConfig: '$config opcional',
+ mapAuthentication: 'Autenticación',
+ mapAwsAuth: 'Credenciales AWS CLI',
+ mapAzureAuth: 'Azure Default Credentials',
+ mapMultiEnvTitle: 'Múltiples entornos',
+ mapMultiEnvDesc:
+ 'Un patrón común es tener un archivo de mapeo por entorno. La estructura es la misma, solo cambian las rutas de los secretos:',
+ mapMultiEnvThenPull: 'Luego obtén el correcto:',
+ mapOverrideTitle: 'Sobreescribir $config con flags CLI',
+ mapOverrideDesc:
+ 'Los flags CLI siempre tienen prioridad sobre los valores de $config. Esto te permite establecer valores por defecto en el archivo y sobreescribirlos por invocación:',
+ mapOverrideComment1: '# Usa $config del archivo de mapeo tal cual',
+ mapOverrideComment2:
+ '# Sobreescribe proveedor y URL del vault, ignorando $config',
+ mapOverrideComment3: '# Sobreescribe solo el perfil AWS',
+ mapPriorityNote:
+ 'Orden de prioridad: flags CLI / inputs GHA → $config en archivo de mapeo → por defecto (AWS).',
+ pullTitle: 'Comando pull',
+ pullDesc:
+ 'Descarga secretos de tu proveedor en la nube y genera un archivo .env local.',
+ pullOptions: 'Opciones',
+ pullExamples: 'Ejemplos',
+ pullOutput: 'Salida',
+ optionHeader: 'Opción',
+ pullOptMap: 'Ruta al archivo JSON de mapeo',
+ pullOptEnv: 'Ruta donde escribir el .env',
+ pullOptProvider: 'aws (por defecto) o azure',
+ pullOptVault: 'URL de Azure Key Vault',
+ pullOptProfile: 'Perfil AWS CLI a usar',
+ pullCommentDefault: '# Por defecto (AWS SSM)',
+ pullCommentProfile: '# Con perfil AWS',
+ pullCommentAzureConfig: '# Azure vía $config en archivo de mapeo',
+ pullCommentAzureFlags: '# Azure vía flags CLI',
+ pullOutputTitle: 'Salida',
+ pushTitle: 'Comando push',
+ pushDesc:
+ 'Sube variables de entorno de un archivo .env local a tu proveedor en la nube usando un archivo de mapeo.',
+ pushOptions: 'Opciones',
+ pushExamples: 'Ejemplos',
+ pushOptPush: 'Activa el modo push (requerido)',
+ pushOptEnv: 'Ruta a tu archivo .env local',
+ pushOptMap: 'Ruta al JSON de mapeo de parámetros',
+ pushOptProvider: 'aws (por defecto) o azure',
+ pushOptVault: 'URL de Azure Key Vault',
+ pushOptProfile: 'Perfil AWS CLI (solo AWS)',
+ pushCommentAws: '# Subir a AWS SSM',
+ pushCommentProfile: '# Con perfil AWS',
+ pushCommentAzureConfig: '# Azure vía $config en archivo de mapeo',
+ pushCommentAzureFlags: '# Azure vía flags CLI',
+ pushSingleTitle: 'Subir variable individual',
+ pushSingleDesc:
+ 'Sube una variable de entorno individual directamente sin ningún archivo.',
+ pushSingleOptions: 'Opciones',
+ pushSingleOptPush: 'Activa el modo push (requerido)',
+ pushSingleOptKey: 'Nombre de la variable de entorno',
+ pushSingleOptValue: 'Valor a almacenar',
+ pushSingleOptPath: 'Ruta completa del secreto en tu proveedor en la nube',
+ pushSingleOptProvider: 'aws (por defecto) o azure',
+ pushSingleOptVault: 'URL de Azure Key Vault',
+ pushSingleOptProfile: 'Perfil AWS CLI (solo AWS)',
+ ghaSetupTitle: 'Configuración de GitHub Action',
+ ghaSetupDesc:
+ 'La GitHub Action de Envilder obtiene secretos de AWS SSM o Azure Key Vault en archivos .env durante tu workflow CI/CD. No hace falta compilar — la action está pre-construida y lista para usar desde GitHub Marketplace.',
+ ghaPrerequisites: 'Prerrequisitos',
+ ghaPrereqAws:
+ 'AWS: Configura credenciales con aws-actions/configure-aws-credentials',
+ ghaPrereqAzure: 'Azure: Configura credenciales con azure/login',
+ ghaPrereqMap: 'Un param-map.json en tu repositorio',
+ ghaPullOnly: 'La GitHub Action solo soporta el modo pull (sin push).',
+ ghaBasicTitle: 'Ejemplo básico de workflow',
+ ghaMultiEnvTitle: 'Workflow multi-entorno',
+ ghaAzureTitle: 'Workflow de Azure Key Vault',
+ ghaInputsTitle: 'Inputs y outputs de la Action',
+ ghaInputsSubtitle: 'Inputs',
+ ghaOutputsSubtitle: 'Outputs',
+ ghaInputRequired: 'Requerido',
+ ghaInputDefault: 'Por defecto',
+ ghaInputDesc: 'Descripción',
+ ghaOutputEnvPath: 'Ruta al archivo .env generado',
+ ghaThInput: 'Input',
+ ghaThRequired: 'Requerido',
+ ghaThOutput: 'Output',
+ ghaYes: 'Sí',
+ ghaNo: 'No',
+ ghaInputMap: 'Ruta al archivo JSON de mapeo',
+ ghaInputEnv: 'Ruta al archivo .env a generar',
+ ghaInputProvider: 'aws o azure',
+ ghaInputVault: 'URL de Azure Key Vault',
+ configPriorityTitle: 'Prioridad de configuración',
+ configPriorityDesc:
+ 'Cuando hay múltiples fuentes de configuración, Envilder las resuelve en este orden (el más alto gana):',
+ configPriority1: 'Flags CLI / inputs GHA',
+ configPriority2: '$config en el archivo de mapeo',
+ configPriority3: 'Por defecto (AWS)',
+ configPriorityExplain:
+ 'Esto significa que --provider=azure en la CLI sobreescribirá "provider": "aws" en $config.',
+ azureSetupTitle: 'Configuración de Azure Key Vault',
+ azureSetupCheck: 'Comprueba qué modelo de acceso usa tu vault:',
+ azureRbacTrue: 'true → Azure RBAC (recomendado)',
+ azureRbacFalse: 'false / null → Vault Access Policy (clásico)',
+ azureOptionA: 'Opción A — Azure RBAC (recomendado)',
+ azureOptionB: 'Opción B — Vault Access Policy',
+ azureAccessNote:
+ 'Para acceso solo de lectura, get list es suficiente. Añade set para push.',
+ },
+};
diff --git a/src/apps/website/src/i18n/releaseMetadata.ts b/src/apps/website/src/i18n/releaseMetadata.ts
new file mode 100644
index 00000000..8d1275b5
--- /dev/null
+++ b/src/apps/website/src/i18n/releaseMetadata.ts
@@ -0,0 +1,12 @@
+/**
+ * Single source of truth for release-specific facts displayed on the website.
+ * Update this file when publishing a new version so all locale files stay in sync.
+ */
+export const releaseMetadata = {
+ /** Latest featured release version label */
+ releaseVersion: __APP_VERSION__,
+ /** ISO date of the featured release */
+ releaseDate: '2026-03-22',
+ /** Number of non-translatable highlight icons */
+ highlightIcons: ['✨', '✨', '✨', '✨', '⚠️'] as const,
+};
diff --git a/src/apps/website/src/i18n/types.ts b/src/apps/website/src/i18n/types.ts
new file mode 100644
index 00000000..00d8f53b
--- /dev/null
+++ b/src/apps/website/src/i18n/types.ts
@@ -0,0 +1,443 @@
+export interface NavLinks {
+ features: string;
+ howItWorks: string;
+ providers: string;
+ githubAction: string;
+ changelog: string;
+ docs: string;
+ getStarted: string;
+}
+
+export interface ThemeTranslations {
+ retro: string;
+ light: string;
+}
+
+export interface HeroTranslations {
+ openSource: string;
+ title1: string;
+ title2: string;
+ titleAccent: string;
+ description: string;
+ descAws: string;
+ descAzure: string;
+ descGcp: string;
+ descOr: string;
+ descComma: string;
+ descSuffix: string;
+ getStarted: string;
+ viewOnGithub: string;
+ terminalComment1: string;
+ terminalComment2: string;
+ terminalFetched1: string;
+ terminalFetched2: string;
+ terminalWritten: string;
+}
+
+export interface TrustTranslations {
+ label: string;
+}
+
+export interface ProblemItem {
+ icon: string;
+ title: string;
+ description: string;
+}
+
+export interface ProblemSolutionTranslations {
+ title: string;
+ titleAccent: string;
+ titleSuffix: string;
+ subtitle: string;
+ problems: ProblemItem[];
+ arrowText: string;
+ solutions: ProblemItem[];
+}
+
+export interface StepItem {
+ title: string;
+ description: string;
+}
+
+export interface HowItWorksTranslations {
+ title: string;
+ titleAccent: string;
+ subtitle: string;
+ steps: StepItem[];
+ terminalFetched1: string;
+ terminalFetched2: string;
+ terminalFetched3: string;
+ terminalWritten: string;
+}
+
+export interface FeatureItem {
+ icon: string;
+ title: string;
+ description: string;
+ badge?: string;
+}
+
+export interface FeaturesTranslations {
+ title: string;
+ titleAccent: string;
+ subtitle: string;
+ features: FeatureItem[];
+}
+
+export interface DemoTranslations {
+ title: string;
+ titleAccent: string;
+ subtitle: string;
+ cliDemo: string;
+ ghaWorkflow: string;
+ comingSoon: string;
+}
+
+export interface ProvidersTranslations {
+ title: string;
+ titleAccent: string;
+ subtitle: string;
+ awsTitle: string;
+ awsDefault: string;
+ awsFeatures: string[];
+ azureTitle: string;
+ azureBadge: string;
+ azureFeatures: string[];
+ gcpTitle: string;
+ gcpBadge: string;
+ gcpFeatures: string[];
+ configPriorityTitle: string;
+ priorityHigh: string;
+ priorityMid: string;
+ priorityLow: string;
+}
+
+export interface GhaTranslations {
+ title: string;
+ subtitle: string;
+ awsSsm: string;
+ azureKeyVault: string;
+ actionInputs: string;
+ thInput: string;
+ thRequired: string;
+ thDefault: string;
+ thDescription: string;
+ inputMapDesc: string;
+ inputEnvDesc: string;
+ inputProviderDesc: string;
+ inputVaultDesc: string;
+ output: string;
+ outputDesc: string;
+ yes: string;
+ no: string;
+}
+
+export interface ChangelogHighlight {
+ icon: string;
+ text: string;
+}
+
+export interface ChangelogTranslations {
+ title: string;
+ titleAccent: string;
+ subtitle: string;
+ releaseTitle: string;
+ releaseDate: string;
+ highlights: ChangelogHighlight[];
+ fullChangelog: string;
+ viewReleases: string;
+}
+
+export interface RoadmapItem {
+ status: string;
+ label: string;
+ title: string;
+ description: string;
+}
+
+export interface RoadmapTranslations {
+ title: string;
+ titleAccent: string;
+ subtitle: string;
+ upNext: string;
+ items: RoadmapItem[];
+}
+
+export interface GetStartedTranslations {
+ title: string;
+ titleAccent: string;
+ subtitle: string;
+ prerequisites: string;
+ prereqNode: string;
+ prereqAws: string;
+ prereqAzure: string;
+ prereqIam: string;
+ prereqAwsNote: string;
+ prereqAzureNote: string;
+ install: string;
+ quickStart: string;
+ step1: string;
+ step2: string;
+ step3: string;
+ terminalTitle: string;
+ commentInstall: string;
+ commentCreate: string;
+ commentPull: string;
+ commentPush: string;
+ doneMessage: string;
+ pushSuccess: string;
+}
+
+export interface FooterTranslations {
+ tagline: string;
+ project: string;
+ documentation: string;
+ community: string;
+ linkGithub: string;
+ linkNpm: string;
+ linkChangelog: string;
+ linkRoadmap: string;
+ linkGettingStarted: string;
+ linkPullCommand: string;
+ linkPushCommand: string;
+ linkGithubAction: string;
+ linkIssues: string;
+ linkDiscussions: string;
+ linkSecurity: string;
+ linkSponsor: string;
+ license: string;
+ copyright: string;
+ builtWith: string;
+}
+
+export interface ChangelogPageTranslations {
+ title: string;
+ backToHome: string;
+ fullChangelog: string;
+ changelogAccent: string;
+ intro: string;
+ githubReleases: string;
+ versions: string;
+ backToTop: string;
+}
+
+export interface DocsTranslations {
+ title: string;
+ backToHome: string;
+ pageTitle: string;
+ intro: string;
+ // Sidebar
+ sidebarGettingStarted: string;
+ sidebarRequirements: string;
+ sidebarInstallation: string;
+ sidebarCredentials: string;
+ sidebarPermissions: string;
+ sidebarCli: string;
+ sidebarMappingFile: string;
+ sidebarPullCommand: string;
+ sidebarPushCommand: string;
+ sidebarPushSingle: string;
+ sidebarGha: string;
+ sidebarGhaSetup: string;
+ sidebarGhaBasic: string;
+ sidebarGhaMultiEnv: string;
+ sidebarGhaAzure: string;
+ sidebarGhaInputs: string;
+ sidebarReference: string;
+ sidebarConfigPriority: string;
+ sidebarAzureSetup: string;
+ // Overview
+ overviewTitle: string;
+ overviewDesc: string;
+ overviewProblem: string;
+ overviewSolution: string;
+ // Requirements
+ reqTitle: string;
+ reqNode: string;
+ reqAws: string;
+ reqAzure: string;
+ reqAwsNote: string;
+ reqAzureNote: string;
+ reqDownload: string;
+ reqInstallGuide: string;
+ // Installation
+ installTitle: string;
+ // Credentials
+ credTitle: string;
+ credAwsTitle: string;
+ credAwsDesc: string;
+ credAwsProfile: string;
+ credAzureTitle: string;
+ credAzureDesc: string;
+ credAzureVault: string;
+ // Permissions
+ permTitle: string;
+ permAwsTitle: string;
+ permAwsDesc: string;
+ permOperation: string;
+ permPermission: string;
+ permPull: string;
+ permPush: string;
+ permPolicyExample: string;
+ permAzureTitle: string;
+ permAzureRbac: string;
+ permAzurePullNote: string;
+ // Mapping file
+ mapTitle: string;
+ mapIntro: string;
+ mapCalloutStructure: string;
+ mapCalloutKey: string;
+ mapCalloutValue: string;
+ mapBasicTitle: string;
+ mapBasicDesc: string;
+ mapBasicGenerates: string;
+ mapConfigTitle: string;
+ mapConfigDesc: string;
+ mapConfigOptionsTitle: string;
+ mapThKey: string;
+ mapThType: string;
+ mapThDefault: string;
+ mapThDescription: string;
+ mapProviderDesc: string;
+ mapVaultUrlDesc: string;
+ mapProfileDesc: string;
+ mapAwsProfileTitle: string;
+ mapAwsProfileDesc: string;
+ mapAwsProfileExplain: string;
+ mapAzureTitle: string;
+ mapAzureDesc: string;
+ mapAzureWarningTitle: string;
+ mapAzureWarningDesc: string;
+ mapDifferencesTitle: string;
+ mapThEmpty: string;
+ mapThAwsSsm: string;
+ mapThAzureKv: string;
+ mapSecretPathFormat: string;
+ mapAwsPathFormat: string;
+ mapAzurePathFormat: string;
+ mapRequiredConfig: string;
+ mapAwsRequiredConfig: string;
+ mapAzureRequiredConfig: string;
+ mapOptionalConfig: string;
+ mapAuthentication: string;
+ mapAwsAuth: string;
+ mapAzureAuth: string;
+ mapMultiEnvTitle: string;
+ mapMultiEnvDesc: string;
+ mapMultiEnvThenPull: string;
+ mapOverrideTitle: string;
+ mapOverrideDesc: string;
+ mapOverrideComment1: string;
+ mapOverrideComment2: string;
+ mapOverrideComment3: string;
+ mapPriorityNote: string;
+ // Pull command
+ pullTitle: string;
+ pullDesc: string;
+ pullOptions: string;
+ pullExamples: string;
+ pullOutput: string;
+ optionHeader: string;
+ pullOptMap: string;
+ pullOptEnv: string;
+ pullOptProvider: string;
+ pullOptVault: string;
+ pullOptProfile: string;
+ pullCommentDefault: string;
+ pullCommentProfile: string;
+ pullCommentAzureConfig: string;
+ pullCommentAzureFlags: string;
+ pullOutputTitle: string;
+ // Push command
+ pushTitle: string;
+ pushDesc: string;
+ pushOptions: string;
+ pushExamples: string;
+ pushOptPush: string;
+ pushOptEnv: string;
+ pushOptMap: string;
+ pushOptProvider: string;
+ pushOptVault: string;
+ pushOptProfile: string;
+ pushCommentAws: string;
+ pushCommentProfile: string;
+ pushCommentAzureConfig: string;
+ pushCommentAzureFlags: string;
+ // Push single
+ pushSingleTitle: string;
+ pushSingleDesc: string;
+ pushSingleOptions: string;
+ pushSingleOptPush: string;
+ pushSingleOptKey: string;
+ pushSingleOptValue: string;
+ pushSingleOptPath: string;
+ pushSingleOptProvider: string;
+ pushSingleOptVault: string;
+ pushSingleOptProfile: string;
+ // GHA
+ ghaSetupTitle: string;
+ ghaSetupDesc: string;
+ ghaPrerequisites: string;
+ ghaPrereqAws: string;
+ ghaPrereqAzure: string;
+ ghaPrereqMap: string;
+ ghaPullOnly: string;
+ ghaBasicTitle: string;
+ ghaMultiEnvTitle: string;
+ ghaAzureTitle: string;
+ ghaInputsTitle: string;
+ ghaInputsSubtitle: string;
+ ghaOutputsSubtitle: string;
+ ghaInputRequired: string;
+ ghaInputDefault: string;
+ ghaInputDesc: string;
+ ghaThInput: string;
+ ghaThRequired: string;
+ ghaThOutput: string;
+ ghaYes: string;
+ ghaNo: string;
+ ghaInputMap: string;
+ ghaInputEnv: string;
+ ghaInputProvider: string;
+ ghaInputVault: string;
+ ghaOutputEnvPath: string;
+ // Reference
+ configPriorityTitle: string;
+ configPriorityDesc: string;
+ configPriority1: string;
+ configPriority2: string;
+ configPriority3: string;
+ configPriorityExplain: string;
+ azureSetupTitle: string;
+ azureSetupCheck: string;
+ azureRbacTrue: string;
+ azureRbacFalse: string;
+ azureOptionA: string;
+ azureOptionB: string;
+ azureAccessNote: string;
+}
+
+export interface HomeMetaTranslations {
+ title: string;
+ description: string;
+}
+
+export interface Translations {
+ homeMeta: HomeMetaTranslations;
+ nav: NavLinks;
+ theme: ThemeTranslations;
+ hero: HeroTranslations;
+ trust: TrustTranslations;
+ problemSolution: ProblemSolutionTranslations;
+ howItWorks: HowItWorksTranslations;
+ features: FeaturesTranslations;
+ demo: DemoTranslations;
+ providers: ProvidersTranslations;
+ gha: GhaTranslations;
+ changelog: ChangelogTranslations;
+ roadmap: RoadmapTranslations;
+ getStarted: GetStartedTranslations;
+ footer: FooterTranslations;
+ changelogPage: ChangelogPageTranslations;
+ docs: DocsTranslations;
+}
diff --git a/src/apps/website/src/i18n/utils.ts b/src/apps/website/src/i18n/utils.ts
new file mode 100644
index 00000000..6b2aae0c
--- /dev/null
+++ b/src/apps/website/src/i18n/utils.ts
@@ -0,0 +1,30 @@
+import { ca } from './ca';
+import { en } from './en';
+import { es } from './es';
+import type { Translations } from './types';
+
+const translations: Record = { en, ca, es };
+
+export const languages = {
+ en: 'English',
+ ca: 'Català',
+ es: 'Español',
+};
+
+export const defaultLang = 'en';
+
+export type Lang = keyof typeof languages;
+
+function normalizeLang(lang: string): Lang {
+ return Object.hasOwn(languages, lang) ? (lang as Lang) : defaultLang;
+}
+
+export function useTranslations(lang: string) {
+ return translations[normalizeLang(lang)];
+}
+
+export function localizedPath(lang: string, path: string) {
+ const normalized = normalizeLang(lang);
+ if (normalized === defaultLang) return path;
+ return `/${normalized}${path}`;
+}
diff --git a/src/apps/website/src/layouts/BaseLayout.astro b/src/apps/website/src/layouts/BaseLayout.astro
new file mode 100644
index 00000000..bb039599
--- /dev/null
+++ b/src/apps/website/src/layouts/BaseLayout.astro
@@ -0,0 +1,70 @@
+---
+export interface Props {
+ title?: string;
+ description?: string;
+ lang?: string;
+}
+
+const {
+ title = 'Envilder — Centralize your secrets. One command.',
+ description = 'A CLI tool and GitHub Action that securely centralizes environment variables from AWS SSM Parameter Store or Azure Key Vault as a single source of truth.',
+ lang = 'en',
+} = Astro.props;
+
+const canonicalUrl = new URL(Astro.url.pathname, Astro.site).href;
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/apps/website/src/pages/ca/changelog.astro b/src/apps/website/src/pages/ca/changelog.astro
new file mode 100644
index 00000000..fd2d331f
--- /dev/null
+++ b/src/apps/website/src/pages/ca/changelog.astro
@@ -0,0 +1,342 @@
+---
+import { readFile } from 'node:fs/promises';
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import Footer from '../../components/Footer.astro';
+import Navbar from '../../components/Navbar.astro';
+import { localizedPath, useTranslations } from '../../i18n/utils';
+import BaseLayout from '../../layouts/BaseLayout.astro';
+import { changelogToHtml, extractVersions } from '../../utils/markdown';
+
+const lang = 'ca';
+const t = useTranslations(lang);
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const changelogPath = resolve(__dirname, '../../../../../../docs/CHANGELOG.md');
+let changelogContent = '';
+try {
+ changelogContent = await readFile(changelogPath, 'utf-8');
+} catch {
+ changelogContent =
+ '# Changelog\n\nChangelog file not found. See [GitHub Releases](https://github.com/macalbert/envilder/releases).';
+}
+
+const changelogHtml = changelogToHtml(changelogContent);
+const versions = extractVersions(changelogContent);
+---
+
+
+
+
+
+
+
+ ↑
+
+
+
+
+
+
+
diff --git a/src/apps/website/src/pages/ca/docs.astro b/src/apps/website/src/pages/ca/docs.astro
new file mode 100644
index 00000000..97d8ed6c
--- /dev/null
+++ b/src/apps/website/src/pages/ca/docs.astro
@@ -0,0 +1,17 @@
+---
+import DocsContent from '../../components/DocsContent.astro';
+import Navbar from '../../components/Navbar.astro';
+import { useTranslations } from '../../i18n/utils';
+import BaseLayout from '../../layouts/BaseLayout.astro';
+
+const lang = 'ca';
+const t = useTranslations(lang);
+---
+
+
+
+
+
diff --git a/src/apps/website/src/pages/ca/index.astro b/src/apps/website/src/pages/ca/index.astro
new file mode 100644
index 00000000..e5a3066c
--- /dev/null
+++ b/src/apps/website/src/pages/ca/index.astro
@@ -0,0 +1,42 @@
+---
+import Changelog from '../../components/Changelog.astro';
+import DemoVideo from '../../components/DemoVideo.astro';
+import FeaturesGrid from '../../components/FeaturesGrid.astro';
+import Footer from '../../components/Footer.astro';
+import GetStarted from '../../components/GetStarted.astro';
+import GitHubAction from '../../components/GitHubAction.astro';
+import Hero from '../../components/Hero.astro';
+import HowItWorks from '../../components/HowItWorks.astro';
+import Navbar from '../../components/Navbar.astro';
+import ProblemSolution from '../../components/ProblemSolution.astro';
+import Providers from '../../components/Providers.astro';
+import Roadmap from '../../components/Roadmap.astro';
+import TrustBadges from '../../components/TrustBadges.astro';
+import { useTranslations } from '../../i18n/utils';
+import BaseLayout from '../../layouts/BaseLayout.astro';
+
+const lang = 'ca';
+const t = useTranslations(lang);
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/apps/website/src/pages/changelog.astro b/src/apps/website/src/pages/changelog.astro
new file mode 100644
index 00000000..e676491a
--- /dev/null
+++ b/src/apps/website/src/pages/changelog.astro
@@ -0,0 +1,348 @@
+---
+import { readFile } from 'node:fs/promises';
+import { dirname, resolve } from 'node:path';
+
+import { fileURLToPath } from 'node:url';
+import Footer from '../components/Footer.astro';
+import Navbar from '../components/Navbar.astro';
+import { localizedPath, useTranslations } from '../i18n/utils';
+import BaseLayout from '../layouts/BaseLayout.astro';
+import { changelogToHtml, extractVersions } from '../utils/markdown';
+
+const lang = 'en';
+const t = useTranslations(lang);
+
+// Read the changelog from the project root
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const changelogPath = resolve(__dirname, '../../../../../docs/CHANGELOG.md');
+let changelogContent = '';
+try {
+ changelogContent = await readFile(changelogPath, 'utf-8');
+} catch {
+ changelogContent =
+ '# Changelog\n\nChangelog file not found. See [GitHub Releases](https://github.com/macalbert/envilder/releases).';
+}
+
+const changelogHtml = changelogToHtml(changelogContent);
+const versions = extractVersions(changelogContent);
+---
+
+
+
+
+
+
+
+ ↑
+
+
+
+
+
+
+
diff --git a/src/apps/website/src/pages/docs.astro b/src/apps/website/src/pages/docs.astro
new file mode 100644
index 00000000..d0f0dcdc
--- /dev/null
+++ b/src/apps/website/src/pages/docs.astro
@@ -0,0 +1,17 @@
+---
+import DocsContent from '../components/DocsContent.astro';
+import Navbar from '../components/Navbar.astro';
+import { useTranslations } from '../i18n/utils';
+import BaseLayout from '../layouts/BaseLayout.astro';
+
+const lang = 'en';
+const t = useTranslations(lang);
+---
+
+
+
+
+
diff --git a/src/apps/website/src/pages/es/changelog.astro b/src/apps/website/src/pages/es/changelog.astro
new file mode 100644
index 00000000..5f360afa
--- /dev/null
+++ b/src/apps/website/src/pages/es/changelog.astro
@@ -0,0 +1,342 @@
+---
+import { readFile } from 'node:fs/promises';
+import { dirname, resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import Footer from '../../components/Footer.astro';
+import Navbar from '../../components/Navbar.astro';
+import { localizedPath, useTranslations } from '../../i18n/utils';
+import BaseLayout from '../../layouts/BaseLayout.astro';
+import { changelogToHtml, extractVersions } from '../../utils/markdown';
+
+const lang = 'es';
+const t = useTranslations(lang);
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const changelogPath = resolve(__dirname, '../../../../../../docs/CHANGELOG.md');
+let changelogContent = '';
+try {
+ changelogContent = await readFile(changelogPath, 'utf-8');
+} catch {
+ changelogContent =
+ '# Changelog\n\nChangelog file not found. See [GitHub Releases](https://github.com/macalbert/envilder/releases).';
+}
+
+const changelogHtml = changelogToHtml(changelogContent);
+const versions = extractVersions(changelogContent);
+---
+
+
+
+
+
+
+
+ ↑
+
+
+
+
+
+
+
diff --git a/src/apps/website/src/pages/es/docs.astro b/src/apps/website/src/pages/es/docs.astro
new file mode 100644
index 00000000..a6d24e36
--- /dev/null
+++ b/src/apps/website/src/pages/es/docs.astro
@@ -0,0 +1,17 @@
+---
+import DocsContent from '../../components/DocsContent.astro';
+import Navbar from '../../components/Navbar.astro';
+import { useTranslations } from '../../i18n/utils';
+import BaseLayout from '../../layouts/BaseLayout.astro';
+
+const lang = 'es';
+const t = useTranslations(lang);
+---
+
+
+
+
+
diff --git a/src/apps/website/src/pages/es/index.astro b/src/apps/website/src/pages/es/index.astro
new file mode 100644
index 00000000..c38710e4
--- /dev/null
+++ b/src/apps/website/src/pages/es/index.astro
@@ -0,0 +1,42 @@
+---
+import Changelog from '../../components/Changelog.astro';
+import DemoVideo from '../../components/DemoVideo.astro';
+import FeaturesGrid from '../../components/FeaturesGrid.astro';
+import Footer from '../../components/Footer.astro';
+import GetStarted from '../../components/GetStarted.astro';
+import GitHubAction from '../../components/GitHubAction.astro';
+import Hero from '../../components/Hero.astro';
+import HowItWorks from '../../components/HowItWorks.astro';
+import Navbar from '../../components/Navbar.astro';
+import ProblemSolution from '../../components/ProblemSolution.astro';
+import Providers from '../../components/Providers.astro';
+import Roadmap from '../../components/Roadmap.astro';
+import TrustBadges from '../../components/TrustBadges.astro';
+import { useTranslations } from '../../i18n/utils';
+import BaseLayout from '../../layouts/BaseLayout.astro';
+
+const lang = 'es';
+const t = useTranslations(lang);
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/apps/website/src/pages/index.astro b/src/apps/website/src/pages/index.astro
new file mode 100644
index 00000000..a51313ef
--- /dev/null
+++ b/src/apps/website/src/pages/index.astro
@@ -0,0 +1,42 @@
+---
+import Changelog from '../components/Changelog.astro';
+import DemoVideo from '../components/DemoVideo.astro';
+import FeaturesGrid from '../components/FeaturesGrid.astro';
+import Footer from '../components/Footer.astro';
+import GetStarted from '../components/GetStarted.astro';
+import GitHubAction from '../components/GitHubAction.astro';
+import Hero from '../components/Hero.astro';
+import HowItWorks from '../components/HowItWorks.astro';
+import Navbar from '../components/Navbar.astro';
+import ProblemSolution from '../components/ProblemSolution.astro';
+import Providers from '../components/Providers.astro';
+import Roadmap from '../components/Roadmap.astro';
+import TrustBadges from '../components/TrustBadges.astro';
+import { useTranslations } from '../i18n/utils';
+import BaseLayout from '../layouts/BaseLayout.astro';
+
+const lang = 'en';
+const t = useTranslations(lang);
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/apps/website/src/styles/global.css b/src/apps/website/src/styles/global.css
new file mode 100644
index 00000000..31c27bbc
--- /dev/null
+++ b/src/apps/website/src/styles/global.css
@@ -0,0 +1,513 @@
+/* ── Envilder Design System ─────────────────────────────── */
+/* Retro Game Boy · Pixel accents · Professional */
+
+/* ── Reset ────────────────────────────────────────────── */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+html {
+ scroll-behavior: smooth;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ overflow-x: hidden;
+}
+
+/* ── CSS Custom Properties ────────────────────────────── */
+:root {
+ /*
+ * Game Boy — vibrant LCD palette
+ * Based on the classic Game Boy green phosphor screen.
+ * palette: #4caf50 · #77be48 · #a2cd41 · #cddc39
+ */
+ --color-bg: #182016; /* very dark green-black */
+ --color-bg-alt: #1e281c; /* slightly lighter band */
+ --color-surface: #243022; /* card/panel background */
+ --color-surface-alt: #2a3828; /* surface hover */
+ --color-border: #3a5036; /* subtle green border */
+ --color-border-light: #4d6848; /* lighter border */
+
+ --color-primary: #77be48; /* Game Boy mid-green — links, buttons */
+ --color-primary-dim: #4caf50; /* deeper GB green */
+ --color-secondary: #a2cd41; /* bright lime green */
+ --color-secondary-dim: #6da03a; /* muted secondary */
+ --color-accent: #cddc39; /* yellow-green LCD highlight */
+
+ --color-text: #c8d8a8; /* soft sage — easy on the eyes */
+ --color-text-muted: #8ca870; /* muted green-grey */
+ --color-text-dim: #5a7248; /* hint text */
+
+ --color-success: #77be48; /* GB green */
+ --color-warning: #cddc39; /* lime-yellow */
+ --color-error: #a06050; /* muted red (small concession) */
+
+ --color-nav-bg: rgba(24, 32, 22, 0.95);
+ --color-nav-mobile: rgba(24, 32, 22, 0.98);
+ --color-btn-text: #182016; /* dark bg for button labels */
+ --color-code-bg: #1a2418; /* dark green-tinted code blocks */
+ --color-code-header: #202c1e; /* slightly lighter for headers */
+ --color-code-text: var(--color-text);
+ --color-code-text-muted: var(--color-text-muted);
+ --color-code-text-dim: var(--color-text-dim);
+ --color-code-border: var(--color-border);
+ --color-sponsor: #db61a2; /* GitHub sponsor pink */
+
+ /* Typography */
+ --font-pixel: "Press Start 2P", monospace;
+ --font-body:
+ "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ --font-mono: "JetBrains Mono", "Fira Code", "Cascadia Code", monospace;
+
+ /* Spacing */
+ --space-xs: 0.25rem;
+ --space-sm: 0.5rem;
+ --space-md: 1rem;
+ --space-lg: 1.5rem;
+ --space-xl: 2rem;
+ --space-2xl: 3rem;
+ --space-3xl: 4rem;
+ --space-4xl: 6rem;
+
+ /* Layout */
+ --max-width: 1200px;
+ --nav-height: 64px;
+
+ /* Effects */
+ --pixel-shadow: 4px 4px 0 var(--color-border);
+ --pixel-shadow-primary: 4px 4px 0 var(--color-primary-dim);
+ --glow-primary: 0 0 20px rgba(119, 190, 72, 0.18);
+ --glow-secondary: 0 0 12px rgba(162, 205, 65, 0.12);
+
+ /* Transitions */
+ --transition-fast: 150ms ease;
+ --transition-base: 250ms ease;
+}
+
+/* ── Theme: light (US Super Nintendo SNES) ────────────── */
+/*
+ * Inspired by the US SNES controller palette:
+ * #b5b6e4 · #4f43ae · #908a99 · #cec9cc · #211a21
+ * Violet (#4f43ae) is reserved for small accents:
+ * links, buttons, badges, and tiny section titles.
+ * The base is neutral grey-lavender for readability.
+ */
+[data-theme="light"] {
+ --color-bg: #f0eff5; /* white with subtle lavender tint */
+ --color-bg-alt: #e8e6f0; /* soft lavender band */
+ --color-surface: #ffffff; /* pure white cards */
+ --color-surface-alt: #ecebf3; /* surface with violet hint */
+ --color-border: #cec9cc; /* SNES light grey */
+ --color-border-light: #b8b4b6; /* slightly deeper grey */
+
+ --color-primary: #4f43ae; /* SNES violet — links, buttons, small accents */
+ --color-primary-dim: #3d3490; /* dimmed violet */
+ --color-secondary: #908a99; /* SNES grey-mauve */
+ --color-secondary-dim: #7a7484; /* deeper mauve */
+ --color-accent: #b5b6e4; /* SNES lavender highlight */
+
+ --color-text: #211a21; /* SNES near-black */
+ --color-text-muted: #5c5462; /* dark mauve-grey */
+ --color-text-dim: #908a99; /* SNES grey-mauve */
+
+ --color-success: #4f7a5c; /* muted green */
+ --color-warning: #a09040; /* warm yellow */
+ --color-error: #a04848; /* muted red */
+
+ --color-nav-bg: rgba(240, 239, 245, 0.92);
+ --color-nav-mobile: rgba(240, 239, 245, 0.98);
+ --color-btn-text: #ffffff; /* white text on violet buttons */
+ --color-code-bg: #e6e4ee; /* lavender-tinted code block */
+ --color-code-header: #dddae8; /* slightly deeper header */
+ --color-sponsor: #c74b8e; /* GitHub sponsor pink — light */
+
+ --pixel-shadow: 4px 4px 0 var(--color-border);
+ --pixel-shadow-primary: 4px 4px 0 var(--color-primary-dim);
+ --glow-primary: 0 0 20px rgba(79, 67, 174, 0.12);
+ --glow-secondary: 0 0 15px rgba(181, 182, 228, 0.15);
+}
+
+/* ── Base ─────────────────────────────────────────────── */
+body {
+ font-family: var(--font-body);
+ background: var(--color-bg);
+ color: var(--color-text);
+ line-height: 1.7;
+ font-size: 1rem;
+ overflow-x: hidden;
+ cursor: default;
+}
+
+/* ── Theme transition ─────────────────────────────────── */
+html:not([data-no-transition]) body,
+html:not([data-no-transition]) .navbar {
+ transition:
+ background-color 300ms ease,
+ color 300ms ease,
+ border-color 300ms ease;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+li,
+.pixel-card,
+.section-title,
+.step,
+.feature-card {
+ cursor: default;
+}
+
+code,
+pre,
+.codeblock {
+ cursor: text;
+ user-select: text;
+}
+
+img,
+video {
+ display: block;
+ max-width: 100%;
+ height: auto;
+}
+
+a {
+ color: var(--color-primary);
+ text-decoration: none;
+ transition: color var(--transition-fast);
+}
+
+a:hover {
+ color: var(--color-accent);
+}
+
+/* ── Typography ───────────────────────────────────────── */
+h1,
+h2,
+h3,
+h4 {
+ font-family: var(--font-pixel);
+ line-height: 1.6;
+ letter-spacing: -0.01em;
+}
+
+h1 {
+ font-size: clamp(1.2rem, 3vw, 1.8rem);
+ color: var(--color-text);
+}
+
+h2 {
+ font-size: clamp(0.9rem, 2vw, 1.2rem);
+ color: var(--color-text);
+ margin-bottom: var(--space-lg);
+}
+
+h3 {
+ font-size: clamp(0.7rem, 1.5vw, 0.85rem);
+ color: var(--color-text-muted);
+}
+
+p {
+ color: var(--color-text-muted);
+ max-width: 65ch;
+}
+
+code {
+ font-family: var(--font-mono);
+ font-size: 0.9em;
+ background: var(--color-surface);
+ padding: 0.15em 0.4em;
+ border-radius: 3px;
+ border: 1px solid var(--color-border);
+}
+
+/* Reset inline-code styling inside code blocks / terminals */
+pre code,
+.codeblock code,
+.terminal code {
+ background: none;
+ padding: 0;
+ border-radius: 0;
+ border: none;
+}
+
+/* ── Layout Utilities ─────────────────────────────────── */
+.container {
+ width: 100%;
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 0 var(--space-lg);
+}
+
+.section {
+ padding: var(--space-4xl) 0;
+ overflow: hidden;
+}
+
+.section-alt {
+ background: var(--color-bg-alt);
+}
+
+.section-title {
+ text-align: center;
+ margin-bottom: var(--space-3xl);
+}
+
+.section-title span {
+ color: var(--color-primary);
+}
+
+.section-subtitle {
+ font-family: var(--font-body);
+ font-size: 1.1rem;
+ color: var(--color-text-muted);
+ max-width: 600px;
+ margin: var(--space-md) auto 0;
+ text-align: center;
+ font-weight: 400;
+}
+
+/* ── Buttons ──────────────────────────────────────────── */
+.btn {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-sm);
+ font-family: var(--font-pixel);
+ font-size: 0.65rem;
+ padding: var(--space-md) var(--space-xl);
+ border: 2px solid transparent;
+ cursor: pointer;
+ transition: all var(--transition-fast);
+ text-decoration: none;
+ position: relative;
+}
+
+.btn-primary {
+ background: var(--color-primary);
+ color: var(--color-btn-text);
+ border-color: var(--color-primary);
+ box-shadow: var(--pixel-shadow-primary);
+}
+
+.btn-primary:hover {
+ background: var(--color-surface);
+ color: var(--color-primary);
+ transform: translate(2px, 2px);
+ box-shadow: none;
+}
+
+.btn-outline {
+ background: var(--color-surface);
+ color: var(--color-text);
+ border-color: var(--color-border-light);
+ box-shadow: var(--pixel-shadow);
+}
+
+.btn-outline:hover {
+ background: var(--color-surface-alt);
+ border-color: var(--color-primary);
+ color: var(--color-primary);
+ transform: translate(2px, 2px);
+ box-shadow: none;
+}
+
+/* ── Cards (pixel-bordered) ───────────────────────────── */
+.pixel-card {
+ background: var(--color-surface);
+ border: 2px solid var(--color-border);
+ padding: var(--space-xl);
+ position: relative;
+ transition: all var(--transition-base);
+}
+
+.pixel-card:hover {
+ border-color: var(--color-primary);
+ box-shadow: var(--pixel-shadow-primary);
+ transform: translate(-2px, -2px);
+}
+
+/* corner notch decoration */
+.pixel-card::before {
+ content: "";
+ position: absolute;
+ top: -2px;
+ right: -2px;
+ width: 12px;
+ height: 12px;
+ background: var(--color-bg);
+ border-left: 2px solid var(--color-border);
+ border-bottom: 2px solid var(--color-border);
+}
+
+.pixel-card:hover::before {
+ border-color: var(--color-primary);
+}
+
+/* ── Pixel icon (8-bit style emoji replacement) ───────── */
+.pixel-icon {
+ font-size: 1.8rem;
+ display: inline-block;
+ image-rendering: pixelated;
+ margin-bottom: var(--space-md);
+ filter: grayscale(100%) sepia(60%) saturate(400%) hue-rotate(50deg)
+ brightness(0.9);
+ text-shadow:
+ 1px 0 0 rgba(139, 172, 15, 0.25),
+ 0 1px 0 rgba(139, 172, 15, 0.25);
+}
+
+[data-theme="light"] .pixel-icon {
+ filter: grayscale(100%) sepia(40%) saturate(100%) brightness(0.7)
+ contrast(1.1);
+ text-shadow:
+ 1px 0 0 rgba(107, 104, 96, 0.2),
+ 0 1px 0 rgba(107, 104, 96, 0.2);
+}
+
+/* ── Badge / Tag ──────────────────────────────────────── */
+.badge {
+ display: inline-flex;
+ align-items: center;
+ gap: var(--space-xs);
+ font-family: var(--font-mono);
+ font-size: 0.75rem;
+ padding: var(--space-xs) var(--space-sm);
+ border: 1px solid var(--color-border);
+ background: var(--color-surface);
+ color: var(--color-text-muted);
+}
+
+.badge-primary {
+ border-color: var(--color-primary);
+ color: var(--color-primary);
+}
+
+.badge-success {
+ border-color: var(--color-success);
+ color: var(--color-success);
+}
+
+/* ── Scanline overlay ─────────────────────────────────── */
+.scanlines {
+ position: relative;
+}
+
+.scanlines::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: repeating-linear-gradient(
+ 0deg,
+ transparent,
+ transparent 2px,
+ rgba(0, 0, 0, 0.08) 2px,
+ rgba(0, 0, 0, 0.08) 4px
+ );
+ pointer-events: none;
+ z-index: 1;
+}
+
+/* ── Grid helpers ─────────────────────────────────────── */
+.grid-2 {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: var(--space-xl);
+}
+
+.grid-3 {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: var(--space-xl);
+}
+
+.grid-4 {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: var(--space-lg);
+}
+
+@media (min-width: 640px) {
+ .grid-2 {
+ grid-template-columns: repeat(2, 1fr);
+ }
+ .grid-3 {
+ grid-template-columns: repeat(2, 1fr);
+ }
+ .grid-4 {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (min-width: 1024px) {
+ .grid-3 {
+ grid-template-columns: repeat(3, 1fr);
+ }
+ .grid-4 {
+ grid-template-columns: repeat(4, 1fr);
+ }
+}
+
+/* ── Divider (pixel style) ────────────────────────────── */
+.pixel-divider {
+ border: none;
+ height: 2px;
+ background: repeating-linear-gradient(
+ 90deg,
+ var(--color-border) 0px,
+ var(--color-border) 8px,
+ transparent 8px,
+ transparent 12px
+ );
+ margin: var(--space-3xl) 0;
+}
+
+/* ── Responsive ───────────────────────────────────────── */
+@media (max-width: 640px) {
+ .container {
+ padding: 0 var(--space-md);
+ }
+
+ .section {
+ padding: var(--space-3xl) 0;
+ }
+
+ .pixel-card::before {
+ display: none;
+ }
+
+ .pixel-card:hover {
+ transform: none;
+ box-shadow: var(--pixel-shadow-primary);
+ }
+}
+
+/* ── Scrollbar ────────────────────────────────────────── */
+::-webkit-scrollbar {
+ width: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--color-bg);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--color-border);
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--color-border-light);
+}
+
+/* ── Selection ────────────────────────────────────────── */
+::selection {
+ background: var(--color-primary);
+ color: var(--color-btn-text);
+}
diff --git a/src/apps/website/src/utils/markdown.ts b/src/apps/website/src/utils/markdown.ts
new file mode 100644
index 00000000..1414add7
--- /dev/null
+++ b/src/apps/website/src/utils/markdown.ts
@@ -0,0 +1,122 @@
+/**
+ * Simple markdown-to-HTML conversion for the changelog.
+ * Only handles the subset of markdown used in docs/CHANGELOG.md:
+ * headings, bold, inline code, links, unordered lists, and horizontal rules.
+ *
+ * Strips HTML comments left over from markdownlint directives.
+ */
+
+/**
+ * Strip HTML comments completely, including malformed or nested fragments.
+ * Uses a loop to guarantee no `` sequences survive
+ * (avoids incomplete multi-character sanitization).
+ */
+function stripHtmlComments(text: string): string {
+ let result = text;
+ // First pass: remove well-formed comments
+ while (
+ result.includes('') || result.includes('--!>'))
+ ) {
+ result = result.replace(/') ||
+ result.includes('--!>')
+ ) {
+ result = result.replace(/