Der Discord-Server hat drei Sorten Rollen, die Dokumente kannten nur eine: Zugehörigkeit (AfiS, Personalabteilung) als Türsteher, Rangrollen für die Rechte, Zugangsrollen für die Freigabestufe. Das bestehende RoleMapping trägt das fast, es fehlt nur der Türsteher. Die beiden Felder dafür kommen mit Phase 1, die Struktur ist in docs/13-entscheidungen.md festgehalten. Die Freigabestufen heißen jetzt wie die Zugangsrollen: Unclassified, Restricted Access, Confidential Access, Sensitive Access, Critical Operations, Executive Oversight. Fachbegriffe der Domäne bleiben englisch, auch in einer deutschen Oberfläche. Die Bezeichnungen sind damit Daten in ClearanceLevelConfig, nicht mehr Oberflächentext in lib/i18n/de.ts: sie sind im Adminbereich änderbar, und was der Leiter ändern kann, gehört nicht in eine Übersetzungsdatei. StampMark bekommt den Namen hereingereicht. Vier neue Tests prüfen den Zustand, den der Seed herstellt, gegen die Datenbank statt gegen eine Konstante im Code.
47 lines
1.7 KiB
TypeScript
47 lines
1.7 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
import { join } from 'node:path'
|
|
import { de } from '@/lib/i18n/de'
|
|
|
|
describe('Sprachkonvention', () => {
|
|
it('haelt die Stufenbezeichnungen aus den Texten heraus', () => {
|
|
// Sie stehen in ClearanceLevelConfig, sind im Adminbereich aenderbar und
|
|
// folgen den Zugangsrollen des Discord-Servers.
|
|
expect(de.clearance).not.toHaveProperty('names')
|
|
})
|
|
|
|
it('verzichtet auf Ausrufezeichen und Entschuldigungen', () => {
|
|
const flat = JSON.stringify(de)
|
|
expect(flat).not.toMatch(/!/)
|
|
expect(flat.toLowerCase()).not.toMatch(/erfolgreich|entschuldigung/)
|
|
})
|
|
|
|
it('haelt deutsche Oberflaechentexte aus dem JSX heraus', () => {
|
|
// Umlaute und Eszett sind der billigste verlaessliche Marker fuer einen
|
|
// deutschen Text. Alles Sichtbare gehoert nach lib/i18n/de.ts, siehe CLAUDE.md.
|
|
const offenders: string[] = []
|
|
const roots = ['app', 'components']
|
|
|
|
function walk(dir: string) {
|
|
for (const name of readdirSync(dir)) {
|
|
const path = join(dir, name)
|
|
if (statSync(path).isDirectory()) {
|
|
walk(path)
|
|
continue
|
|
}
|
|
if (!name.endsWith('.tsx')) continue
|
|
const source = readFileSync(path, 'utf8')
|
|
for (const [index, line] of source.split('\n').entries()) {
|
|
const withoutComments = line.replace(/\/\/.*$/, '').replace(/\/\*.*?\*\//g, '')
|
|
if (/[äöüÄÖÜß]/.test(withoutComments)) {
|
|
offenders.push(`${path}:${index + 1}`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const root of roots) walk(join(process.cwd(), root))
|
|
expect(offenders).toEqual([])
|
|
})
|
|
})
|