feat: German as the main language, English under /en, with a language switch

The site now exists in German (at the root, with /impressum and /datenschutz)
and English (/en). A request without a language follows the visitor's choice
from the language switch, then the browser language, then German; the old
English legal addresses move to /en for good.

All visible texts are data in src/content/de.ts and en.ts, so components hold
none of their own. The header carries a DE/EN switch whose bit curtain covers
the page while it is swapped and reopens at the same reading position. Title,
description, canonical, hreflang, Open Graph, the share image, the sitemap and
the structured data are per language. The privacy notice now describes the
cookie that remembers the chosen language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 13:07:22 +02:00
co-authored by Claude Opus 5.5
parent e89a248ca5
commit 75993100fa
72 changed files with 4418 additions and 1405 deletions
+35 -113
View File
@@ -1,11 +1,12 @@
import type { FlowNodeCopy, NodeKind, WorkflowCopy } from "./types";
/**
* Example workflows for the automation section. They are illustrations of typical
* cases, not descriptions of specific client projects.
* cases, not descriptions of specific client projects. This file holds what the
* drawing needs (nodes, positions, runs); the texts are in en.ts and de.ts.
*/
export type NodeKind = "trigger" | "rule" | "ai" | "action" | "human";
export type FlowNode = {
type NodeLayout = {
readonly id: string;
readonly kind: NodeKind;
/** Position of the node centre on the wide 920 x 400 canvas. */
@@ -14,38 +15,28 @@ export type FlowNode = {
/** Position of the node centre on the tall 400 x 540 canvas for phones. */
readonly nx: number;
readonly ny: number;
readonly system: string;
readonly label: string;
/** What appears in the log when a run reaches this node. */
readonly log: string;
};
export type FlowNode = NodeLayout & FlowNodeCopy;
/** One step of a run: the connections travelled at the same time. */
export type FlowStep = readonly (readonly [from: string, to: string])[];
export type Workflow = {
type WorkflowLayout = {
readonly id: string;
readonly name: string;
readonly summary: string;
readonly byHand: string;
readonly automated: string;
readonly nodes: readonly FlowNode[];
readonly nodes: readonly NodeLayout[];
/** Every connection that exists, for drawing. */
readonly edges: readonly (readonly [from: string, to: string])[];
/** The runs that play in turn. Each run is a list of steps. */
readonly runs: readonly (readonly FlowStep[])[];
};
export const WORKFLOWS: readonly Workflow[] = [
export type Workflow = Omit<WorkflowLayout, "nodes"> &
Omit<WorkflowCopy, "nodes"> & { readonly nodes: readonly FlowNode[] };
const LAYOUTS: readonly WorkflowLayout[] = [
{
id: "order",
name: "Order to invoice",
summary:
"A new order arrives in the shop. The invoice, the customer record and the confirmation follow without anyone typing.",
byHand:
"Someone opens the order, writes the invoice, updates the customer record and sends an email.",
automated:
"The order triggers everything. People only see the finished result.",
nodes: [
{
id: "shop",
@@ -54,9 +45,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 200,
nx: 200,
ny: 60,
system: "Shop",
label: "New order",
log: "order received",
},
{
id: "stock",
@@ -65,9 +53,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 200,
nx: 200,
ny: 190,
system: "Rule",
label: "In stock?",
log: "stock checked: available",
},
{
id: "invoice",
@@ -76,9 +61,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 110,
nx: 105,
ny: 330,
system: "Accounting",
label: "Create invoice",
log: "invoice created",
},
{
id: "crm",
@@ -87,9 +69,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 290,
nx: 295,
ny: 330,
system: "CRM",
label: "Update customer",
log: "customer record updated",
},
{
id: "mail",
@@ -98,9 +77,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 200,
nx: 200,
ny: 470,
system: "Email",
label: "Send confirmation",
log: "confirmation sent",
},
],
edges: [
@@ -126,13 +102,6 @@ export const WORKFLOWS: readonly Workflow[] = [
},
{
id: "documents",
name: "Incoming documents",
summary:
"Invoices arrive as PDF by email. An AI step reads them, a rule checks the result, and a person only sees the unclear ones.",
byHand:
"Someone opens every PDF, reads supplier, amount and date, and types them into the accounting system.",
automated:
"Clear cases are booked directly. Unclear ones go to a person with the document attached.",
nodes: [
{
id: "inbox",
@@ -141,9 +110,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 200,
nx: 200,
ny: 60,
system: "Inbox",
label: "Email with PDF",
log: "document received",
},
{
id: "read",
@@ -152,9 +118,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 200,
nx: 200,
ny: 190,
system: "AI step",
label: "Read the document",
log: "supplier, amount and date extracted",
},
{
id: "check",
@@ -163,9 +126,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 200,
nx: 200,
ny: 320,
system: "Rule",
label: "Plausible?",
log: "values checked",
},
{
id: "book",
@@ -174,9 +134,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 110,
nx: 105,
ny: 460,
system: "Accounting",
label: "Book it",
log: "booked automatically",
},
{
id: "ask",
@@ -185,9 +142,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 290,
nx: 295,
ny: 460,
system: "A person",
label: "Review and decide",
log: "unclear: sent to a person for review",
},
],
edges: [
@@ -204,12 +158,6 @@ export const WORKFLOWS: readonly Workflow[] = [
},
{
id: "lead",
name: "Enquiry to follow-up",
summary:
"Someone fills in the contact form. The contact is created, the right person is told, and a reminder makes sure nobody forgets.",
byHand:
"Someone copies the enquiry into the CRM, forwards it by email and hopes the follow-up is not forgotten.",
automated: "The enquiry is filed, assigned and followed up on schedule.",
nodes: [
{
id: "form",
@@ -218,9 +166,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 200,
nx: 200,
ny: 60,
system: "Website",
label: "Contact form",
log: "enquiry received",
},
{
id: "contact",
@@ -229,9 +174,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 200,
nx: 200,
ny: 190,
system: "CRM",
label: "Create contact",
log: "contact created",
},
{
id: "notify",
@@ -240,9 +182,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 110,
nx: 105,
ny: 330,
system: "Team chat",
label: "Notify sales",
log: "sales notified",
},
{
id: "wait",
@@ -251,9 +190,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 290,
nx: 295,
ny: 330,
system: "Rule",
label: "No reply in 3 days?",
log: "follow-up scheduled",
},
{
id: "remind",
@@ -262,9 +198,6 @@ export const WORKFLOWS: readonly Workflow[] = [
y: 290,
nx: 295,
ny: 470,
system: "Tasks",
label: "Create reminder",
log: "reminder created",
},
],
edges: [
@@ -286,37 +219,26 @@ export const WORKFLOWS: readonly Workflow[] = [
},
];
export const KIND_LABEL: Readonly<Record<NodeKind, string>> = {
trigger: "Trigger: something happens",
rule: "Rule: a fixed check",
ai: "AI step: reads or sorts",
action: "Action: a system does something",
human: "Person: decides the unclear cases",
};
const NO_TEXT: FlowNodeCopy = { system: "", label: "", log: "" };
export const AUTOMATION_NOTES = [
{
title: "Worth automating when",
items: [
"The same steps repeat every day or every week",
"Data is typed from one tool into another",
"Mistakes happen because the work is monotonous",
],
},
{
title: "What stays with people",
items: [
"Decisions and exceptions",
"Anything a customer should hear from a person",
"The final say when the automation is unsure",
],
},
{
title: "What I need from you",
items: [
"Access to the tools and their interfaces (APIs)",
"Someone who knows the process as it is done today",
"Real examples to test with",
],
},
] as const;
/** The example workflows with their texts in one language. */
export const workflowsFor = (
copy: Readonly<Record<string, WorkflowCopy>>,
): readonly Workflow[] =>
LAYOUTS.flatMap((layout) => {
const text = copy[layout.id];
if (text === undefined) return [];
return [
{
...layout,
name: text.name,
summary: text.summary,
byHand: text.byHand,
automated: text.automated,
nodes: layout.nodes.map((node) => ({
...node,
...(text.nodes[node.id] ?? NO_TEXT),
})),
},
];
});