kimal3 0.2.9 β 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -0
- package/dist/api.js +16 -7
- package/dist/arbeitsplatz-dateien.js +62 -30
- package/dist/argumente.js +25 -97
- package/dist/befehle/arbeitsplatz.js +51 -68
- package/dist/befehle/firma-daten.js +24 -26
- package/dist/befehle/kontext-fragen.js +62 -44
- package/dist/befehle/kontext-gemeinsam.js +86 -43
- package/dist/befehle/kontext-hoch.js +40 -46
- package/dist/befehle/kontext-runter.js +70 -51
- package/dist/befehle/login.js +23 -9
- package/dist/befehle/logout.js +9 -4
- package/dist/befehle/projekt-liste.js +24 -29
- package/dist/befehle/projekt-neu.js +25 -31
- package/dist/befehle/skill-install.js +78 -49
- package/dist/befehle/skill-tausch.js +28 -0
- package/dist/befehle/wache.js +12 -6
- package/dist/befehle/werkbank.js +118 -88
- package/dist/befehle-katalog-en.js +185 -0
- package/dist/befehle-katalog.js +10 -49
- package/dist/befehlswoerter.js +69 -0
- package/dist/einstellungen-mischen.js +2 -2
- package/dist/einstellungen-schreiben.js +8 -5
- package/dist/fehler.js +5 -5
- package/dist/firmen-liste.js +42 -5
- package/dist/format.js +41 -0
- package/dist/fragen-lesen.js +3 -3
- package/dist/hilfe.js +24 -0
- package/dist/kimal3.js +77 -71
- package/dist/kontext-dateien.js +30 -23
- package/dist/kontext-hoch-zeile.js +5 -3
- package/dist/kontext-rat.js +151 -0
- package/dist/kontext-stand.js +68 -210
- package/dist/nachricht.js +39 -15
- package/dist/projekt-neu-firma.js +9 -9
- package/dist/projekt-zeilen.js +40 -54
- package/dist/slash-befehle.js +36 -0
- package/dist/sprache.js +114 -0
- package/dist/texte/allgemein.js +99 -0
- package/dist/texte/arbeitsplatz.js +135 -0
- package/dist/texte/kontext-hoch.js +128 -0
- package/dist/texte/kontext.js +171 -0
- package/dist/texte/projekte.js +126 -0
- package/dist/texte/skill.js +87 -0
- package/dist/texte/wache.js +36 -0
- package/dist/texte/werkbank.js +161 -0
- package/dist/wache-rechnung.js +27 -40
- package/dist/wache-schalter.js +40 -5
- package/dist/werkbank-name.js +28 -10
- package/dist/wort.js +98 -0
- package/package.json +27 -27
- package/skill/SKILL.md +18 -10
- package/skill/commands/k3.md +1 -1
- package/skill/commands/k3feierabend.md +99 -97
- package/skill/commands/k3kontext.md +2 -2
- package/skill/commands/k3kurs.md +6 -5
- package/skill/commands/k3start.md +2 -1
- package/skill/en/SKILL.md +451 -0
- package/skill/en/commands/k3.md +106 -0
- package/skill/en/commands/k3analyse.md +59 -0
- package/skill/en/commands/k3context.md +92 -0
- package/skill/en/commands/k3course.md +54 -0
- package/skill/en/commands/k3list.md +26 -0
- package/skill/en/commands/k3project.md +50 -0
- package/skill/en/commands/k3start.md +47 -0
- package/skill/en/commands/k3wrapup.md +99 -0
- package/dist/zz-kaputt.js +0 -1
package/dist/projekt-zeilen.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* π Eine reine Rechnung, damit `check:cli` sie ohne Server pruefen kann --
|
|
5
5
|
* dieselbe Ueberlegung wie bei `firmen-liste.ts` und `fehler.ts`.
|
|
6
6
|
*
|
|
7
|
-
* β οΈ **Warum
|
|
7
|
+
* β οΈ **Warum die Formatierer in `format.ts` eigene sind, obwohl die App `euroBetrag` und
|
|
8
8
|
* `datumLang` schon hat:** Die CLI ist ein eigenstaendiges npm-Paket mit
|
|
9
9
|
* `rootDir: src` -- sie kann aus `../../src` nichts importieren. Das ist kein
|
|
10
10
|
* Versehen, sondern der Preis dafuer, dass das Paket ohne die App baut.
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* Terminal "0 β¬" liest, haelt seine Eingabe fuer verloren. Das Terminal ist der
|
|
17
17
|
* Ort der Nachschau, die oeffentliche Karte der Ort der Aussage.
|
|
18
18
|
*/
|
|
19
|
+
import { datumLang, euro, stunden } from "./format.js";
|
|
20
|
+
import { PROJEKTE_TEXTE } from "./texte/projekte.js";
|
|
19
21
|
/** Ein Zahlenfeld: `null` heisst "nicht angegeben", `false` heisst "unlesbar". */
|
|
20
22
|
function zahlFeld(o, name) {
|
|
21
23
|
const w = o[name];
|
|
@@ -71,69 +73,30 @@ export function projekteAusAntwort(koerper) {
|
|
|
71
73
|
const liveSeit = o.live_seit;
|
|
72
74
|
if (liveSeit !== null && liveSeit !== undefined && typeof liveSeit !== "string")
|
|
73
75
|
return null;
|
|
74
|
-
const
|
|
76
|
+
const std = zahlFeld(o, "zeitersparnis_std_wo");
|
|
75
77
|
const ergebnis = zahlFeld(o, "ergebnis_monat_eur");
|
|
76
78
|
const umsatz = zahlFeld(o, "umsatz_monat_eur");
|
|
77
|
-
if (
|
|
79
|
+
if (std === false || ergebnis === false || umsatz === false)
|
|
78
80
|
return null;
|
|
79
81
|
projekte.push({
|
|
80
82
|
titel: o.titel,
|
|
81
83
|
live_seit: typeof liveSeit === "string" ? liveSeit : null,
|
|
82
84
|
laeuft: o.laeuft,
|
|
83
|
-
zeitersparnis_std_wo:
|
|
85
|
+
zeitersparnis_std_wo: std,
|
|
84
86
|
ergebnis_monat_eur: ergebnis,
|
|
85
87
|
umsatz_monat_eur: umsatz,
|
|
86
88
|
});
|
|
87
89
|
}
|
|
88
90
|
return projekte;
|
|
89
91
|
}
|
|
90
|
-
const MONATE = [
|
|
91
|
-
"Januar", "Februar", "MΓ€rz", "April", "Mai", "Juni",
|
|
92
|
-
"Juli", "August", "September", "Oktober", "November", "Dezember",
|
|
93
|
-
];
|
|
94
|
-
/**
|
|
95
|
-
* "2026-07-01" β "1. Juli 2026". Bewusst aus der Zeichenkette gerechnet statt
|
|
96
|
-
* ueber `new Date()`: Ein ISO-Datum ohne Uhrzeit gilt als UTC-Mitternacht und
|
|
97
|
-
* kippt sonst je nach Zeitzone einen Tag zurueck -- und ein Terminal steht
|
|
98
|
-
* irgendwo auf der Welt.
|
|
99
|
-
*
|
|
100
|
-
* β οΈ Erwartet ein REINES Datum (`JJJJ-MM-TT`), wie es die `date`-Spalte
|
|
101
|
-
* `live_seit` liefert. Passt es nicht, steht lieber das rohe Datum da als ein
|
|
102
|
-
* kaputter Satz ("NaN. August 2026" -- der Fehler vom 2026-08-12).
|
|
103
|
-
*/
|
|
104
|
-
function datumLang(isoDatum) {
|
|
105
|
-
const [jahr, monat, tag] = isoDatum.split("-");
|
|
106
|
-
const monatsname = MONATE[Number(monat) - 1];
|
|
107
|
-
if (!monatsname || !jahr || !Number.isFinite(Number(tag)))
|
|
108
|
-
return isoDatum;
|
|
109
|
-
return `${Number(tag)}. ${monatsname} ${jahr}`;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* "1200" β "1.200 β¬", "0.1" β "0,10 β¬".
|
|
113
|
-
*
|
|
114
|
-
* Cents erscheinen nur, wenn welche da sind: "1.200,00 β¬" taeuscht eine
|
|
115
|
-
* Genauigkeit vor, die niemand behauptet hat -- "0 β¬" bei 0,10 β¬ verschweigt
|
|
116
|
-
* dagegen eine, die dasteht.
|
|
117
|
-
*/
|
|
118
|
-
function euro(wert) {
|
|
119
|
-
const hatCent = Math.round(wert * 100) % 100 !== 0;
|
|
120
|
-
const stellen = hatCent ? 2 : 0;
|
|
121
|
-
return `${new Intl.NumberFormat("de-DE", {
|
|
122
|
-
minimumFractionDigits: stellen,
|
|
123
|
-
maximumFractionDigits: stellen,
|
|
124
|
-
}).format(wert)} β¬`;
|
|
125
|
-
}
|
|
126
|
-
/** "6" β "6", "6.5" β "6,5". Die Spalte ist numeric(6,2). */
|
|
127
|
-
function stunden(wert) {
|
|
128
|
-
return new Intl.NumberFormat("de-DE", { maximumFractionDigits: 2 }).format(wert);
|
|
129
|
-
}
|
|
130
92
|
/** Was oben in der zweiten Zeile steht: laeuft, geplant oder noch nicht live. */
|
|
131
|
-
function zustand(p) {
|
|
93
|
+
function zustand(p, s) {
|
|
94
|
+
const t = PROJEKTE_TEXTE[s];
|
|
132
95
|
if (p.live_seit === null)
|
|
133
|
-
return
|
|
96
|
+
return t.notLiveYet;
|
|
134
97
|
return p.laeuft
|
|
135
|
-
?
|
|
136
|
-
:
|
|
98
|
+
? t.liveSince(datumLang(p.live_seit, s))
|
|
99
|
+
: t.plannedFrom(datumLang(p.live_seit, s));
|
|
137
100
|
}
|
|
138
101
|
/**
|
|
139
102
|
* Zwei Zeilen je Projekt: Titel, darunter die Angaben.
|
|
@@ -144,26 +107,27 @@ function zustand(p) {
|
|
|
144
107
|
* eines Menschen. Eine ausdrueckliche 0 ist dagegen eine ANGABE und wird
|
|
145
108
|
* gezeigt -- deshalb wird auf `null` geprueft, nicht auf Wahrheitswert.
|
|
146
109
|
*/
|
|
147
|
-
export function projektZeilen(projekte) {
|
|
110
|
+
export function projektZeilen(projekte, s) {
|
|
111
|
+
const t = PROJEKTE_TEXTE[s];
|
|
148
112
|
const zeilen = [];
|
|
149
113
|
projekte.forEach((p, i) => {
|
|
150
114
|
if (i > 0)
|
|
151
115
|
zeilen.push("");
|
|
152
116
|
zeilen.push(` ${i + 1}. ${p.titel}`);
|
|
153
|
-
const teile = [zustand(p)];
|
|
117
|
+
const teile = [zustand(p, s)];
|
|
154
118
|
if (p.zeitersparnis_std_wo !== null) {
|
|
155
|
-
teile.push(`${stunden(p.zeitersparnis_std_wo)}
|
|
119
|
+
teile.push(`${stunden(p.zeitersparnis_std_wo, s)}${t.perWeek}`);
|
|
156
120
|
}
|
|
157
121
|
if (p.ergebnis_monat_eur !== null) {
|
|
158
|
-
teile.push(`${euro(p.ergebnis_monat_eur)}
|
|
122
|
+
teile.push(`${euro(p.ergebnis_monat_eur, s)}${t.resultPerMonth}`);
|
|
159
123
|
}
|
|
160
124
|
if (p.umsatz_monat_eur !== null) {
|
|
161
|
-
teile.push(`${euro(p.umsatz_monat_eur)}
|
|
125
|
+
teile.push(`${euro(p.umsatz_monat_eur, s)}${t.revenuePerMonth}`);
|
|
162
126
|
}
|
|
163
127
|
// Nur der Zustand steht da? Dann sagen wir das ausdruecklich, statt eine
|
|
164
128
|
// halbe Zeile hinzustellen, die aussieht, als sei etwas abgeschnitten.
|
|
165
129
|
if (teile.length === 1)
|
|
166
|
-
teile.push(
|
|
130
|
+
teile.push(t.noFigures);
|
|
167
131
|
zeilen.push(` ${teile.join(" Β· ")}`);
|
|
168
132
|
});
|
|
169
133
|
return zeilen;
|
|
@@ -207,6 +171,28 @@ export function titelAusAlterAntwort(koerper) {
|
|
|
207
171
|
* eingefangen hat.*
|
|
208
172
|
*/
|
|
209
173
|
export const MAX_PROJEKTE_ANZEIGE = 12;
|
|
174
|
+
/**
|
|
175
|
+
* Die Liste OHNE Zahlen, wenn nur die Titel lesbar sind -- je Eintrag EIN `console.log`-Argument.
|
|
176
|
+
* `alt`: die App ist aelter als das Werkzeug (L-7); `neu`: die App ist neuer (Review-Runde 4/6).
|
|
177
|
+
* Rein, damit `check:cli` die ganze Ausgabe messen kann statt nach Schluesselnamen zu suchen.
|
|
178
|
+
*/
|
|
179
|
+
export function titelListeZeilen(titel, art, s, url) {
|
|
180
|
+
const t = PROJEKTE_TEXTE[s];
|
|
181
|
+
const hinweis = art === "alt"
|
|
182
|
+
? [t.oldAppNote1, t.oldAppNote2, t.oldAppNote3]
|
|
183
|
+
: [t.newAppNote1, t.newAppNote2, t.newAppNote3];
|
|
184
|
+
return [
|
|
185
|
+
t.projectsHeader(titel.length, MAX_PROJEKTE_ANZEIGE),
|
|
186
|
+
...titel.map((x, i) => ` ${i + 1}. ${x}`),
|
|
187
|
+
...hinweis,
|
|
188
|
+
t.viewOnline(url),
|
|
189
|
+
];
|
|
190
|
+
}
|
|
191
|
+
/** Die Absage, wenn nicht einmal die Titel lesbar sind (`stderr`). */
|
|
192
|
+
export function werkzeugZuAltZeilen(s) {
|
|
193
|
+
const t = PROJEKTE_TEXTE[s];
|
|
194
|
+
return [t.toolTooOld1, t.toolTooOld2, t.toolTooOld3];
|
|
195
|
+
}
|
|
210
196
|
/**
|
|
211
197
|
* Sagt, ob die Antwort ueberhaupt ein Feld `projekte` mitbringt.
|
|
212
198
|
*
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const SLASH_PAARE = [
|
|
2
|
+
{ de: "k3", en: "k3" },
|
|
3
|
+
{ de: "k3start", en: "k3start" },
|
|
4
|
+
{ de: "k3feierabend", en: "k3wrapup" }, // kein-anzeigetext: Befehlsname (Spec Teil 4, Abschnitt 6)
|
|
5
|
+
{ de: "k3kontext", en: "k3context" }, // kein-anzeigetext: Befehlsname
|
|
6
|
+
{ de: "k3projekt", en: "k3project" }, // kein-anzeigetext: Befehlsname
|
|
7
|
+
{ de: "k3liste", en: "k3list" }, // kein-anzeigetext: Befehlsname
|
|
8
|
+
{ de: "k3analyse", en: "k3analyse" }, // britisch, in beiden Sprachen gleich
|
|
9
|
+
{ de: "k3kurs", en: "k3course" }, // kein-anzeigetext: Befehlsname
|
|
10
|
+
];
|
|
11
|
+
/** Der Name in der Sprache des Satzes, der ihn nennt (ohne `/`). Unbekannt -> unveraendert. */
|
|
12
|
+
export function slashIn(deutsch, sprache) {
|
|
13
|
+
return SLASH_PAARE.find((p) => p.de === deutsch)?.[sprache] ?? deutsch;
|
|
14
|
+
}
|
|
15
|
+
/** Die Liste fuer /pro: alle ausser `k3`, in Anzeige-Reihenfolge. */
|
|
16
|
+
export function slashListeFuer(sprache) {
|
|
17
|
+
return SLASH_PAARE.filter((p) => p.de !== "k3").map((p) => p[sprache]);
|
|
18
|
+
}
|
|
19
|
+
/** Wo Skill und Befehlssatz einer Sprache im Paket liegen (relativ zu `cli/`). */
|
|
20
|
+
export function skillQuelle(sprache) {
|
|
21
|
+
return sprache === "en"
|
|
22
|
+
? { skill: "skill/en/SKILL.md", befehle: "skill/en/commands" } // kein-anzeigetext: Paketpfade
|
|
23
|
+
: { skill: "skill/SKILL.md", befehle: "skill/commands" }; // kein-anzeigetext: Paketpfade
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Welche Dateien der ANDEREN Sprache nach einem Lauf weg duerfen (T4-3): nur die, deren Ersatz in
|
|
27
|
+
* DIESEM Lauf wirklich geschrieben wurde -- und nie ein gleichnamiger Befehl (`k3`, `k3start`,
|
|
28
|
+
* `k3analyse` wurden gerade selbst ueberschrieben). Ob eine Datei unsere Marke traegt, prueft
|
|
29
|
+
* `raeumeVeralteteAuf`; diese Rechnung kennt nur Namen.
|
|
30
|
+
*/
|
|
31
|
+
export function andereSpracheZuRaeumen(geschrieben, sprache) {
|
|
32
|
+
const andere = sprache === "en" ? "de" : "en";
|
|
33
|
+
return SLASH_PAARE
|
|
34
|
+
.filter((p) => p.de !== p.en && geschrieben.includes(`${p[sprache]}.md`)) // kein-anzeigetext: Dateiendung
|
|
35
|
+
.map((p) => `${p[andere]}.md`); // kein-anzeigetext: Dateiendung
|
|
36
|
+
}
|
package/dist/sprache.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In welcher Sprache spricht das Programm? (Spec Teil 3, T3-1, T3-2, T3-10)
|
|
3
|
+
*
|
|
4
|
+
* π EIN WERT FUER DEN GANZEN LAUF: Ein Programmlauf gehoert genau einem Menschen. `sprache()`
|
|
5
|
+
* wird beim ersten Gebrauch bestimmt und gilt bis zum Ende -- es sei denn, eine Antwort des
|
|
6
|
+
* Servers nennt eine andere Kontosprache (`uebernimmKontosprache`), dann ab sofort die neue.
|
|
7
|
+
*
|
|
8
|
+
* π΄ WARUM `Intl` UND NICHT `LANG` (gemessen am 01.10.): In Git Bash steht `LANG=en_US.UTF-8`,
|
|
9
|
+
* obwohl Windows deutsch ist -- und Claude Code ruft auf Windows jeden Befehl ueber Git Bash
|
|
10
|
+
* auf. Node meldet ueber `Intl` trotzdem richtig `de-DE`.
|
|
11
|
+
*
|
|
12
|
+
* π DER MERKZETTEL UEBERSTEHT DAS ABMELDEN (T3-1): Er liegt in einer eigenen Datei neben der
|
|
13
|
+
* Anmeldung, und `kimal3 logout` loescht nur die Anmeldung.
|
|
14
|
+
*
|
|
15
|
+
* Die drei Rechnungen sind rein (check:cli); nur `sprache()` und `uebernimmKontosprache` lesen
|
|
16
|
+
* und schreiben die Platte.
|
|
17
|
+
*/
|
|
18
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { homedir } from "node:os";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { leseAnmeldung } from "./anmeldung.js";
|
|
22
|
+
/** Der Kopf, den das Programm bei jedem Aufruf mitschickt (Spec 3.2, Schritt 2). */
|
|
23
|
+
export const SPRACH_KOPF = "X-Kimal3-Sprache"; // kein-anzeigetext: HTTP-Kopf
|
|
24
|
+
/** Der Kopf, in dem der Server die Kontosprache nennt -- klein, so liefert `Headers.get` ihn ohnehin. */
|
|
25
|
+
export const KONTO_KOPF = "x-kimal3-kontosprache"; // kein-anzeigetext: HTTP-Kopf
|
|
26
|
+
/** Nur genau "de" oder "en" -- "EN", "en " und alles andere sind keine Angabe. */
|
|
27
|
+
export function alsSprache(wert) {
|
|
28
|
+
return wert === "de" || wert === "en" ? wert : null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Was Node ueber das Betriebssystem meldet -> Sprache. Englisch nur bei `en` oder `en-β¦`.
|
|
32
|
+
* π΄ `-u-va-posix` ist das C/POSIX-Gebiet (`LANG=C` auf macOS/Linux) -- das ist KEINE Angabe
|
|
33
|
+
* und faellt auf Deutsch zurueck (Messung c im Teil-3-Plan; dort ungemessen, aus der
|
|
34
|
+
* ICU-Dokumentation).
|
|
35
|
+
*/
|
|
36
|
+
export function spracheAusSystem(gebiet) {
|
|
37
|
+
const klein = gebiet.toLowerCase();
|
|
38
|
+
if (klein.includes("-u-va-posix"))
|
|
39
|
+
return "de";
|
|
40
|
+
return klein === "en" || klein.startsWith("en-") ? "en" : "de";
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Die Sprache beim Start (T3-2): Merkzettel -> sonst Deutsch, wenn schon eine Anmeldung liegt
|
|
44
|
+
* (wer von einer aelteren Fassung aktualisiert: Sabine mit englischem Windows sah bisher immer
|
|
45
|
+
* Deutsch) -> sonst das Betriebssystem.
|
|
46
|
+
*/
|
|
47
|
+
export function startSprache(lage) {
|
|
48
|
+
const gemerkt = alsSprache(lage.merkzettel);
|
|
49
|
+
if (gemerkt !== null)
|
|
50
|
+
return gemerkt;
|
|
51
|
+
if (lage.angemeldet)
|
|
52
|
+
return "de";
|
|
53
|
+
return spracheAusSystem(lage.systemGebiet);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Was eine Antwort am Merkzettel aendert: die neue Sprache, wenn der Kopf eine gueltige Sprache
|
|
57
|
+
* nennt UND der Merkzettel sie noch nicht traegt -- sonst `null` (nichts zu tun).
|
|
58
|
+
*
|
|
59
|
+
* π VERGLICHEN WIRD MIT DEM MERKZETTEL, NICHT MIT DER GERADE GESPROCHENEN SPRACHE: Sabine
|
|
60
|
+
* spricht nach dem Update Deutsch ohne Merkzettel. Wuerde nur bei einem Unterschied geschrieben,
|
|
61
|
+
* entstuende fuer sie nie einer -- und nach `kimal3 logout` fiele sie auf ihr englisches Windows
|
|
62
|
+
* zurueck.
|
|
63
|
+
*/
|
|
64
|
+
export function nachAntwort(gemerkt, kopf) {
|
|
65
|
+
const neu = alsSprache(kopf);
|
|
66
|
+
return neu !== null && neu !== alsSprache(gemerkt) ? neu : null;
|
|
67
|
+
}
|
|
68
|
+
const ORDNER = join(homedir(), ".kimal3");
|
|
69
|
+
const DATEI = join(ORDNER, "sprache.json");
|
|
70
|
+
function leseMerkzettel() {
|
|
71
|
+
try {
|
|
72
|
+
const roh = JSON.parse(readFileSync(DATEI, "utf8"));
|
|
73
|
+
return roh && typeof roh === "object" ? roh.sprache : null;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
let gemerkt;
|
|
80
|
+
let aktuell = null;
|
|
81
|
+
/** Die Sprache dieses Laufs. */
|
|
82
|
+
export function sprache() {
|
|
83
|
+
if (aktuell === null) {
|
|
84
|
+
gemerkt = leseMerkzettel();
|
|
85
|
+
aktuell = startSprache({
|
|
86
|
+
merkzettel: gemerkt,
|
|
87
|
+
angemeldet: leseAnmeldung() !== null,
|
|
88
|
+
systemGebiet: Intl.DateTimeFormat().resolvedOptions().locale,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return aktuell;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Nach jeder Antwort des Servers: nennt sie eine Kontosprache, gilt die ab sofort -- auch fuer den
|
|
95
|
+
* Rest dieses Laufs (Spec 3.2, Schritt 3). Ohne gueltigen Kopf bleibt alles, wie es ist (Schritt 4).
|
|
96
|
+
* Wirft nie: Der Merkzettel ist eine Bequemlichkeit, kein Grund, einen Befehl umzuwerfen.
|
|
97
|
+
*/
|
|
98
|
+
export function uebernimmKontosprache(kopf) {
|
|
99
|
+
sprache();
|
|
100
|
+
const neu = nachAntwort(gemerkt, kopf);
|
|
101
|
+
if (neu === null)
|
|
102
|
+
return;
|
|
103
|
+
aktuell = neu;
|
|
104
|
+
gemerkt = neu;
|
|
105
|
+
try {
|
|
106
|
+
// β οΈ `mode: 0o700` wie in anmeldung.ts und gesehen.ts -- wer den Ordner zuerst anlegt, legt
|
|
107
|
+
// seine Rechte fuer immer fest (Kommentar in gesehen.ts).
|
|
108
|
+
mkdirSync(ORDNER, { recursive: true, mode: 0o700 });
|
|
109
|
+
writeFileSync(DATEI, JSON.stringify({ sprache: neu }, null, 2), "utf8");
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* Merkzettel nicht schreibbar: dieser Lauf spricht trotzdem richtig. */
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const de = {
|
|
2
|
+
// ββ Verteiler (kimal3.ts) ββ
|
|
3
|
+
// Der Zeilenumbruch gehoert zum Satz: Dahinter steht die Hilfe, ein eigener Baustein.
|
|
4
|
+
unknownCommand: (wort) => `Den Befehl β${wort}" kenne ich nicht.\n`,
|
|
5
|
+
signInFirst: "Bitte zuerst `kimal3 login` ausfΓΌhren.",
|
|
6
|
+
unreachable: "kimal3 ist gerade nicht erreichbar.",
|
|
7
|
+
credentialsStored: (email) => ` β Ausweis hinterlegt fΓΌr ${email}\n` + " (ob er noch gilt, zeigt erst der nΓ€chste Befehl)",
|
|
8
|
+
notSignedIn: " β Nicht angemeldet. β kimal3 login",
|
|
9
|
+
versionLines: (version, url) => ` kimal3 ${version}\n spricht mit ${url}`,
|
|
10
|
+
versionUnknown: "unbekannt",
|
|
11
|
+
outputChannelError: (text) => `kimal3: Ausgabekanal-Fehler -- ${text}\n`,
|
|
12
|
+
// ββ login.ts ββ
|
|
13
|
+
loginOpen: (url) => `\n Γffne ${url}/cli`,
|
|
14
|
+
loginEnterCode: (code) => ` und gib diesen Code ein: ${code}\n`,
|
|
15
|
+
loginWaiting: " Warte auf die BestΓ€tigung β¦ (Abbruch mit Strg+C)",
|
|
16
|
+
loginExpired: "\nDer Anmeldecode ist abgelaufen. Bitte `kimal3 login` noch einmal.",
|
|
17
|
+
needsPro: "DafΓΌr brauchst du ein Pro-Konto.",
|
|
18
|
+
signedInAs: (email) => `\nβ Angemeldet als ${email}.`,
|
|
19
|
+
loginFailed: "\nDas hat nicht geklappt.",
|
|
20
|
+
// Gesamt-Review Teil 4: Nach dem Login steht Claudes Anleitung in der Kontosprache (`skill-tausch.ts`).
|
|
21
|
+
// Die Zeile spricht immer die Sprache, in die umgestellt wurde -- deshalb steht der Sprachname fest im Satz.
|
|
22
|
+
skillSwitched: "β Claudes Anleitung (Skill und /k3-Befehle) ist jetzt auf Deutsch.",
|
|
23
|
+
skillSwitchIncomplete: "β Claudes Anleitung lieΓ sich nicht ganz auf Deutsch umstellen. Bitte `kimal3 skill install` ausfΓΌhren.",
|
|
24
|
+
// ββ logout.ts ββ
|
|
25
|
+
noSignIn: "Auf diesem Rechner war keine Anmeldung.",
|
|
26
|
+
unreachableNothingChanged: "kimal3 ist gerade nicht erreichbar. Es wurde nichts geΓ€ndert.",
|
|
27
|
+
signedOut: (email) => `Abgemeldet. (${email})`,
|
|
28
|
+
// ββ argumente.ts (zerlege) ββ
|
|
29
|
+
cannotUseWord: (teil) => `Mit β${teil}" kann ich hier nichts anfangen. Erwartet werden nur Optionen wie β--name wert".`,
|
|
30
|
+
unknownOption: (getippt) => `Die Option β--${getippt}" kenne ich nicht.`,
|
|
31
|
+
duplicateOption: (getippt) => `Die Option β--${getippt}" steht zweimal da. Bitte nenne sie nur einmal.`,
|
|
32
|
+
missingValue: (getippt) => `Der Option β--${getippt}" fehlt ihr Wert.`,
|
|
33
|
+
// ββ fehler.ts (schlussFehler) ββ
|
|
34
|
+
appDoesNotKnowCommand: "Diese Version der App kennt den Befehl noch nicht. " +
|
|
35
|
+
"Hast du gerade aktualisiert, versuch es in ein paar Minuten noch einmal.",
|
|
36
|
+
serverOverloaded: "Der Server ist gerade ΓΌberlastet oder hat zu lange gebraucht. " +
|
|
37
|
+
"Versuch es in einem Moment noch einmal.",
|
|
38
|
+
// ββ kontext-gemeinsam.ts (`absage`, die Firmen-Zeilen) und die unlesbare Antwort ββ
|
|
39
|
+
genericFailure: "Das hat nicht geklappt.",
|
|
40
|
+
// Auch von kontext-runter.ts/kontext-hoch.ts (Task 10): ein `200` mit unlesbarem Rumpf.
|
|
41
|
+
unreadableAnswer: "Die Antwort war nicht lesbar. Bitte gleich noch einmal.",
|
|
42
|
+
pleaseSignIn: " Bitte `kimal3 login` ausfΓΌhren.",
|
|
43
|
+
sameNameUseId: "\n Diese beiden heissen gleich β nimm die ID:",
|
|
44
|
+
yourCompanies: "\n Deine Firmen:",
|
|
45
|
+
createCompany: (url) => `\n Eine Firma anlegen: ${url}/firma/neu`,
|
|
46
|
+
nameNotUnique: (name, wieOft) => ` β${name}" β kommt ${wieOft}Γ vor und ist ΓΌber den Namen nicht wΓ€hlbar.`,
|
|
47
|
+
// Befehlszeilen zum Kopieren, kein Satz: in beiden Sprachen mit Absicht gleich gebaut -- Befehl und
|
|
48
|
+
// Schalter kommen schon in der Sprache des Satzes herein (befehlIn, schalterIn).
|
|
49
|
+
companyLine: (befehl, schalter, wert) => ` kimal3 ${befehl} --${schalter} ${wert}`,
|
|
50
|
+
companyIdLine: (befehl, schalter, id, name, nr) => ` kimal3 ${befehl} --${schalter} "${id}" # ${name} (${nr}.)`,
|
|
51
|
+
// ββ Hilfe (hilfe.ts) -- der Rahmen gehoert zum Schluessel (TU-2) ββ
|
|
52
|
+
helpTagline: "\n kimal3 β trag deine KI-Projekte dort ein, wo sie entstehen.\n\n",
|
|
53
|
+
helpGermanWordsToo: "", // Deutsch: keine Zeile (T3-13) -- die deutsche Hilfe bleibt Zeichen fuer Zeichen
|
|
54
|
+
helpMore: "\n\n Mehr zu einem Befehl: kimal3 projekt neu --help\n",
|
|
55
|
+
};
|
|
56
|
+
const en = {
|
|
57
|
+
unknownCommand: (wort) => `Unknown command β${wort}β.\n`,
|
|
58
|
+
signInFirst: "Please run `kimal3 login` first.",
|
|
59
|
+
unreachable: "Can't reach kimal3 right now.",
|
|
60
|
+
credentialsStored: (email) => ` β Sign-in stored for ${email}\n` +
|
|
61
|
+
" (the next command will show whether it is still valid)",
|
|
62
|
+
notSignedIn: " β Not signed in. β kimal3 login",
|
|
63
|
+
versionLines: (version, url) => ` kimal3 ${version}\n connected to ${url}`,
|
|
64
|
+
versionUnknown: "unknown",
|
|
65
|
+
outputChannelError: (text) => `kimal3: output channel error -- ${text}\n`,
|
|
66
|
+
loginOpen: (url) => `\n Open ${url}/cli`,
|
|
67
|
+
loginEnterCode: (code) => ` and enter this code: ${code}\n`,
|
|
68
|
+
loginWaiting: " Waiting for confirmation β¦ (cancel with Ctrl+C)",
|
|
69
|
+
loginExpired: "\nThe sign-in code has expired. Please run `kimal3 login` again.",
|
|
70
|
+
needsPro: "You need a Pro account for this.",
|
|
71
|
+
signedInAs: (email) => `\nβ Signed in as ${email}.`,
|
|
72
|
+
loginFailed: "\nThat didn't work.",
|
|
73
|
+
skillSwitched: "β Claude's instructions (skill and /k3 commands) are now in English.",
|
|
74
|
+
skillSwitchIncomplete: "β Claude's instructions couldn't be fully switched to English. Please run `kimal3 skill install`.",
|
|
75
|
+
noSignIn: "No sign-in found on this computer.",
|
|
76
|
+
unreachableNothingChanged: "Can't reach kimal3 right now. Nothing was changed.",
|
|
77
|
+
signedOut: (email) => `Signed out. (${email})`,
|
|
78
|
+
cannotUseWord: (teil) => `Unexpected β${teil}β. Expected only options like β--name valueβ.`,
|
|
79
|
+
unknownOption: (getippt) => `Unknown option β--${getippt}β.`,
|
|
80
|
+
duplicateOption: (getippt) => `The option β--${getippt}β was given twice. Please use it once only.`,
|
|
81
|
+
missingValue: (getippt) => `The option β--${getippt}β is missing its value.`,
|
|
82
|
+
appDoesNotKnowCommand: "This version of the app doesn't know that command yet. " +
|
|
83
|
+
"If you've just updated, try again shortly.",
|
|
84
|
+
serverOverloaded: "The server is overloaded or timed out. " +
|
|
85
|
+
"Try again in a moment.",
|
|
86
|
+
genericFailure: "That didn't work.",
|
|
87
|
+
unreadableAnswer: "Couldn't read the reply. Please try again in a moment.",
|
|
88
|
+
pleaseSignIn: " Please run `kimal3 login`.",
|
|
89
|
+
sameNameUseId: "\n These two have the same name β use the ID:",
|
|
90
|
+
yourCompanies: "\n Your companies:",
|
|
91
|
+
createCompany: (url) => `\n Create a company: ${url}/firma/neu`,
|
|
92
|
+
nameNotUnique: (name, wieOft) => ` β${name}β appears ${wieOft} times and can't be chosen by name.`,
|
|
93
|
+
companyLine: (befehl, schalter, wert) => ` kimal3 ${befehl} --${schalter} ${wert}`,
|
|
94
|
+
companyIdLine: (befehl, schalter, id, name, nr) => ` kimal3 ${befehl} --${schalter} "${id}" # ${name} (${nr}.)`,
|
|
95
|
+
helpTagline: "\n kimal3 β log your AI projects where they happen.\n\n",
|
|
96
|
+
helpGermanWordsToo: "\n\n The German command words work too, e.g. kimal3 kontext runter.",
|
|
97
|
+
helpMore: "\n\n More on a command: kimal3 project new --help\n",
|
|
98
|
+
};
|
|
99
|
+
export const ALLGEMEIN_TEXTE = { de, en };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const de = {
|
|
2
|
+
// ββ arbeitsplatz.ts: Hilfe ββ
|
|
3
|
+
help: `
|
|
4
|
+
kimal3 arbeitsplatz neu [dein-vorname] [--firma "Name" | --firma-id "β¦"]
|
|
5
|
+
|
|
6
|
+
Holt deine KI-Arbeitsplatz-Vorlage und legt sie hier als Ordner an --
|
|
7
|
+
die ganze Familie: Firmenebene, Werkbank, Beispielprojekt.
|
|
8
|
+
|
|
9
|
+
Der Ordner heisst "KI-Arbeitsplatz" (bei englischer Firma "AI-Workplace").
|
|
10
|
+
Mehrere Firmen trennst du ueber den Ordner darueber, z. B.
|
|
11
|
+
Kunden/ARNO/KI-Arbeitsplatz.
|
|
12
|
+
|
|
13
|
+
dein-vorname Optional. Er benennt die WERKBANK darin -- aus "sabine"
|
|
14
|
+
wird "werkbank-sabine" (bei englischem Konto
|
|
15
|
+
"workbench-sabine"). Ohne Angabe steht dort der Name,
|
|
16
|
+
den der Server aus deiner Anmelde-Adresse gebildet hat.
|
|
17
|
+
|
|
18
|
+
--firma "Name" Noetig, wenn du zu mehreren Firmen gehoerst -- der
|
|
19
|
+
Firmenkontext kommt gleich mit in den Ordner.
|
|
20
|
+
Die Absage nennt dir die Namen als fertige Befehle.
|
|
21
|
+
|
|
22
|
+
--firma-id "β¦" Fuer den Fall, dass zwei deiner Firmen GLEICH heissen.
|
|
23
|
+
Auch diese Absage nennt dir fertige Befehle.
|
|
24
|
+
|
|
25
|
+
π΄ Bricht ab, wenn der Zielordner schon existiert und NICHT leer ist --
|
|
26
|
+
eigene Arbeit wird dadurch nie versehentlich ueberschrieben.
|
|
27
|
+
|
|
28
|
+
Du hast schon einen Firmen-Ordner und brauchst nur eine eigene Werkbank?
|
|
29
|
+
kimal3 werkbank neu <dein-vorname>
|
|
30
|
+
`,
|
|
31
|
+
// ββ arbeitsplatz.ts: der Rat in der Absage "Ordner nicht leer" ββ
|
|
32
|
+
adviceWorkbenchOnly: (ziel, befehl) => " Steht dein Arbeitsplatz schon und dir fehlt nur die eigene Werkbank?\n" +
|
|
33
|
+
" Dann geh HINEIN und leg sie dort an:\n" +
|
|
34
|
+
` cd ${ziel}\n` +
|
|
35
|
+
` kimal3 ${befehl} <dein-vorname>`,
|
|
36
|
+
// ββ arbeitsplatz.ts: Absagen vor dem Holen ββ
|
|
37
|
+
emptyName: "Ein leerer Name ist kein gueltiger Name.\n",
|
|
38
|
+
nameSuggestion: (befehl, zusammen, optionen) => `Besteht dein Name aus mehreren Woertern? Dann schreib ihn zusammen:\n` +
|
|
39
|
+
` kimal3 ${befehl} ${zusammen}${optionen}\n`,
|
|
40
|
+
onlyOneName: (wort) => `Mit β${wort}" kann ich hier nichts anfangen -- nur EIN Name ist erlaubt.\n`,
|
|
41
|
+
unusableName1: (name) => `Aus β${name}" kann ich keinen Ordnernamen machen -- es bleiben nur`,
|
|
42
|
+
unusableName2: "Zeichen uebrig, die in keinem Ordnernamen stehen duerfen.",
|
|
43
|
+
unusableName3: (befehl) => ` Nimm deinen Vornamen: kimal3 ${befehl} sabine`,
|
|
44
|
+
// ββ arbeitsplatz.ts: Absagen nach dem Holen ββ
|
|
45
|
+
unsafeFolderName: (name) => `Die Antwort enthaelt einen unsicheren Ordnernamen: β${name}". Bitte melden.`,
|
|
46
|
+
noWorkbank1: "In der Vorlage steckt kein Werkbank-Ordner -- damit fehlt der Teil,",
|
|
47
|
+
noWorkbank2: "in dem du arbeiten wuerdest. Das liegt nicht an dir. Bitte melden.",
|
|
48
|
+
// ββ arbeitsplatz.ts: Erfolg und Rat (nextStepsHeader und nextStepsSay benutzt auch werkbank neu) ββ
|
|
49
|
+
filesCreated: (n) => `β ${n} ${n === 1 ? "Datei" : "Dateien"} angelegt.`,
|
|
50
|
+
workbenchNamed: (name) => ` Deine Werkbank darin heisst β${name}".`,
|
|
51
|
+
nextStepsHeader: " Weiter geht's:",
|
|
52
|
+
nextStepsSay: ' dann "Lass uns einrichten" tippen',
|
|
53
|
+
questionsOpen: (offen, gesamt) => ` Danach: ${offen} von ${gesamt} Fragen ΓΌber deine Firma sind noch offen.`,
|
|
54
|
+
questionsAskedOneByOne: " Claude stellt sie dir einzeln und trΓ€gt deine Antworten ein:",
|
|
55
|
+
// ββ arbeitsplatz-dateien.ts: das Schreiben der Dateien (auch von werkbank neu benutzt) ββ
|
|
56
|
+
noFiles1: "Die Antwort enthielt keine einzige Datei -- das ist kein gueltiges Ergebnis.",
|
|
57
|
+
noFiles2: "Bitte gleich noch einmal versuchen.",
|
|
58
|
+
isAFile1: (ordner) => `β${ordner}" gibt es schon -- als DATEI, nicht als Ordner.`,
|
|
59
|
+
isAFile2: "Benenn die Datei um oder geh in einen anderen Ordner.",
|
|
60
|
+
notEmpty1: (ordner) => `Der Ordner β${ordner}" gibt es schon und ist nicht leer.`,
|
|
61
|
+
notEmpty2: "Ich fasse ihn nicht an -- so geht nie versehentlich eigene Arbeit verloren.",
|
|
62
|
+
cannotReach1: (ordner) => `Ich komme an den Ordner β${ordner}" nicht heran.`,
|
|
63
|
+
reason: (grund) => `Grund: ${grund}`,
|
|
64
|
+
cannotReach2: "Das liegt an diesem Rechner, nicht am Server -- Rechte pruefen.",
|
|
65
|
+
unsafePath: (pfad) => `Die Antwort enthaelt einen unsicheren Pfad: β${pfad}". Bitte melden.`,
|
|
66
|
+
writeFailed: (grund) => `Schreiben fehlgeschlagen: ${grund}`,
|
|
67
|
+
incomplete: (ordner) => `Der Ordner β${ordner}" ist dadurch UNVOLLSTAENDIG -- er darf geloescht werden.`,
|
|
68
|
+
writeFailedBlame: "Das liegt an diesem Rechner, nicht am Server -- Platz und Rechte pruefen.",
|
|
69
|
+
};
|
|
70
|
+
const en = {
|
|
71
|
+
help: `
|
|
72
|
+
kimal3 workplace new [your-first-name] [--company "Name" | --company-id "β¦"]
|
|
73
|
+
|
|
74
|
+
Fetches your AI Workplace template and creates it here as a folder β
|
|
75
|
+
the whole family: company level, workbench, example project.
|
|
76
|
+
|
|
77
|
+
The folder is called "AI-Workplace" ("KI-Arbeitsplatz" if your company
|
|
78
|
+
works in German). To keep several companies apart, put each in its own
|
|
79
|
+
parent folder, e.g. Clients/ARNO/AI-Workplace.
|
|
80
|
+
|
|
81
|
+
your-first-name Optional. It names the WORKBENCH inside β "sabine"
|
|
82
|
+
becomes "workbench-sabine" ("werkbank-sabine" with a
|
|
83
|
+
German account). Without it, the workbench carries the
|
|
84
|
+
name the server built from your sign-in address.
|
|
85
|
+
|
|
86
|
+
--company "Name" Needed if you belong to several companies β the
|
|
87
|
+
company context is added to the folder.
|
|
88
|
+
The error message lists the names as ready-to-run commands.
|
|
89
|
+
|
|
90
|
+
--company-id "β¦" For when two of your companies have the SAME name.
|
|
91
|
+
This error message also lists ready-to-run commands.
|
|
92
|
+
|
|
93
|
+
The German option names (--firma, --firma-id) work too.
|
|
94
|
+
|
|
95
|
+
π΄ Stops if the target folder already exists and is NOT empty β
|
|
96
|
+
your own work is never overwritten by accident.
|
|
97
|
+
|
|
98
|
+
You already have a company folder and only need your own workbench?
|
|
99
|
+
kimal3 workbench new <your-first-name>
|
|
100
|
+
`,
|
|
101
|
+
adviceWorkbenchOnly: (ziel, befehl) => " Is your workplace already there and you only need your own workbench?\n" +
|
|
102
|
+
" Then go INTO it and create it there:\n" +
|
|
103
|
+
` cd ${ziel}\n` +
|
|
104
|
+
` kimal3 ${befehl} <your-first-name>`,
|
|
105
|
+
emptyName: "A name can't be empty.\n",
|
|
106
|
+
nameSuggestion: (befehl, zusammen, optionen) => "Does your name have several words? Then write it as one:\n" +
|
|
107
|
+
` kimal3 ${befehl} ${zusammen}${optionen}\n`,
|
|
108
|
+
onlyOneName: (wort) => `β${wort}β won't work here β only ONE name is allowed.\n`,
|
|
109
|
+
unusableName1: (name) => `I can't make a folder name out of β${name}β β all that's left are`,
|
|
110
|
+
unusableName2: "characters that aren't allowed in any folder name.",
|
|
111
|
+
unusableName3: (befehl) => ` Use your first name: kimal3 ${befehl} sabine`,
|
|
112
|
+
unsafeFolderName: (name) => `The reply contains an unsafe folder name: β${name}β. Please report this.`,
|
|
113
|
+
noWorkbank1: "The template has no workbench folder, so the place you'd work in is missing.",
|
|
114
|
+
noWorkbank2: "That isn't down to you. Please report this.",
|
|
115
|
+
filesCreated: (n) => `β ${n} ${n === 1 ? "file" : "files"} created.`,
|
|
116
|
+
workbenchNamed: (name) => ` Inside it, your workbench is called β${name}β.`,
|
|
117
|
+
nextStepsHeader: " Next steps:",
|
|
118
|
+
nextStepsSay: " then type βLet's get set upβ",
|
|
119
|
+
questionsOpen: (offen, gesamt) => ` After that: ${offen} of ${gesamt} questions about your company are still open.`,
|
|
120
|
+
questionsAskedOneByOne: " Claude asks them one at a time and records your answers:",
|
|
121
|
+
noFiles1: "The reply didn't contain a single file β that isn't a valid result.",
|
|
122
|
+
noFiles2: "Please try again in a moment.",
|
|
123
|
+
isAFile1: (ordner) => `β${ordner}β already exists β as a FILE, not a folder.`,
|
|
124
|
+
isAFile2: "Rename the file or go to a different folder.",
|
|
125
|
+
notEmpty1: (ordner) => `The folder β${ordner}β already exists and isn't empty.`,
|
|
126
|
+
notEmpty2: "I won't touch it β that way your own work is never lost by accident.",
|
|
127
|
+
cannotReach1: (ordner) => `I can't access the folder β${ordner}β.`,
|
|
128
|
+
reason: (grund) => `Reason: ${grund}`,
|
|
129
|
+
cannotReach2: "That's down to this computer, not the server β check the permissions.",
|
|
130
|
+
unsafePath: (pfad) => `The reply contains an unsafe path: β${pfad}β. Please report this.`,
|
|
131
|
+
writeFailed: (grund) => `Writing failed: ${grund}`,
|
|
132
|
+
incomplete: (ordner) => `The folder β${ordner}β is therefore INCOMPLETE β you can delete it.`,
|
|
133
|
+
writeFailedBlame: "That's down to this computer, not the server β check the disk space and permissions.",
|
|
134
|
+
};
|
|
135
|
+
export const ARBEITSPLATZ_TEXTE = { de, en };
|