mirror of
https://github.com/praktimarc/kst4contest.git
synced 2026-10-19 05:42:58 +02:00
Document the two languages, and how to contribute a third
Both manuals get the language picker in full: where it sits, that it applies per profile and takes effect at once in every open window, and -- at more length -- what is deliberately left alone and why. An operator who sees an English beacon text in a German client should be able to read that it is meant that way. The contribution route is written down in both, because plain text files are only useful if somebody knows they are there: copy, translate, submit; a partial file is welcome; the build reports mistakes by file name and writes a worksheet of what is still missing. PROJECT_CONTEXT records the decisions that outlive this stage -- the four kinds of text and the rule that the addressee decides, that core produces no display text, that incompleteness is allowed and incorrectness is not, that anything built outside a composition needs its text read inside one, and that a column id is never translated. CLAUDE.md said two modules; there are three, and the third never ships. It also states the German orthography now, which this session got wrong across a spec and two plans: ss after a short vowel, so muss and dass.
This commit is contained in:
7 files changed
+2796
-4
No files matched your search
@@ -22,8 +22,19 @@ neue Subagent die Codebasis von null und kostet mehr, als er beiträgt.
|
|||||||
## Build
|
## Build
|
||||||
|
|
||||||
Gradle, nicht Maven. `./gradlew clean build`, Java 21. Das Projekt besteht aus
|
Gradle, nicht Maven. `./gradlew clean build`, Java 21. Das Projekt besteht aus
|
||||||
zwei Modulen: `core` (Fachlichkeit) und `app-desktop` (Oberfläche). Paketnamen
|
drei Modulen: `core` (Fachlichkeit), `app-desktop` (Oberfläche) und
|
||||||
sind über beide Module hinweg dieselben.
|
`i18n-generator` (reines Bauwerkzeug, das aus den Übersetzungsdateien Kotlin
|
||||||
|
erzeugt und **nicht ausgeliefert** wird). Paketnamen sind über `core` und
|
||||||
|
`app-desktop` hinweg dieselben.
|
||||||
|
|
||||||
|
## Sprache
|
||||||
|
|
||||||
|
Kommunikation mit Marc auf Deutsch, in **neuer Rechtschreibung** — `muss` und `dass`,
|
||||||
|
nicht `muß` und `daß`. Quelltextkommentare und Javadoc bleiben ausschließlich Englisch.
|
||||||
|
|
||||||
|
Oberflächentexte stehen seit Etappe 9 in `app-desktop/src/main/i18n/strings_<code>.properties`
|
||||||
|
und nicht mehr im Quelltext. Wer dort etwas ändert: der Erzeuger prüft beim Bau, und die
|
||||||
|
Regel, was übersetzt wird, steht in `docs/PROJECT_CONTEXT.md` unter „Important Decisions".
|
||||||
|
|
||||||
## Laufende Arbeit
|
## Laufende Arbeit
|
||||||
|
|
||||||
|
|||||||
+25
-1
@@ -70,7 +70,7 @@ A batch of changes belongs in one `SimpleRoster.mutate(...)`, not one call per e
|
|||||||
|
|
||||||
## Operator Profiles and Per-Profile Persistence
|
## Operator Profiles and Per-Profile Persistence
|
||||||
|
|
||||||
- One operator profile owns one `preferences.xml`, one worked-station database and, since release 1.50, its own pair of stylesheets. Everything else under `~/.praktiKST/` stays global: audio files, DEM and terrain packages, the error log and the version-info feed.
|
- One operator profile owns one `preferences.xml`, one worked-station database, its own pair of stylesheets and its own interface language (all since release 1.50). Everything else under `~/.praktiKST/` stays global: audio files, DEM and terrain packages, the error log and the version-info feed.
|
||||||
- The **root profile** is the historic flat installation: `preferences.xml` and `praktiKST.db` directly below the application directory. It is never moved, and it always uses the common station database, because that database is the installation's own.
|
- The **root profile** is the historic flat installation: `preferences.xml` and `praktiKST.db` directly below the application directory. It is never moved, and it always uses the common station database, because that database is the installation's own.
|
||||||
- Additional profiles live under `profiles/<profileId>/`. `profileId` is a stable, file-system-safe slug assigned once; renaming a profile changes only its display name and never moves a directory.
|
- Additional profiles live under `profiles/<profileId>/`. `profileId` is a stable, file-system-safe slug assigned once; renaming a profile changes only its display name and never moves a directory.
|
||||||
- The registry `profiles.xml` is created lazily. An installation that has only the flat layout gets no registry and no `profiles/` directory; the root profile is synthesised in memory. Startup with no or exactly one profile therefore asks nothing and writes nothing, and a downgrade to an older release is a no-op.
|
- The registry `profiles.xml` is created lazily. An installation that has only the flat layout gets no registry and no `profiles/` directory; the root profile is synthesised in memory. Startup with no or exactly one profile therefore asks nothing and writes nothing, and a downgrade to an older release is a no-op.
|
||||||
@@ -270,6 +270,30 @@ section records only the durable architecture and operational boundaries.
|
|||||||
|
|
||||||
## Important Decisions and Workarounds
|
## Important Decisions and Workarounds
|
||||||
|
|
||||||
|
- **The interface language has four kinds of text, and the rule is the addressee** (release
|
||||||
|
1.50): interface text is translated; protocol text (the pipe-framed opcodes in
|
||||||
|
`On4KstProtocol`, and the `/`-prefixed chat commands) and amateur shorthand (the
|
||||||
|
`ChatPreferences` defaults, snippets, shortcuts, and QRA/QRG/QTF/LOS/Fresnel in labels) are
|
||||||
|
not; and diagnostic text -- the `println` calls in `core` -- is not either, because a German
|
||||||
|
stack trace is worse for whoever reads the bug report. Ask who reads it, not what it looks
|
||||||
|
like.
|
||||||
|
- **`core` produces no display text.** It returns a key or an enum and `app-desktop` turns it
|
||||||
|
into words. That is the language half of "core stays toolkit-free", and it also means no
|
||||||
|
German string can reach a protocol or a log line by construction. Two violations predate the
|
||||||
|
rule and are named in the stage 9 spec: `PathPropagationAssessment` and `PriorityCalculator`.
|
||||||
|
- **Incompleteness is allowed in a translation, incorrectness is not.** A missing key warns and
|
||||||
|
falls back to English at runtime, so a partial contributed language can be submitted; an
|
||||||
|
orphan key, a placeholder mismatch, a duplicate key or an unreadable file fail the build. The
|
||||||
|
argument count always comes from the English base.
|
||||||
|
- **Anything built outside a composition needs its text read inside one.** Window titles, tab
|
||||||
|
titles and column headers are all built once at startup -- the columns from Java -- so each
|
||||||
|
holds a function rather than a string. A stored string freezes at whatever was true when the
|
||||||
|
window opened, which is how the main window's title said "KST4Contest (Compose)" for a whole
|
||||||
|
session and the tab strip stayed English while its contents switched.
|
||||||
|
- **A column's `id` is never translated.** It is what the remembered column widths and order
|
||||||
|
are keyed on; translating it would read an operator's stored layout under names that no
|
||||||
|
longer match.
|
||||||
|
|
||||||
- **The palette has three sources, and the later one wins role by role** (release 1.50): the
|
- **The palette has three sources, and the later one wins role by role** (release 1.50): the
|
||||||
shipped stylesheet from the classpath, then the profile's own stylesheet file if present,
|
shipped stylesheet from the classpath, then the profile's own stylesheet file if present,
|
||||||
then the six role overrides stored in that profile's preferences. `resolvePalette` is a pure
|
then the six role overrides stored in that profile's preferences. `resolvePalette` is a pure
|
||||||
|
|||||||
@@ -1269,7 +1269,7 @@ denselben vier Pfaden, nicht `git add -A`.
|
|||||||
> | Text im Feld | 21,0 | **≈2,9** |
|
> | Text im Feld | 21,0 | **≈2,9** |
|
||||||
> | Akzent auf Fläche | **2,86** | **2,26** |
|
> | Akzent auf Fläche | **2,86** | **2,26** |
|
||||||
>
|
>
|
||||||
> Eine absolute Grenze würde also ab dem ersten Start in beiden Entwürfen warnen, ohne daß
|
> Eine absolute Grenze würde also ab dem ersten Start in beiden Entwürfen warnen, ohne dass
|
||||||
> der Operateur etwas getan hätte — und eine Warnung, die immer leuchtet, bringt ihm bei,
|
> der Operateur etwas getan hätte — und eine Warnung, die immer leuchtet, bringt ihm bei,
|
||||||
> Warnungen zu übersehen. Die Grenze bis zum Durchkommen der Vorlagen zu senken (auf etwa
|
> Warnungen zu übersehen. Die Grenze bis zum Durchkommen der Vorlagen zu senken (auf etwa
|
||||||
> 2,2) macht sie wertlos.
|
> 2,2) macht sie wertlos.
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,301 @@
|
|||||||
|
# Entwurf — Mehrsprachigkeit
|
||||||
|
|
||||||
|
Vertieft: `docs/superpowers/specs/2026-09-25-compose-migration-design.md`, Etappe 9.
|
||||||
|
|
||||||
|
Dieser Entwurf ersetzt den dortigen Abschnitt. Er ist im September geschrieben worden, und
|
||||||
|
seine Zahlen und eine seiner Annahmen stimmen nicht mehr.
|
||||||
|
|
||||||
|
## Ziel
|
||||||
|
|
||||||
|
Die Oberfläche spricht die Sprache des Operateurs, Deutsch und Englisch zum Start, und
|
||||||
|
weitere Sprachen können von außen beigetragen werden, ohne dass der Beitragende Kotlin anfasst.
|
||||||
|
|
||||||
|
Erfolg ist nachprüfbar:
|
||||||
|
|
||||||
|
- Die Sprache lässt sich umschalten, wirkt **sofort in allen offenen Fenstern** und bleibt nach
|
||||||
|
dem Neustart umgeschaltet.
|
||||||
|
- Sie gilt **pro Operateurprofil**.
|
||||||
|
- **Kein einziger Text, der an den ON4KST-Server oder an andere Funker geht, ändert sich mit
|
||||||
|
der Sprache.** Geprüft, nicht angesehen.
|
||||||
|
- Eine fehlende Übersetzung fällt auf Englisch zurück, statt zu fehlen.
|
||||||
|
- Eine fehlerhafte Übersetzung bricht den Bau ab, bevor sie ausgeliefert wird.
|
||||||
|
|
||||||
|
## Umfang dieser Etappe: die Mechanik, nicht die Texte
|
||||||
|
|
||||||
|
Gemessen am heutigen Stand, nach den Etappen 4 bis 8:
|
||||||
|
|
||||||
|
| | Zeichenketten |
|
||||||
|
|---|---|
|
||||||
|
`app-desktop` Kotlin | 852
|
||||||
|
`app-desktop` Java | 417
|
||||||
|
davon nach Oberflächentext aussehend, verschieden | **~394**
|
||||||
|
`core` | 2.968, davon 273 `println`
|
||||||
|
|
||||||
|
Die Zahlen des Ursprungsabschnitts (232 Compose, 492 Java) sind durch den JavaFX-Ausbau
|
||||||
|
überholt.
|
||||||
|
|
||||||
|
**Diese Etappe baut die Mechanik und zieht ein Dutzend Texte als Beweis heraus.** Die übrigen
|
||||||
|
rund 380 sind mechanische Folgearbeit, die häppchenweise gehen kann und keine
|
||||||
|
Architekturentscheidung mehr enthält. Der Grund für diesen Schnitt: die Mechanik trägt alle
|
||||||
|
380, und sie jetzt falsch zu bauen kostet 380 Mal nach.
|
||||||
|
|
||||||
|
## Die vier Textsorten
|
||||||
|
|
||||||
|
Der Ursprungsabschnitt nennt drei. Gemessen sind es vier, und die vierte ist die größte.
|
||||||
|
|
||||||
|
**Die Regel, die entscheidet, ist der Adressat des Textes** — nicht, wie er aussieht.
|
||||||
|
|
||||||
|
| Adressat | Sorte | Wo sie heute lebt | übersetzen |
|
||||||
|
|---|---|---|---|
|
||||||
|
Der Operateur am Bildschirm | Oberflächentext | `app-desktop/src/main` | **ja** |
|
||||||
|
Der ON4KST-Server | Protokolltext | `On4KstProtocol` (`LOGINC\|`, `SDONE\|`, `ACHAT\|`, `CK`, `DXQ`), dazu die `/`-Chatbefehle und `SETNAME`/`MYQRG` | nein |
|
||||||
|
Andere Funker | Funkertext | `ChatPreferences`-Vorgabewerte, Schnipsel, Kurztasten | nein |
|
||||||
|
Der Entwickler beim Fehlersuchen | Diagnosetext | 273 `println` in `core` | nein |
|
||||||
|
|
||||||
|
Die beiden mittleren sind dasselbe Risiko aus zwei Richtungen: ein übersetzter Protokollbefehl
|
||||||
|
bricht die Verbindung, ein übersetztes Funkerkürzel macht den Operateur unverständlich.
|
||||||
|
|
||||||
|
Der Protokolltext hat dabei zwei Ebenen, die nicht zu verwechseln sind: die **Rahmen** auf dem
|
||||||
|
Draht (`LOGINC|`, `SDONE|`, `ACHAT|` in `On4KstProtocol`) brechen bei einer Übersetzung die
|
||||||
|
Verbindung, die **`/`-Chatbefehle** (`/CQ`, `/AWAY`) gehen als Nachricht durch und werden vom
|
||||||
|
Server nicht verstanden. Beide dürfen sich nicht mit der Sprache ändern.
|
||||||
|
|
||||||
|
Keiner dieser Fehler fällt in einem Test auf, der nur die Oberfläche ansieht — und im Betrieb
|
||||||
|
fällt er erst im Contest auf, also unter Zeitdruck.
|
||||||
|
|
||||||
|
Der Diagnosetext ist neu benannt und gehört ausdrücklich dazu: ein deutscher Fehlerbericht ist
|
||||||
|
für den Wartenden schlechter als ein englischer, und die 273 `println`-Aufrufe sehen im
|
||||||
|
Quelltext genauso aus wie Oberflächentext.
|
||||||
|
|
||||||
|
### Die Regel für `core`
|
||||||
|
|
||||||
|
**`core` erzeugt keinen Anzeigetext. Es liefert einen Schlüssel oder einen Aufzählungswert,
|
||||||
|
und `app-desktop` macht daraus Text.**
|
||||||
|
|
||||||
|
Das ist die Fortsetzung des bestehenden Schnitts „`core` bleibt toolkit-frei" in die Sprache
|
||||||
|
hinein: `core` hat nicht zu wissen, welche Sprache der Mensch liest. Der zweite, praktische
|
||||||
|
Grund: weil `core` keine Nachschlagefunktion bekommt, **kann kein deutscher Text in eine
|
||||||
|
Protokoll- oder Diagnosezeile geraten.** Die Regel erzwingt die Abgrenzung, statt sie zu
|
||||||
|
verlangen.
|
||||||
|
|
||||||
|
Abgewogen und verworfen wurde, `core` ein Bündel mitzugeben. Ein `ResourceBundle` ist reines
|
||||||
|
JDK und hätte den Toolkit-Schnitt nicht verletzt — aber es hätte die Nachschlagefunktion neben
|
||||||
|
die 273 `println`-Aufrufe gestellt, also genau dorthin, wo Deutsch nicht hingehört.
|
||||||
|
|
||||||
|
**Zwei heutige Verstöße** werden benannt und bleiben diese Etappe englisch:
|
||||||
|
|
||||||
|
- `core/src/main/java/kst4contest/view/map/PathPropagationAssessment.java` — die
|
||||||
|
Ausbreitungsbeschreibungen („Aircraft scatter, tropo ducting/enhancement, …").
|
||||||
|
- `core/src/main/java/kst4contest/logic/PriorityCalculator.java` — die Prioritätsgründe
|
||||||
|
(„Active now").
|
||||||
|
|
||||||
|
Sie zu verlegen ist Folgearbeit. Der Nutzen der Regel jetzt: die ~380 Folgetexte haben eine
|
||||||
|
Regel statt einer Diskussion pro Fall.
|
||||||
|
|
||||||
|
## Die Übersetzungen sind Dateien, die niemand kompilieren muss
|
||||||
|
|
||||||
|
`app-desktop/src/main/i18n/strings_en.properties` als Grundlage, `strings_de.properties`
|
||||||
|
daneben. Flach, ein Schlüssel, ein Text:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
settings.save=Save settings
|
||||||
|
alert.armedFor=Armed for {0}
|
||||||
|
```
|
||||||
|
|
||||||
|
Wer eine Sprache beitragen will, kopiert die englische Datei, übersetzt die Werte rechts vom
|
||||||
|
Gleichheitszeichen und öffnet eine PR. Kein Kotlin, kein Gradle, keine Escapes für Umlaute.
|
||||||
|
|
||||||
|
**Warum `.properties` und nicht JSON.** Gemessen: `java.util.Properties` ist JDK und liest
|
||||||
|
seit Java 9 UTF-8, funktioniert also im Erzeuger ohne jede Abhängigkeit. JSON hätte entweder
|
||||||
|
eine neue Bauwerkzeug-Abhängigkeit gekostet oder einen selbstgeschriebenen Leser, dessen
|
||||||
|
Escape- und Unicode-Fehler dann hausgemacht wären — `groovy.json.JsonSlurper` steht nur im
|
||||||
|
Gradle-Kontext zur Verfügung und nicht in einem Modul, dessen Tests im normalen Lauf
|
||||||
|
mitzählen. Für Beitragende ist das Format zudem robuster: keine Klammern und Kommas, die man
|
||||||
|
kaputtmachen kann.
|
||||||
|
|
||||||
|
**Eine Datei pro Sprache**, nicht eine Datei mit allen Sprachen nebeneinander: so bleiben
|
||||||
|
Sprach-PRs voneinander getrennt. Den Vorteil der Nebeneinander-Form — der Übersetzer sieht das
|
||||||
|
Original neben seinem Feld — bildet ein Arbeitsblatt nach (siehe unten).
|
||||||
|
|
||||||
|
Englisch ist die Quellsprache, weil der Quelltext es so hält und `AGENTS.md` es für
|
||||||
|
Oberflächentext vorschreibt.
|
||||||
|
|
||||||
|
## Der Erzeuger: Unvollständigkeit ist erlaubt, Unrichtigkeit nicht
|
||||||
|
|
||||||
|
Ein Gradle-Task liest die Textdateien und erzeugt daraus Kotlin. **Die Fehler entstehen
|
||||||
|
dort**, nicht in einem Prüfer, der danebensteht.
|
||||||
|
|
||||||
|
| Befund | Erzeuger |
|
||||||
|
|---|---|
|
||||||
|
Schlüssel fehlt in einer Übersetzung | **Warnung**, Rückfall auf Englisch zur Laufzeit
|
||||||
|
Schlüssel steht in der Übersetzung, aber nicht in der englischen Grundlage | **Bau bricht ab** — Tippfehler des Beitragenden
|
||||||
|
Platzhalter weichen ab (`{0} {1}` → `{0}`) | **Bau bricht ab** — verschluckt Daten lautlos, und `MessageFormat` sagt dazu nichts
|
||||||
|
Datei unlesbar oder doppelter Schlüssel | **Bau bricht ab**
|
||||||
|
Schlüssel lässt sich nicht eindeutig in einen Kotlin-Namen übersetzen | **Bau bricht ab**
|
||||||
|
|
||||||
|
Diese Trennung ist der Kern: eine zu 80 % fertige beigetragene Sprache **muss** einreichbar
|
||||||
|
sein, sonst ist die ganze Auslagerung zwecklos. Eine falsche Übersetzung darf dagegen nicht
|
||||||
|
ausgeliefert werden.
|
||||||
|
|
||||||
|
Erzeugt wird nach `build/generated/`, nicht in den Quellbaum — damit kann das Erzeugnis nicht
|
||||||
|
von den Textdateien abdriften.
|
||||||
|
|
||||||
|
**Der Erzeuger ist ein eigenes Modul, kein Bauskript.** Die Spezifikation verlangt, ihn zu
|
||||||
|
testen; `buildSrc` leistet das nicht — gemessen: `buildSrc`-Tests laufen bei
|
||||||
|
`./gradlew build` nicht mit, was genau den Fehler ergäbe, den der Erzeuger verhindern soll. Er
|
||||||
|
wird deshalb ein drittes, **reines Bauwerkzeug-Modul** `i18n-generator` im Hauptbau, von
|
||||||
|
`app-desktop` über eine `JavaExec`-Aufgabe vor `compileKotlin` aufgerufen. Seine Tests sind
|
||||||
|
gewöhnliche Projekttests: sie laufen bei `./gradlew build`, zählen in der Test-XML mit und
|
||||||
|
werden von der bestehenden CI gefahren.
|
||||||
|
|
||||||
|
Das Modul wird **nicht** ausgeliefert: es steht nicht im Laufzeit-Klassenpfad von
|
||||||
|
`app-desktop`, nur in einer eigenen Konfiguration für den Aufruf. `CLAUDE.md` und
|
||||||
|
`docs/PROJECT_CONTEXT.md` nennen heute zwei Module und sind entsprechend nachzuziehen.
|
||||||
|
|
||||||
|
**Kein Parser zur Laufzeit.** Die Texte stehen als Kotlin im Jar: kein `ResourceBundle`, keine
|
||||||
|
Datei-E/A, und **keine neue Abhängigkeit, weder im Jar noch im Bau**.
|
||||||
|
|
||||||
|
### Die erzeugte Form macht auch die Stelligkeit prüfbar
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class Strings(private val lookup: (String) -> String) {
|
||||||
|
val settingsSave: String get() = lookup("settings.save")
|
||||||
|
fun alertArmedFor(arg0: Any): String = MessageFormat.format(lookup("alert.armedFor"), arg0)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also `strings.settingsSave` statt `strings.get("settings.save")`. Ein Tippfehler ist ein
|
||||||
|
Übersetzungsfehler, und `alertArmedFor()` ohne Argument übersetzt nicht.
|
||||||
|
|
||||||
|
Das ist der Teil, der die ~380 Folgetexte trägt: bei stringbasierten Schlüsseln wäre jeder
|
||||||
|
davon eine neue Gelegenheit für einen Laufzeitfehler, und die Fehlerart wäre „Text fehlt im
|
||||||
|
Fenster" — dieselbe stille Sorte, die diesen Zweig schon mehrfach erwischt hat.
|
||||||
|
|
||||||
|
Form und Stelligkeit kommen **immer aus der englischen Grundlage.** Deshalb bleibt ein falscher
|
||||||
|
Platzhalter ein Bauabbruch, während ein fehlender Schlüssel nur ein Rückfall ist.
|
||||||
|
|
||||||
|
### Das Arbeitsblatt für Beitragende
|
||||||
|
|
||||||
|
Ein Task erzeugt `strings_<lang>.todo.properties`: die fehlenden Schlüssel mit dem
|
||||||
|
**englischen Text als Wert**. Ohne das müsste ein Beitragender zwei Dateien diffen, um zu finden, was fehlt.
|
||||||
|
|
||||||
|
### Deckung
|
||||||
|
|
||||||
|
Der Erzeuger gibt beim Bau eine Zeile aus — `de: 394/394, fr: 120/394` —, damit einer PR
|
||||||
|
ansehbar ist, wie weit sie ist.
|
||||||
|
|
||||||
|
**Deutsch wird durch einen Test auf Vollständigkeit gehalten, weitere Sprachen nicht.** Deutsch
|
||||||
|
ist eine ausgelieferte Sprache; dort ist eine Lücke ein Fehler. Bei einer beigetragenen Sprache
|
||||||
|
ist eine Lücke der Normalfall.
|
||||||
|
|
||||||
|
## Dass es alle Fenster erreicht
|
||||||
|
|
||||||
|
Dieselbe Bauform wie bei den Farben in Etappe 8, damit es nur eine zu verstehen gibt.
|
||||||
|
|
||||||
|
Ein **`LanguageStore`** hält die Sprache als Compose-Zustand. Zwei Aufrufwege, eine Wahrheit:
|
||||||
|
|
||||||
|
- **Compose:** `LocalStrings.current.settingsSave`, bereitgestellt in
|
||||||
|
`ComposeWindowHost.show` neben `LocalPaletteStore`. Der Zustandszugriff ist wieder genau das,
|
||||||
|
was die Neuzeichnung auslöst — in Etappe 8 war `remember(darkMode)` der Grund, warum eine
|
||||||
|
Änderung gar kein Fenster erreichte.
|
||||||
|
- **Java:** `Strings.current()` gegen ein `@Volatile`-Feld, das der Store beim Wechsel setzt.
|
||||||
|
Die verbliebenen AWT-Aufrufstellen holen damit beim nächsten Aufruf die neue Sprache, ohne
|
||||||
|
eigene Mechanik.
|
||||||
|
|
||||||
|
Der Store wird beim Profilwechsel neu gebildet, wie jeder andere profilgebundene Zustand.
|
||||||
|
|
||||||
|
## Die Einstellung
|
||||||
|
|
||||||
|
Ein Feld `guiOptions_language` in `ChatPreferences` — pro Profil, wie jede andere
|
||||||
|
GUI-Einstellung. Bei einer Mehrmann-Station ist das auch das Richtige: zwei Operateure, zwei
|
||||||
|
Sprachen. Drei Werte: leer (Systemsprache), `en`, `de`. Ein `Form.choice` im GUI-Reiter.
|
||||||
|
|
||||||
|
Die Vorgabe ist eine **reine Funktion** `localeFor(stored, systemDefault)`, damit sie ohne
|
||||||
|
Systemwechsel prüfbar ist: leerer Wert heißt Systemsprache, und ist die nicht Deutsch, dann
|
||||||
|
Englisch.
|
||||||
|
|
||||||
|
## Die zwölf Beweistexte
|
||||||
|
|
||||||
|
Bewußt **über mehrere Fenster verteilt**, nicht die zwölf nächstliegenden aus einer Datei: die
|
||||||
|
Save- und Close-Knöpfe des Einstellungsfensters, die Abschnittstitel des GUI-Reiters, die
|
||||||
|
Beschriftung der Sprachwahl selbst, ein Menütitel im Hauptfenster, die Verbindungsanzeige in
|
||||||
|
der Statuszeile und ein `ComposeAlert`-Knopf.
|
||||||
|
|
||||||
|
Der Grund ist das Abnahmekriterium: beim Umschalten im Einstellungsfenster muss sich **das
|
||||||
|
Hauptfenster dahinter sichtbar mitändern.** Zwölf Texte aus einer Datei würden das nicht
|
||||||
|
zeigen, und genau diese Sorte Scheinbeweis hat diesen Zweig mehrfach erwischt — eine
|
||||||
|
Einstellung, die durchgeschrieben und nicht gezeichnet wurde.
|
||||||
|
|
||||||
|
## Prüfung
|
||||||
|
|
||||||
|
Das Wertvolle sind die Regeln, nicht die Oberfläche. Alles ohne Bildschirm prüfbar:
|
||||||
|
|
||||||
|
- **Die drei Abgrenzungstests.** (1) `On4KstProtocol` baut seine Rahmen unter
|
||||||
|
`Locale.GERMAN` und `Locale.ENGLISH` zeichengleich — `login`, `settingsDone`, `addChat`,
|
||||||
|
`clientLivenessProbe`, `serverLivenessProbeResponse`. Geprüft wird die Ausgabe, nicht das
|
||||||
|
Bündel. Die Klasse ist paketprivat, der Test gehört also nach
|
||||||
|
`core/src/test/java/kst4contest/controller/`. (2) Die `ChatPreferences`-Vorgabewerte ändern sich mit keiner Sprache, **über
|
||||||
|
Reflexion über alle `String`-Felder**, nicht über eine gepflegte Liste: das deckt auch ab,
|
||||||
|
was nach dieser Etappe dazukommt. (3) Kein Wert in einem Bündel ist ein
|
||||||
|
Protokoll-Opcode, ein `/`-Chatbefehl, `SETNAME`, `MYQRG` oder ein
|
||||||
|
`ChatPreferences`-Vorgabewert.
|
||||||
|
- **Der Erzeuger selbst**, je ein Test für die fünf Befunde seiner Tabelle, gegen kleine
|
||||||
|
Textschnipsel. Das ist der Teil, der falsch sein kann, ohne dass es auffällt — ein
|
||||||
|
Bauabbruch, der nie abbricht, sieht aus wie ein sauberes Projekt.
|
||||||
|
- **`localeFor`**: leer und deutsches System → Deutsch; leer und französisches System →
|
||||||
|
Englisch; `"de"` gespeichert und englisches System → Deutsch; Müll gespeichert → Englisch.
|
||||||
|
- **Der Rückfall**: ein Schlüssel, den nur Englisch hat, kommt unter Deutsch englisch zurück.
|
||||||
|
- **Deutsch ist vollständig**, über die erzeugten Schlüssel.
|
||||||
|
- **Dass ein Sprachwechsel die Komposition erreicht** — dieselbe Bauform wie
|
||||||
|
`ThemeReadsPaletteStoreTest`, das bei den Farben genau diesen Fehler gefunden hat.
|
||||||
|
|
||||||
|
**Die PR-Prüfung fängt das alles schon.** Der `compile`-Auftrag in `pr-compile-check.yml` läuft
|
||||||
|
`./gradlew -S classes testClasses`; der Erzeuger hängt an `classes`, also schlägt eine kaputte
|
||||||
|
Übersetzung dort mit seiner eigenen Meldung fehl. Der `test`-Auftrag fährt die
|
||||||
|
Abgrenzungstests. **Kein neuer Workflow nötig** — das ist eine Folge davon, den Erzeuger in den
|
||||||
|
Bau zu hängen statt in ein eigenes Prüfskript.
|
||||||
|
|
||||||
|
## Was unverändert bleibt
|
||||||
|
|
||||||
|
- Die vier `ChatPreferences`-Vorgabewerte und alles, was in der gespeicherten XML eines
|
||||||
|
Operateurs steht: `bcn_beaconTextMainCat`, `bcn_beaconTextSecondCat`,
|
||||||
|
`messageHandling_autoAnswerTextMainCat`, `messageHandling_autoAnswerTextSecondCat`, sowie
|
||||||
|
Schnipsel und Kurztasten.
|
||||||
|
- `core` bekommt keine Nachschlagefunktion.
|
||||||
|
- Die Reiterreihenfolge im Einstellungsfenster; die Sprachwahl kommt in den bestehenden
|
||||||
|
GUI-Reiter.
|
||||||
|
- Keine neue Abhängigkeit, weder im Jar noch im Bau.
|
||||||
|
- `app-desktop`s übrige ~380 Texte bleiben englisch, mit der Regel aus „Die vier Textsorten"
|
||||||
|
für die Folgearbeit.
|
||||||
|
- Quelltextkommentare und Javadoc bleiben ausschließlich Englisch. Mehrsprachigkeit betrifft
|
||||||
|
die Oberfläche, nicht den Quelltext.
|
||||||
|
|
||||||
|
## Risiken
|
||||||
|
|
||||||
|
**Die Abgrenzung ist der Preis dieser Etappe, nicht die Mechanik.** Ein übersetzter
|
||||||
|
Protokollbefehl oder ein übersetztes Funkerkürzel fällt beim Bauen nicht auf und beim Hinsehen
|
||||||
|
auch nicht. Die drei Abgrenzungstests sind die Gegenmaßnahme, und der reflexionsbasierte unter
|
||||||
|
ihnen ist der einzige, der auch das abdeckt, woran beim Schreiben niemand gedacht hat.
|
||||||
|
|
||||||
|
**Ein Sprachwechsel erreicht alle Fenster — also auch alle falsch.** Der `LanguageStore` ist
|
||||||
|
wie der `PaletteStore` eine Stelle, an der ein Fehler sich überall gleichzeitig zeigt. Dafür
|
||||||
|
zeigt er sich sofort.
|
||||||
|
|
||||||
|
**Eine beigetragene Sprache kann halb englisch sein.** Das ist gewollt und der Preis dafür,
|
||||||
|
Beiträge überhaupt annehmen zu können. Die Deckungszeile macht es sichtbar; ob die Sprachwahl
|
||||||
|
dem Operateur den Deckungsgrad anzeigen soll, ist hier **nicht** entschieden und wäre eine
|
||||||
|
eigene kleine Erweiterung.
|
||||||
|
|
||||||
|
**Der Erzeuger ist neuer Bau-Code und damit eine neue Stelle, an der der Bau brechen kann.**
|
||||||
|
Dagegen hilft nur, ihn zu testen wie Produktionscode — deshalb steht er in der Prüfliste.
|
||||||
|
|
||||||
|
## Festlegungen, die sonst im Bau beliebig würden
|
||||||
|
|
||||||
|
**Die Regel ist der Adressat, nicht das Aussehen.** Wer beim Herausziehen unsicher ist, fragt
|
||||||
|
nicht „sieht das nach Oberfläche aus", sondern „wer liest das".
|
||||||
|
|
||||||
|
**Unvollständigkeit ist erlaubt, Unrichtigkeit nicht.** Das entscheidet jeden Zweifelsfall im
|
||||||
|
Erzeuger.
|
||||||
|
|
||||||
|
**Die Stelligkeit kommt aus der englischen Grundlage.** Eine Übersetzung kann einen Schlüssel
|
||||||
|
weglassen, aber nicht seine Platzhalter umdeuten.
|
||||||
@@ -1106,3 +1106,60 @@ Die Prüfung verwendet immer den **primären Bildschirm**. Sie stellt nicht die
|
|||||||
Die automatische Größenbegrenzung gilt derzeit außerdem nur für das Hauptfenster. Das Einstellungsfenster, das separate Cluster- und QSO-Monitorfenster sowie weitere Zusatzfenster verwenden weiterhin ihre jeweils gespeicherten Größen, ohne dieselbe zusätzliche Prüfung gegen den primären Bildschirm.
|
Die automatische Größenbegrenzung gilt derzeit außerdem nur für das Hauptfenster. Das Einstellungsfenster, das separate Cluster- und QSO-Monitorfenster sowie weitere Zusatzfenster verwenden weiterhin ihre jeweils gespeicherten Größen, ohne dieselbe zusätzliche Prüfung gegen den primären Bildschirm.
|
||||||
|
|
||||||
Im Klartext: Die Schutzfunktion verhindert vor allem, dass das zentrale Hauptfenster nach einem Wechsel auf einen kleineren Bildschirm unbenutzbar startet. Sie ist keine vollständige Verwaltung aller Fensterpositionen in einem wechselnden Mehrmonitor-Setup.
|
Im Klartext: Die Schutzfunktion verhindert vor allem, dass das zentrale Hauptfenster nach einem Wechsel auf einen kleineren Bildschirm unbenutzbar startet. Sie ist keine vollständige Verwaltung aller Fensterpositionen in einem wechselnden Mehrmonitor-Setup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sprache der Oberfläche (ab v1.50)
|
||||||
|
|
||||||
|
Die Oberfläche spricht Deutsch oder Englisch. Die Wahl sitzt im Einstellungsfenster im Reiter
|
||||||
|
**GUI**, im letzten Abschnitt **Language**, und gilt **pro Operatorprofil** — an einer
|
||||||
|
Mehrmann-Station können zwei Operateure zwei Sprachen haben.
|
||||||
|
|
||||||
|
Drei Werte stehen zur Wahl:
|
||||||
|
|
||||||
|
| Wert | Bedeutung |
|
||||||
|
|---|---|
|
||||||
|
| **Systemsprache** | die Sprache des Betriebssystems; ist das keine, für die Texte vorliegen, dann Englisch |
|
||||||
|
| **DE** | Deutsch |
|
||||||
|
| **EN** | Englisch |
|
||||||
|
|
||||||
|
**Ein Wechsel wirkt sofort in allen offenen Fenstern** — Hauptfenster, Einstellungen, Karte und
|
||||||
|
Monitor zugleich, ohne Neustart und ohne ein Fenster zu schließen. Dauerhaft wird die Wahl wie
|
||||||
|
jede andere Einstellung mit **Einstellungen speichern**.
|
||||||
|
|
||||||
|
### Was nicht übersetzt wird, und warum
|
||||||
|
|
||||||
|
Drei Sorten Text bleiben unverändert, in jeder Sprache:
|
||||||
|
|
||||||
|
- **Alles, was an den ON4KST-Server geht.** Protokollbefehle und Chat-Befehle wie `/CQ` oder
|
||||||
|
`/AWAY` würden übersetzt die Verbindung brechen oder vom Server nicht verstanden.
|
||||||
|
- **Alles, was an andere Funker geht.** Deine Bakentexte, Antworttexte, Textbausteine und
|
||||||
|
Kurztasten bleiben so, wie du sie eingetragen hast. Sie stehen in der gespeicherten
|
||||||
|
Konfiguration deines Profils; sie zu übersetzen würde bestehende Profile verändern.
|
||||||
|
- **Funkerkürzel in der Oberfläche.** QRA, QRG, QTF, QRB, Tropo, LOC, wkd, NOT QRV, LOS,
|
||||||
|
Fresnel, Sked — die sind international und lesen sich in beiden Sprachen gleich.
|
||||||
|
|
||||||
|
Drei Tests wachen darüber, und zwar an der Ausgabe statt an der Textdatei: die Protokollrahmen
|
||||||
|
werden unter drei Locales gebaut und verglichen, sämtliche Vorgabewerte der Konfiguration
|
||||||
|
werden über Reflexion verglichen, und kein Wert in einer Übersetzungsdatei darf ein
|
||||||
|
Protokollbefehl sein.
|
||||||
|
|
||||||
|
### Eine Sprache beitragen
|
||||||
|
|
||||||
|
Die Texte liegen als gewöhnliche Textdateien unter `app-desktop/src/main/i18n/`, eine je
|
||||||
|
Sprache. Wer eine weitere beitragen will, kopiert `strings_en.properties` nach
|
||||||
|
`strings_<code>.properties` und übersetzt die Werte rechts vom Gleichheitszeichen. Kein Kotlin,
|
||||||
|
kein Gradle.
|
||||||
|
|
||||||
|
**Eine unvollständige Übersetzung ist willkommen** — ein nicht übersetzter Schlüssel erscheint
|
||||||
|
englisch statt zu fehlen. Eine *fehlerhafte* bricht dagegen den Bau ab: ein Schlüssel, den die
|
||||||
|
englische Grundlage nicht kennt, oder ein Platzhalter wie `{0}`, der in der Übersetzung fehlt
|
||||||
|
und damit stillschweigend Daten verschluckt.
|
||||||
|
|
||||||
|
`./gradlew :app-desktop:classes` baut und meldet jeden Fehler mit dem Dateinamen. Was noch
|
||||||
|
fehlt, schreibt der Bau als Arbeitsblatt nach
|
||||||
|
`app-desktop/build/generated/i18n/todo/strings_<code>.todo.properties` — die fehlenden
|
||||||
|
Schlüssel mit dem englischen Text als Wert, zum Ausfüllen.
|
||||||
|
|
||||||
|
Deutsch wird durch einen Test auf Vollständigkeit gehalten, weil es ausgeliefert wird. Eine
|
||||||
|
beigetragene Sprache nicht.
|
||||||
@@ -1085,3 +1085,57 @@ The check always uses the **primary screen**. It does not restore the previous p
|
|||||||
The automatic size restriction currently applies to the main window only. The settings window, the separate cluster and QSO monitor window and other auxiliary windows continue to use their stored sizes without the same additional check against the primary screen.
|
The automatic size restriction currently applies to the main window only. The settings window, the separate cluster and QSO monitor window and other auxiliary windows continue to use their stored sizes without the same additional check against the primary screen.
|
||||||
|
|
||||||
In plain terms: the protection mainly prevents the central main window from becoming unusable after moving to a smaller display. It is not a complete window-position manager for a changing multi-monitor setup.
|
In plain terms: the protection mainly prevents the central main window from becoming unusable after moving to a smaller display. It is not a complete window-position manager for a changing multi-monitor setup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Interface Language (from v1.50)
|
||||||
|
|
||||||
|
The interface speaks English or German. The choice sits in the settings window on the **GUI**
|
||||||
|
tab, in the last section **Language**, and applies **per operator profile** — at a multi
|
||||||
|
operator station two operators can have two languages.
|
||||||
|
|
||||||
|
Three values are offered:
|
||||||
|
|
||||||
|
| Value | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| **System language** | the operating system's language; English when no texts exist for it |
|
||||||
|
| **DE** | German |
|
||||||
|
| **EN** | English |
|
||||||
|
|
||||||
|
**A change applies at once in every open window** — main window, settings, map and monitor
|
||||||
|
together, with no restart and without closing anything. The choice is kept permanently the
|
||||||
|
same way as every other setting, with **Save settings**.
|
||||||
|
|
||||||
|
### What is not translated, and why
|
||||||
|
|
||||||
|
Three kinds of text stay as they are, in every language:
|
||||||
|
|
||||||
|
- **Anything that goes to the ON4KST server.** Protocol frames and chat commands such as
|
||||||
|
`/CQ` or `/AWAY` would break the connection or not be understood if they were translated.
|
||||||
|
- **Anything that goes to other radio amateurs.** Your beacon texts, auto answers, snippets
|
||||||
|
and shortcuts stay exactly as you entered them. They live in your profile's stored
|
||||||
|
configuration; translating them would alter existing profiles.
|
||||||
|
- **Amateur radio shorthand in the interface.** QRA, QRG, QTF, QRB, Tropo, LOC, wkd, NOT QRV,
|
||||||
|
LOS, Fresnel, Sked — these are international and read the same either way.
|
||||||
|
|
||||||
|
Three tests guard this, and they check the output rather than the text files: the protocol
|
||||||
|
frames are built under three locales and compared, every configuration default is compared by
|
||||||
|
reflection, and no value in a translation file may be a protocol command.
|
||||||
|
|
||||||
|
### Contributing a language
|
||||||
|
|
||||||
|
The texts are ordinary text files under `app-desktop/src/main/i18n/`, one per language. To
|
||||||
|
contribute another, copy `strings_en.properties` to `strings_<code>.properties` and translate
|
||||||
|
the values to the right of the equals sign. No Kotlin, no Gradle.
|
||||||
|
|
||||||
|
**A partial translation is welcome** — an untranslated key appears in English rather than
|
||||||
|
going missing. An *incorrect* one fails the build instead: a key the English base does not
|
||||||
|
know, or a placeholder such as `{0}` missing from the translation, which would silently drop
|
||||||
|
whatever it was carrying.
|
||||||
|
|
||||||
|
`./gradlew :app-desktop:classes` builds and reports every mistake by file name. What is still
|
||||||
|
missing is written as a worksheet to
|
||||||
|
`app-desktop/build/generated/i18n/todo/strings_<code>.todo.properties` — the missing keys with
|
||||||
|
the English text as the value, ready to fill in.
|
||||||
|
|
||||||
|
German is held complete by a test because it is shipped. A contributed language is not.
|
||||||
Reference in new issue
Block a user