diff --git a/felsokning/app/src/App.tsx b/felsokning/app/src/App.tsx
index 69409a9..4ec660c 100644
--- a/felsokning/app/src/App.tsx
+++ b/felsokning/app/src/App.tsx
@@ -33,6 +33,7 @@ import AlvaAbonnemang from "./pages/alva/Abonnemang";
import AlvaGarantier from "./pages/alva/Garantier";
import AlvaImpressum from "./pages/alva/Impressum";
import AlvaUtgavor from "./pages/alva/Utgavor";
+import AlvaSprak from "./pages/alva/Sprak";
import AlvaForsakring from "./pages/alva/Forsakring";
import AlvaSupport from "./pages/alva/Support";
import { Portalvakt } from "./pages/alva/Portalvakt";
@@ -76,6 +77,7 @@ function AppContent() {
en del av kravet, inte en placering. */}
} />
} />
+ } />
{/* Portalen är stängd utan giltig plattformssession — se
Portalvakt. Utan konfigurerad plattform finns ingen session
att kräva, och vyerna är då märkta som demonstration. */}
diff --git a/felsokning/app/src/pages/alva/Ram.tsx b/felsokning/app/src/pages/alva/Ram.tsx
index 83a9ea9..6693d85 100644
--- a/felsokning/app/src/pages/alva/Ram.tsx
+++ b/felsokning/app/src/pages/alva/Ram.tsx
@@ -127,6 +127,7 @@ export function Ram({ children, portal = false }: { children: ReactNode; portal?
["/alva/dataskydd", "Privacy"],
["/alva/villkor", "Terms"],
["/alva/tillganglighet", "Accessibility"],
+ ["/alva/sprak", "Languages"],
["/alva/utgavor", "Release notes"],
].map(([till, text]) => (
diff --git a/felsokning/app/src/pages/alva/Sprak.tsx b/felsokning/app/src/pages/alva/Sprak.tsx
new file mode 100644
index 0000000..6b9058a
--- /dev/null
+++ b/felsokning/app/src/pages/alva/Sprak.tsx
@@ -0,0 +1,217 @@
+// ALVA-SPEC-060 · Språksidan.
+//
+// Sidan finns i foten och inte i portalens navigation, av två skäl. Den
+// är dokumentation snarare än ett arbetsverktyg, och portalraden har
+// redan tio poster — en elfte hade brutit raden till ytterligare en rad
+// på telefon utan att någon sökt sig dit oftare.
+//
+// ---- Vad sidan gör som en språkmeny inte gör -----------------------------
+//
+// En vanlig språkväljare påstår att produkten finns på tio språk och
+// slutar där. Den här redovisar i stället skillnaden mellan att en text
+// ÄR översatt och att den är GRANSKAD av någon som kan yrket, och den
+// visar det på de strängar där skillnaden faktiskt betyder något:
+// grindens hinder, alltså de meningar som nekar en tekniker att avsluta
+// ett ärende.
+//
+// Det är ett obekvämt sätt att presentera sin egen produkt, och det är
+// hela poängen. En verkstadschef i Rumänien som får veta att metodiken
+// inte är fackgranskad på rumänska kan planera för det. Samma chef som
+// får veta det av en tekniker som missförstått en säkerhetsinstruktion
+// kan inte.
+
+import { useState } from "react";
+import { SPRAK, metodikvarning, oversattare, tackning } from "../../../../services/gemensam/sprak/index.mjs";
+import { Block, Etikett, FARG, Rubrik, Tabell } from "@/alva/komponenter";
+import { Ram } from "./Ram";
+
+type Sprakpost = { kod: string; namn: string; egetNamn: string; granskat: boolean };
+const SPRAKEN = SPRAK as Sprakpost[];
+
+// De strängar som väljs för förhandsvisningen är inte slumpvis valda.
+// Var och en är en mening som nekar ett avslut, och därmed en mening som
+// måste gå att förstå av den som blir nekad.
+const PROV = [
+ "grind.objekt",
+ "grind.historik",
+ "grind.matarstallning.ej_foto",
+ "grind.hogvolt.spanningslos",
+ "slutsats.utan_slutsats",
+];
+
+function Sektion({ etikett, rubrik, children }: { etikett: string; rubrik: string; children: React.ReactNode }) {
+ return (
+
+
+ English is the default and the source language. Nine translations follow. What the platform does
+ not do is claim that a translated interface means a translated method.
+
+
+
+
+ {/* ---- Den avgränsning som styr allt annat ---- */}
+
+
+
+
+ Labels, buttons, statuses. Finite, rarely changed. An English string reaching a German user is
+ an irritation, not a hazard — so a missing translation falls back to English without comment.
+
+
+
+
+ Instructions for work on a vehicle. Here an unreviewed translation is worse than a foreign
+ language one — because English looks foreign, while a bad translation looks like an
+ instruction. It is shown in English and marked, with the language named.
+
+
+
+
+
+ The method itself is never translated. Phase names and status words are ALVA’s structure and
+ read identically in every country, so an auditor can read a Romanian and a German case record
+ without knowing which language the workshop works in.
+
+
+
+ {/* ---- Redovisningen ---- */}
+
+ [
+ `${s.namn} — ${s.egetNamn}`,
+ `${Math.round((tackning(s.kod) as number) * 100)} %`,
+ s.granskat ? "Yes" : "No — procedure text shown in English",
+ ])}
+ />
+
+ Interface coverage is measured, not estimated: a test fails the build if any key is missing from
+ any language. Review status is a statement about people, not about files, and is set by hand.
+
+
+
+ {/* ---- Beviset ---- */}
+
+
+ These are the sentences that refuse to let a technician close a case. A refusal nobody understands
+ is a refusal with no way through — so they are the right strings to judge a translation by.
+
+
+ {/* Rutnät, inte flexradbrytning. Tio språk bryts av flex till nio
+ plus ett ensamt, och den ensamma knappen läser sig som en
+ eftertanke i stället för som ett språk bland tio. Fem kolumner
+ och två gör tio jämnt i båda riktningarna. */}
+
+ {SPRAKEN.map((s) => (
+
+ ))}
+
+
+
+ {PROV.map((nyckel) => (
+
+
+ {nyckel}
+
+
+ {t(nyckel)}
+
+
+ ))}
+
+
+ {/* Varningen står under provet, inte över det. Den som just läst
+ fem meningar på sitt eget språk ska mötas av vad de INTE
+ garanterar — inte varnas i förväg och sedan lugnas av att
+ texten ser bra ut. */}
+ {varning && (
+
+ Not reviewed — {post.egetNamn}
+
+ {varning}
+
+
+ )}
+ {!varning && (
+
+ Reviewed — {post.egetNamn}
+
+ Procedure text in {post.egetNamn} has been read by a specialist working in the trade. Steps and
+ checks are shown in {post.egetNamn} throughout.
+
+
+ )}
+
+
+ {/* ---- Hur språket väljs ---- */}
+
+
+ {[
+ ["User preference", "What the individual has selected."],
+ ["Organization setting", "The workshop's documentation language."],
+ ["Browser language", "The first language the platform recognises."],
+ ["English", "The default, and the source."],
+ ].map(([rubrik, text], i) => (
+
+
+ {String(i + 1).padStart(2, "0")}
+
+
+
+ {rubrik}
+
+
+ {text}
+
+
+
+ ))}
+
+
+ The organization ranks above the browser deliberately. A workshop in Germany with a Polish
+ technician needs one documentation language — the case record must not change language depending on
+ who happened to write the line.
+
+
+
+ );
+}
diff --git a/felsokning/services/gemensam/version.mjs b/felsokning/services/gemensam/version.mjs
index feb2fa2..db3be8a 100644
--- a/felsokning/services/gemensam/version.mjs
+++ b/felsokning/services/gemensam/version.mjs
@@ -21,6 +21,19 @@
// TÜV-härdningen ändrade vad loggen garanterar.
export const UTGAVOR = [
+ {
+ version: "3.2",
+ datum: "2026-08-06",
+ commit: "af27275",
+ rubrik: "Engelska som standard, tio EU-språk, och en regel som gällde på ett",
+ andringar: [
+ "Engelska är standard- och källspråk. Nio översättningar: tyska, franska, spanska, italienska, polska, nederländska, portugisiska, rumänska och svenska.",
+ "Gränssnittstext faller tillbaka på engelska tyst. Metodikinnehåll gör det aldrig tyst — en ogranskad översättning av en säkerhetsinstruktion är sämre än en engelsk, därför att engelska SYNS som ett annat språk.",
+ "Metodens struktur översätts inte. Fasnamn och statusord står likadant i varje land, så att en revisor kan läsa en rumänsk och en tysk rapport utan att veta vilket språk verkstaden arbetar på.",
+ "Kvalitetsgrindens hinder följer organisationens språk. En spärr på fel språk är en spärr utan väg förbi.",
+ "ALVA-RULE-200 gällde bara på svenska: en tekniker som skrev \"erledigt\" eller \"done\" i motiveringsfältet passerade filtret för icke-svar. Rättat på tio språk — det var ett hål i regeln, inte en översättningsbrist.",
+ ],
+ },
{
version: "3.1",
datum: "2026-08-06",