Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .archgate/adrs/GEN-002-docs-i18n.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Archgate targets a global developer audience, but the documentation site ([GEN-0
2. **Community growth is limited** -- Open-source adoption in non-English markets depends on accessible documentation
3. **Translation efforts lack governance** -- Without structure, translations drift from the source language, pages get added without corresponding translations, and stale translations mislead users

Brazilian Portuguese is the first translation target. The Starlight documentation framework already provides built-in i18n support with locale-based routing, automatic language switching, and fallback behavior.
Brazilian Portuguese and Norwegian Bokmål are translation targets. The Starlight documentation framework already provides built-in i18n support with locale-based routing, automatic language switching, and fallback behavior.

**Alternatives considered:**

Expand Down Expand Up @@ -56,6 +56,7 @@ The sidebar in `docs/astro.config.mjs` does NOT need per-locale duplication. Sta
| ---------- | ------------------ | ---------- | ---------- |
| `root` | English | `en` | _(none)_ |
| `pt-br` | Portugues (Brasil) | `pt-BR` | `/pt-br/` |
| `nb` | Norsk (Bokmål) | `nb` | `/nb/` |

## Do's and Don'ts

Expand All @@ -71,6 +72,7 @@ The sidebar in `docs/astro.config.mjs` does NOT need per-locale duplication. Sta
- **DO** preserve Starlight component import statements identically in translated files
- **DO** update translations in the same PR that modifies the English source content
- **DO** use correct diacritical marks (accents) in Portuguese translations -- ã, ç, é, í, ó, ú, â, ê, ô, à are mandatory. Never write unaccented Portuguese (e.g., `não` not `nao`, `código` not `codigo`, `você` not `voce`, `segurança` not `seguranca`, `funções` not `funcoes`)
- **DO** use Norwegian Bokmål (not Nynorsk) for Norwegian translations, with the informal "du" form and correct Norwegian characters (æ, ø, å)
- **DO** update the `LOCALES` constant in the companion rules file when adding a new language

### Don't
Expand All @@ -88,17 +90,17 @@ The sidebar in `docs/astro.config.mjs` does NOT need per-locale duplication. Sta

### Positive

- **Broader international audience** -- Brazilian Portuguese speakers can read documentation in their language, lowering the barrier to adoption
- **Broader international audience** -- Brazilian Portuguese and Norwegian speakers can read documentation in their language, lowering the barrier to adoption
- **Zero breaking changes** -- The root locale pattern preserves all existing English URLs; no redirects or link updates needed
- **Automatic language switching** -- Starlight renders a language switcher in the navigation with no custom code
- **Automated parity enforcement** -- The companion rule catches missing translations and orphan files before they reach production
- **Extensible to more languages** -- Adding a new locale requires only a config entry, a constant update in the rules file, and the translated content files

### Negative

- **Doubled content maintenance** -- Every content PR must update both English and Portuguese files, increasing review scope
- **Multiplied content maintenance** -- Every content PR must update English, Portuguese, and Norwegian files, increasing review scope
- **Translation quality depends on reviewers** -- The automated rule only checks file existence, not translation accuracy or completeness
- **Contributor friction** -- Contributors who only speak English must still account for the Portuguese translation (even if they only add a placeholder file)
- **Contributor friction** -- Contributors who only speak English must still account for all locale translations (even if they only add a placeholder file)

### Risks

Expand Down
2 changes: 1 addition & 1 deletion .archgate/adrs/GEN-002-docs-i18n.rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* When adding a new language, add its directory name here AND in
* docs/astro.config.mjs under the `locales` key.
*/
const LOCALES = ["pt-br"];
const LOCALES = ["pt-br", "nb"];

const CONTENT_ROOT = "docs/src/content/docs";

Expand Down
20 changes: 19 additions & 1 deletion docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default defineConfig({
locales: {
root: { label: "English", lang: "en" },
"pt-br": { label: "Português (Brasil)", lang: "pt-BR" },
nb: { label: "Norsk (Bokmål)", lang: "nb" },
},
description:
"Enforce Architecture Decision Records as executable rules — for both humans and AI agents.",
Expand Down Expand Up @@ -112,6 +113,10 @@ export default defineConfig({
tag: "meta",
attrs: { property: "og:locale:alternate", content: "pt_BR" },
},
{
tag: "meta",
attrs: { property: "og:locale:alternate", content: "nb_NO" },
},
{
tag: "meta",
attrs: {
Expand Down Expand Up @@ -163,7 +168,7 @@ export default defineConfig({
url: "https://cli.archgate.dev",
description:
"Documentation for Archgate — enforce Architecture Decision Records as executable TypeScript rules for automated code governance.",
inLanguage: ["en", "pt-BR"],
inLanguage: ["en", "pt-BR", "nb"],
}),
},
// ── JSON-LD: SoftwareApplication ──────────────────────────
Expand Down Expand Up @@ -195,13 +200,18 @@ export default defineConfig({
sidebar: [
{
label: "Getting Started",
translations: { "pt-BR": "Primeiros Passos", nb: "Kom i gang" },
items: [
{ label: "Installation", slug: "getting-started/installation" },
{ label: "Quick Start", slug: "getting-started/quick-start" },
],
},
{
label: "Core Concepts",
translations: {
"pt-BR": "Conceitos Fundamentais",
nb: "Grunnleggende konsepter",
},
items: [
{ label: "Architecture Decision Records", slug: "concepts/adrs" },
{ label: "Rules", slug: "concepts/rules" },
Expand All @@ -210,6 +220,7 @@ export default defineConfig({
},
{
label: "Guides",
translations: { "pt-BR": "Guias", nb: "Guider" },
items: [
{ label: "Writing ADRs", slug: "guides/writing-adrs" },
{ label: "Writing Rules", slug: "guides/writing-rules" },
Expand All @@ -229,9 +240,14 @@ export default defineConfig({
},
{
label: "Reference",
translations: { "pt-BR": "Referência", nb: "Referanse" },
items: [
{
label: "CLI Commands",
translations: {
"pt-BR": "Comandos da CLI",
nb: "CLI-kommandoer",
},
items: [
{ label: "Overview", slug: "reference/cli" },
{ label: "archgate login", slug: "reference/cli/login" },
Expand Down Expand Up @@ -261,11 +277,13 @@ export default defineConfig({
},
{
label: "Examples",
translations: { "pt-BR": "Exemplos", nb: "Eksempler" },
collapsed: true,
items: [{ autogenerate: { directory: "examples", collapsed: true } }],
},
{
label: "Studies",
translations: { "pt-BR": "Estudos", nb: "Studier" },
collapsed: true,
items: [{ autogenerate: { directory: "studies", collapsed: true } }],
},
Expand Down
8 changes: 5 additions & 3 deletions docs/scripts/generate-llms-full.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* bun run docs/scripts/generate-llms-full.ts
*/
import { readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
import { join, relative } from "node:path";
import { join, relative, sep } from "node:path";

const docsDir = join(import.meta.dirname, "..", "src", "content", "docs");
const outputPath = join(import.meta.dirname, "..", "public", "llms-full.txt");
Expand Down Expand Up @@ -110,8 +110,10 @@ for (const section of sections) {
continue; // section directory may not exist
}

// Skip pt-br files — English only
const enFiles = files.filter((f) => !f.includes("pt-br"));
// Skip translated locale files — English only
const enFiles = files.filter(
(f) => !f.includes(`${sep}pt-br${sep}`) && !f.includes(`${sep}nb${sep}`)
);
if (enFiles.length === 0) continue;

for (const file of enFiles) {
Expand Down
25 changes: 20 additions & 5 deletions docs/src/components/HeadSEO.astro
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,27 @@ const description = hasRoute ? route.entry.data.description : undefined;
const template = hasRoute ? route.entry.data.template : undefined;

// ── hreflang alternate links ─────────────────────────────────────
// Map between English and pt-BR pages for search engine language targeting.
const isPtBr = pathname.startsWith("/pt-br/");
const enPath = isPtBr ? pathname.replace(/^\/pt-br\//u, "/") : pathname;
const ptBrPath = isPtBr ? pathname : `/pt-br${pathname}`;
// Map between all locale variants for search engine language targeting.
const LOCALE_PREFIXES = ["pt-br", "nb"] as const;
const LOCALE_HREFLANG: Record<string, string> = {
"pt-br": "pt-BR",
nb: "nb",
};

// Strip any locale prefix to get the base English path.
let enPath = pathname;
for (const prefix of LOCALE_PREFIXES) {
if (pathname.startsWith(`/${prefix}/`)) {
enPath = pathname.replace(new RegExp(`^/${prefix}/`, "u"), "/");
break;
}
}

const enUrl = `${siteUrl}${enPath}`;
const ptBrUrl = `${siteUrl}${ptBrPath}`;
const localeAlternates = LOCALE_PREFIXES.map((prefix) => ({
hreflang: LOCALE_HREFLANG[prefix],
href: `${siteUrl}/${prefix}${enPath}`,
}));

/**
* Convert a URL path segment into a human-readable breadcrumb label.
Expand Down
183 changes: 183 additions & 0 deletions docs/src/content/docs/nb/concepts/adrs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
---
title: Architecture Decision Records
description: Lær hvordan Architecture Decision Records (ADR-er) fungerer i Archgate som både lesbar dokumentasjon og maskinelt kjørbare samsvarregler.
---

En Architecture Decision Record (ADR) er et kort dokument som fanger opp en enkelt arkitekturbeslutning sammen med dens kontekst og konsekvenser. ADR-er svarer på spørsmålet: _hvorfor_ ble denne beslutningen tatt, og _hva_ er avveiningene?

Archgate bygger videre på ADR-konseptet ved å gi hver beslutning to uttrykk: et **dokument** som mennesker og AI-agenter leser, og en valgfri **regelfil** som maskiner kjører.

## To uttrykk for en ADR

### ADR som dokument

Dokumentet er en Markdown-fil med YAML frontmatter lagret i `.archgate/adrs/`. Det beskriver beslutningen i vanlig språk: hvilket problem det løser, hvilke alternativer som ble vurdert, hva teamet besluttet, og hvilke konsekvenser som følger.

Både mennesker og AI-agenter bruker dette dokumentet. Når en AI-kodeagent skal skrive kode, leser den de relevante ADR-ene for å forstå begrensningene før den genererer noe.

:::tip[Automatiser dette med editorplugins]
Med [Claude Code](/guides/claude-code-plugin/)- eller [Cursor](/guides/cursor-integration/)-pluginen leser AI-agenten din de gjeldende ADR-ene automatisk før hver kodeoppgave -- ingen manuell kopiering og liming inn i ledetekster. [Registrer deg for beta-tilgang](https://plugins.archgate.dev).
:::

### ADR som regler

Regelfilen er en tilhørende `.rules.ts`-fil som eksporterer et vanlig objekt typet med `satisfies RuleSet`. Når du kjører `archgate check`, laster CLI-en inn hver ADR som har `rules: true` i frontmatteren, kjører den tilhørende regelfilen mot kodebasen din og rapporterer eventuelle brudd med filstier og linjenumre.

Ikke alle ADR-er trenger regler. Noen beslutninger håndheves best kun gjennom kodegjennomgang. Sett `rules: false` når ingen automatisert sjekk er praktisk.

## Navnekonvensjon for filer

ADR-filer følger en streng navnekonvensjon som koder domeneprefiks, sekvensnummer og en lesbar slug:

```
{PREFIX}-{NNN}-{slug}.md # The document
{PREFIX}-{NNN}-{slug}.rules.ts # The companion rules file (optional)
```

For eksempel vil en ADR i arkitekturdomenet om kommandostruktur gi:

```
ARCH-001-command-structure.md
ARCH-001-command-structure.rules.ts
```

Prefikset kommer fra ADR-ens domene (se [Domener](/concepts/domains/)). Sekvensnummeret er nullpolstret til tre sifre og auto-inkrementert av `archgate adr create`.

## YAML Frontmatter

Hvert ADR-dokument starter med en YAML frontmatter-blokk mellom `---`-skilletegn. Frontmatteren er de maskinlesbare metadataene som Archgate bruker til å laste, filtrere og avgrense regler.

| Felt | Type | Påkrevd | Beskrivelse |
| ------------------ | ------------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Ja | Unik identifikator som `ARCH-001` eller `BE-003` |
| `title` | string | Ja | Lesbar tittel for beslutningen |
| `domain` | string | Ja | Registrert domenenavn. Innebygde: `backend`, `frontend`, `data`, `architecture`, `general`. [Egendefinerte domener](/concepts/domains/#custom-domains) kan legges til via `archgate adr domain add`. |
| `rules` | boolean | Ja | Om denne ADR-en har en tilhørende `.rules.ts`-fil |
| `files` | string array | Nei | Glob-mønstre som avgrenser hvilke filer reglene sjekker |
| `respectGitignore` | boolean | Nei | Om filer i `.gitignore` skal filtreres bort. Standard er `true`. |

Feltet `files` er valgfritt. Når det er tilstede, begrenser det regelkjøring til kun filene som matcher de angitte globbene. Når det er fraværende, kjøres regler mot alle prosjektfiler. For eksempel begrenser `files: ["src/commands/**/*.ts"]` sjekker til kun kommandofiler.

Feltet `respectGitignore` er også valgfritt. Som standard ekskluderes filer oppført i `.gitignore` fra alle filsøkeoperasjoner (`ctx.scopedFiles`, `ctx.glob()`, `ctx.grepFiles()`). Sett `respectGitignore: false` for å inkludere git-ignorerte filer -- nyttig for regler som trenger å inspisere byggeresultater eller genererte filer.

## ADR-seksjonene

Etter frontmatteren følger ADR-kroppen en standardisert seksjonsstruktur:

### Context

Beskriver problemet eller situasjonen som utløste beslutningen. Inkluder alternativer som ble vurdert og hvorfor de ble forkastet.

### Decision

Fastslår selve beslutningen og dens viktigste begrensninger. Dette er seksjonen AI-agenter legger mest merke til når de bestemmer hvordan de skal skrive kode.

### Do's and Don'ts

Konkret, handlingsrettet veiledning delt i to underseksjoner. Fungerer som en hurtigreferanse-sjekkliste for utviklere og AI-agenter.

### Consequences

Delt i tre underseksjoner:

- **Positive** -- fordeler beslutningen gir
- **Negative** -- avveininger som aksepteres
- **Risks** -- ting som kan gå galt og hvordan de kan begrenses

### Compliance and Enforcement

Beskriver hvordan beslutningen håndheves, både gjennom automatiserte regler (med regel-ID-er og alvorlighetsgrader) og manuelle gjennomgangssjekklister.

### References

Lenker til relaterte ADR-er, ekstern dokumentasjon eller designdokumenter.

## Komplett eksempel

Nedenfor er en fullstendig ADR med frontmatter og alle seksjoner fylt ut.

```markdown
---
id: BE-001
title: API Response Envelope
domain: backend
rules: true
files: ["src/api/**/*.ts"]
---

## Context

The API returns data in inconsistent shapes across endpoints. Some endpoints
wrap responses in `{ data, error }`, others return raw arrays, and error
responses vary between plain strings and structured objects.

**Alternatives considered:**

- **No envelope** -- Return raw data and rely on HTTP status codes alone.
Simple, but clients cannot distinguish between "the endpoint returned an
empty array" and "the endpoint errored."
- **GraphQL-style errors array** -- Use `{ data, errors: [] }`. Flexible
but adds complexity for simple REST endpoints.

The chosen envelope balances consistency with simplicity.

## Decision

All API endpoints MUST return responses in a standard envelope:

- Success: `{ data: T }`
- Error: `{ error: { code: string, message: string } }`

HTTP status codes remain the primary success/failure signal. The envelope
provides a predictable structure for clients to parse.

## Do's and Don'ts

### Do

- Wrap all API responses in the `{ data }` or `{ error }` envelope
- Use specific error codes (e.g., `VALIDATION_FAILED`, `NOT_FOUND`)
- Include the HTTP status code that matches the error semantics

### Don't

- Don't return raw arrays or primitives from API endpoints
- Don't nest envelopes (no `{ data: { data: ... } }`)
- Don't put stack traces in the error message field

## Consequences

### Positive

- Clients can parse every response with the same logic
- Error responses always have a machine-readable code for programmatic handling

### Negative

- Adds a small amount of boilerplate to every endpoint handler
- Slightly larger payloads due to the wrapper object

### Risks

- Developers may forget the envelope on new endpoints. Mitigated by
the automated rule that scans for non-conforming return statements.

## Compliance and Enforcement

### Automated Enforcement

- **Archgate rule** BE-001/response-envelope: Scans API handler files for
return statements and verifies they use the envelope helper. Severity: error.

### Manual Enforcement

Code reviewers MUST verify:

1. New API endpoints use the response envelope
2. Error responses include a specific error code, not a generic message

## References

- [Microsoft REST API Guidelines](https://github.com/microsoft/api-guidelines)
- [ARCH-002 -- Error Handling](./ARCH-002-error-handling.md)
```
Loading
Loading