- Wir verwenden notwendige Cookies, damit die Website zuverlässig funktioniert. Darüber hinaus würden wir gern optionale
- Analyse-Cookies von Hotjar einsetzen, um das Nutzungserlebnis zu verbessern. Sie entscheiden, ob Sie diese zulassen –
- alle Details finden Sie in unserer
- Datenschutzerklärung.
+ We use essential cookies to keep the site reliable. We would also like to enable optional Hotjar analytics cookies to
+ improve the experience. You decide whether to allow them—full details are available in our
+ privacy policy.
-
Notwendige Cookies
-
Speichern Ihre Auswahl und sorgen für sichere Anmeldung sowie eine stabile Grundfunktionalität.
+
Necessary cookies
+
Store your preferences and keep sign-in secure while ensuring the core experience remains stable.
- Immer aktiv
+ Always active
-
Analyse (Hotjar)
-
Hilft uns, Nutzungsverhalten anonym auszuwerten und unsere Inhalte gezielt zu verbessern.
+
Analytics (Hotjar)
+
Helps us analyse anonymised usage patterns and improve our content in a focused way.
- Optionales Tracking wird erst nach Ihrer Zustimmung geladen. Sie können Ihre Entscheidung jederzeit über „Cookie-Einstellungen“
- unten links widerrufen.
+ Optional tracking loads only after you agree. You can change your decision any time via "Cookie settings" in the lower left.
- Browser-TTS (Web Speech)
- Läuft offline, startet schneller und spart unsere API-Anfragen.
+ Browser TTS (Web Speech)
+ Runs offline, starts faster, and saves our API calls.
- Audio Challenge
- Blendet Zieltext & Infos, bis du sie manuell einblendest.
+ Audio challenge
+ Hides target text & info until you reveal it manually.
- Funk-Level (1..5)
+ Radio level (1..5)
@@ -383,22 +383,22 @@
- Probe-TTS
+ Test TTS
mdi-volume-high
- Probe
+ Test
mdi-stop
- Stopp
+ Stop
- Schließen
+ Close
@@ -668,7 +668,7 @@ const airportsData: AirportData[] = [
},
{
icao: 'EDDM',
- name: 'München',
+ name: 'Munich',
city: 'Munich',
runways: ['26R', '26L', '08R', '08L'],
stands: ['211', '214', '302', 'N16', 'H45'],
@@ -1014,17 +1014,17 @@ const modules = ref([
lessons: [
{
id: 'icao-alphabet',
- title: 'ICAO Alphabet & Zahlen',
- desc: 'Buchstaben und Ziffern sauber buchstabieren',
+ title: 'ICAO Alphabet & Numbers',
+ desc: 'Spell letters and digits clearly',
keywords: ['Alphabet', 'Numbers', 'Normalize'],
hints: [
- 'Buchstabiere jeden Buchstaben mit dem ICAO-Namen (z. B. Delta, Lima, Hotel).',
- 'Ziffern im Funk: tree, fower, fife, niner.'
+ 'Spell each letter with its ICAO name (e.g. Delta, Lima, Hotel).',
+ 'Use ATC numbers: tree, fower, fife, niner.'
],
fields: [
{
key: 'letters',
- label: 'Buchstaben',
+ label: 'Letters',
expected: scenario => scenario.callsignNato,
placeholder: 'Delta Lima Hotel',
width: 'xl',
@@ -1032,7 +1032,7 @@ const modules = ref([
},
{
key: 'digits',
- label: 'Zahlen',
+ label: 'Numbers',
expected: scenario => scenario.flightNumberWords,
placeholder: 'one two three',
width: 'lg',
@@ -1040,7 +1040,7 @@ const modules = ref([
}
],
readback: [
- { type: 'text', text: 'Rufezeichen: ' },
+ { type: 'text', text: 'Call sign: ' },
{ type: 'field', key: 'letters', width: 'lg' },
{ type: 'text', text: ' · ' },
{ type: 'field', key: 'digits', width: 'md' }
@@ -1048,21 +1048,21 @@ const modules = ref([
defaultFrequency: 'DEL',
phrase: scenario => `${scenario.callsignNato} ${scenario.flightNumberWords}`,
info: scenario => [
- `Kennzeichen: ${scenario.callsign}`,
- `Airline Call: ${scenario.radioCall}`,
- `ICAO: ${scenario.callsignNato}`,
- `Flightnummer gesprochen: ${scenario.flightNumberWords}`
+ `Callsign: ${scenario.callsign}`,
+ `Radio call: ${scenario.radioCall}`,
+ `ICAO spelling: ${scenario.callsignNato}`,
+ `Flight number spoken: ${scenario.flightNumberWords}`
],
generate: createBaseScenario
},
{
id: 'atis',
- title: 'ATIS verstehen',
- desc: 'Kennung und Kerndaten aus der ATIS ziehen',
- keywords: ['ATIS', 'Wetter'],
+ title: 'Understand the ATIS',
+ desc: 'Extract the identifier and key data from the ATIS',
+ keywords: ['ATIS', 'Weather'],
hints: [
- 'ATIS-Kennung als einzelnen Buchstaben merken.',
- 'Reihenfolge: Runway – Wind – Sicht – Temperatur – Taupunkt – QNH.'
+ 'Remember the ATIS identifier as a single letter.',
+ 'Order: runway – wind – visibility – temperature – dew point – QNH.'
],
fields: [
{
@@ -1074,7 +1074,7 @@ const modules = ref([
scenario.atisCode.toLowerCase(),
`Information ${scenario.atisCode}`
],
- placeholder: 'Buchstabe',
+ placeholder: 'Letter',
width: 'xs',
threshold: 0.9
},
@@ -1098,21 +1098,21 @@ const modules = ref([
},
{
key: 'atis-visibility',
- label: 'Sicht',
+ label: 'Visibility',
expected: scenario => scenario.atisSummary.visibility,
alternatives: scenario => [scenario.visibility, scenario.visibilityWords],
width: 'sm'
},
{
key: 'atis-temp',
- label: 'Temperatur',
+ label: 'Temperature',
expected: scenario => scenario.atisSummary.temperature,
alternatives: scenario => [scenario.temperatureWords],
width: 'sm'
},
{
key: 'atis-dew',
- label: 'Taupunkt',
+ label: 'Dew point',
expected: scenario => scenario.atisSummary.dewpoint,
alternatives: scenario => [scenario.dewpointWords],
width: 'sm'
@@ -1144,19 +1144,19 @@ const modules = ref([
defaultFrequency: 'ATIS',
phrase: scenario => scenario.atisText,
info: () => [
- 'Notiere Kennung, Runway, Wind, Sichtweite, Temperatur, Taupunkt und QNH.',
- 'Sicht: vier Ziffern oder 9999 für ≥10 km · QNH als vierstellige Zahl.'
+ 'Note the identifier, runway, wind, visibility, temperature, dew point, and QNH.',
+ 'Visibility: four digits or 9999 for ≥10 km; QNH as a four-digit value.'
],
generate: createBaseScenario
},
{
id: 'metar',
- title: 'METAR entschlüsseln',
- desc: 'Rohe METAR-Werte extrahieren',
+ title: 'Decode the METAR',
+ desc: 'Extract the raw METAR values',
keywords: ['METAR', 'Weather'],
hints: [
- 'METAR blockweise lesen: Wind – Sicht – Wolken – Temperatur – QNH.',
- 'Der Temperaturblock hat die Form 18/10, Minuswerte beginnen mit M.'
+ 'Read the METAR in blocks: wind – visibility – clouds – temperature – QNH.',
+ 'The temperature block looks like 18/10; negative values start with M.'
],
fields: [
{
@@ -1172,7 +1172,7 @@ const modules = ref([
},
{
key: 'metar-vis',
- label: 'Sicht',
+ label: 'Visibility',
expected: scenario => scenario.metarSegments.visibility,
alternatives: scenario => [scenario.visibility],
placeholder: '9999',
@@ -1199,9 +1199,9 @@ const modules = ref([
readback: [
{ type: 'text', text: 'Wind ' },
{ type: 'field', key: 'metar-wind', width: 'md' },
- { type: 'text', text: ', Sicht ' },
+ { type: 'text', text: ', visibility ' },
{ type: 'field', key: 'metar-vis', width: 'sm' },
- { type: 'text', text: ', Temperatur ' },
+ { type: 'text', text: ', temperature ' },
{ type: 'field', key: 'metar-temp', width: 'md' },
{ type: 'text', text: ', QNH ' },
{ type: 'field', key: 'metar-qnh', width: 'sm' }
@@ -1210,18 +1210,18 @@ const modules = ref([
phrase: scenario => scenario.metar,
info: scenario => [
`METAR: ${scenario.metar}`,
- `Interpretation: Wind ${scenario.metarSegments.wind}, Sicht ${scenario.visibilityWords}, Temperatur ${scenario.temperature}°C, QNH ${scenario.qnh}`
+ `Interpretation: Wind ${scenario.metarSegments.wind}, visibility ${scenario.visibilityWords}, temperature ${scenario.temperature}°C, QNH ${scenario.qnh}`
],
generate: createBaseScenario
},
{
id: 'radio-check',
title: 'Radio Check',
- desc: 'Lesbarkeit melden',
+ desc: 'Report readability',
keywords: ['Ground', 'Comms'],
hints: [
- 'Antwort besteht aus "Readability" plus Zahl.',
- 'Beende den Check immer mit deinem Callsign.'
+ 'Reply with "Readability" followed by the number.',
+ 'Always finish the check with your call sign.'
],
fields: [
{
@@ -1238,7 +1238,7 @@ const modules = ref([
},
{
key: 'rc-readability',
- label: 'Lesbarkeit',
+ label: 'Readability',
expected: scenario => scenario.readabilityWord,
alternatives: scenario => [scenario.readability.toString(), scenario.readabilityWord],
placeholder: 'five',
@@ -1254,8 +1254,8 @@ const modules = ref([
defaultFrequency: 'GND',
phrase: scenario => `${scenario.airport.name} Ground, ${scenario.radioCall}, radio check on ${scenario.groundFreq}.`,
info: scenario => [
- `Frequenz: ${scenario.groundFreq} (${scenario.frequencyWords.GND})`,
- `Erwartete Antwort: Readability ${scenario.readability} (${scenario.readabilityWord})`
+ `Frequency: ${scenario.groundFreq} (${scenario.frequencyWords.GND})`,
+ `Expected response: Readability ${scenario.readability} (${scenario.readabilityWord})`
],
generate: createBaseScenario
}
@@ -1264,17 +1264,17 @@ const modules = ref([
{
id: 'arc',
title: 'ARC Decision Tree',
- subtitle: 'Vom Clearance Call bis Departure',
+ subtitle: 'From clearance call to departure',
art: gradientArt(['#f97316', '#fb923c', '#0f172a']),
lessons: [
{
id: 'clearance-contact',
- title: 'Delivery: Erstkontakt',
- desc: 'Clearance Delivery kontaktieren',
+ title: 'Delivery: Initial contact',
+ desc: 'Contact clearance delivery',
keywords: ['Delivery', 'Clearance'],
hints: [
- 'Reihenfolge: Einheit, Callsign, ATIS, Ziel, Stand, Request.',
- 'ATIS als einzelner Buchstabe sprechen.'
+ 'Order: unit, call sign, ATIS, destination, stand, request.',
+ 'Say the ATIS as a single letter.'
],
fields: [
{
@@ -1298,7 +1298,7 @@ const modules = ref([
},
{
key: 'cd-stand',
- label: 'Stand',
+ label: 'Stand/Gate',
expected: scenario => scenario.stand,
width: 'sm'
}
@@ -1318,20 +1318,20 @@ const modules = ref([
defaultFrequency: 'DEL',
phrase: scenario => `${scenario.airport.city} Delivery, ${scenario.radioCall}, information ${scenario.atisCode}, IFR to ${scenario.destination.city}, stand ${scenario.stand}, request clearance.`,
info: scenario => [
- `ATIS ${scenario.atisCode}`,
- `Ziel: ${scenario.destination.city} (${scenario.destination.icao})`,
- `Stand: ${scenario.stand}`
+ `ATIS: ${scenario.atisCode}`,
+ `Destination: ${scenario.destination.city} (${scenario.destination.icao})`,
+ `Stand/Gate: ${scenario.stand}`
],
generate: createBaseScenario
},
{
id: 'clearance-readback',
title: 'Clearance Readback',
- desc: 'Freigabe komplett zurücklesen',
+ desc: 'Read back the clearance in full',
keywords: ['Delivery', 'Readback'],
hints: [
- 'Reihenfolge merken: Ziel – SID – Runway – Altitude – Squawk.',
- 'Zahlen bei Altitude und Squawk sauber ausschreiben.'
+ 'Remember the order: destination – SID – runway – altitude – squawk.',
+ 'Speak altitude and squawk digits clearly.'
],
fields: [
{
@@ -1393,11 +1393,11 @@ const modules = ref([
{
id: 'pushback',
title: 'Push & Start Readback',
- desc: 'Pushback-Freigabe bestätigen',
+ desc: 'Acknowledge the pushback clearance',
keywords: ['Ground', 'Pushback'],
hints: [
- 'Bahnrichtung und QNH übernehmen, Callsign ans Ende.',
- 'QNH darf als Zahl oder "QNH xxxx" stehen.'
+ 'Repeat the runway direction and QNH, call sign at the end.',
+ 'QNH may be just the number or "QNH xxxx".'
],
fields: [
{
@@ -1437,7 +1437,7 @@ const modules = ref([
defaultFrequency: 'GND',
phrase: scenario => `${scenario.radioCall}, push and start approved, facing runway ${scenario.runway}. QNH ${scenario.qnh}.`,
info: scenario => [
- `Stand: ${scenario.stand}`,
+ `Stand/Gate: ${scenario.stand}`,
`Ground: ${scenario.groundFreq} (${scenario.frequencyWords.GND})`
],
generate: createBaseScenario
@@ -1445,11 +1445,11 @@ const modules = ref([
{
id: 'taxi',
title: 'Taxi Readback',
- desc: 'Taxi-Freigabe mit Hold Short',
+ desc: 'Read back the taxi clearance with hold short',
keywords: ['Ground', 'Taxi'],
hints: [
- 'Route wie gehört wiederholen, inklusive Hold Short.',
- 'Am Ende wieder Callsign.'
+ 'Repeat the route exactly as received, including the hold short.',
+ 'Finish with the call sign.'
],
fields: [
{
@@ -1498,19 +1498,19 @@ const modules = ref([
defaultFrequency: 'GND',
phrase: scenario => `${scenario.radioCall}, taxi to runway ${scenario.runway} via ${scenario.taxiRoute}, hold short runway ${scenario.runway}.`,
info: scenario => [
- `Taxi-Route: ${scenario.taxiRoute}`,
- `Hold Short: ${scenario.runway}`
+ `Taxi route: ${scenario.taxiRoute}`,
+ `Hold short: ${scenario.runway}`
],
generate: createBaseScenario
},
{
id: 'lineup',
title: 'Line-up Clearance',
- desc: 'Aufrollen und warten bestätigen',
+ desc: 'Acknowledge line up and wait',
keywords: ['Tower', 'Line Up'],
hints: [
- 'Runway wiederholen, dann "line up and wait".',
- 'Callsign am Ende setzen.'
+ 'Repeat the runway, then say "line up and wait".',
+ 'Place the call sign at the end.'
],
fields: [
{
@@ -1549,11 +1549,11 @@ const modules = ref([
{
id: 'takeoff',
title: 'Takeoff Clearance',
- desc: 'Takeoff-Freigabe bestätigen',
+ desc: 'Acknowledge the takeoff clearance',
keywords: ['Tower', 'Departure'],
hints: [
- 'Reihenfolge: Runway – cleared for takeoff – Callsign.',
- 'Windangabe kann entfallen, wenn nicht gefordert.'
+ 'Order: runway – cleared for takeoff – call sign.',
+ 'Wind information can be omitted if it was not given.'
],
fields: [
{
@@ -1592,11 +1592,11 @@ const modules = ref([
{
id: 'departure-handoff',
title: 'Departure Handoff',
- desc: 'Auf Departure wechseln',
+ desc: 'Switch to departure',
keywords: ['Departure', 'Handoff'],
hints: [
- 'Frequenz exakt wiederholen, optional als Zahl oder gesprochen.',
- 'Callsign am Ende anhängen.'
+ 'Repeat the frequency exactly, either as digits or spoken form.',
+ 'Append the call sign at the end.'
],
fields: [
{
@@ -1632,18 +1632,18 @@ const modules = ref([
phrase: scenario => `${scenario.radioCall}, contact departure ${scenario.departureFreq}.`,
info: scenario => [
`Departure: ${scenario.departureFreq} (${scenario.frequencyWords.DEP})`,
- `Tower Handoff nach Start.`
+ `Tower handoff after departure.`
],
generate: createBaseScenario
},
{
id: 'departure-checkin',
title: 'Departure Check-in',
- desc: 'Erstmeldung bei Departure',
+ desc: 'Initial call to departure',
keywords: ['Departure', 'Check-in'],
hints: [
- 'Einheit ansprechen, dann Callsign nennen.',
- 'Altitude und SID exakt wie gehört wiederholen.'
+ 'Address the unit first, then state the call sign.',
+ 'Repeat the altitude and SID exactly as received.'
],
fields: [
{
@@ -1694,11 +1694,11 @@ const modules = ref([
{
id: 'climb-instruction',
title: 'Climb & Direct',
- desc: 'Steigauftrag komplett zurücklesen',
+ desc: 'Read back the climb instruction in full',
keywords: ['Departure', 'Climb'],
hints: [
- 'Mit "Climb" beginnen, dann Höhe und gegebenenfalls Direct.',
- 'Callsign am Ende wiederholen.'
+ 'Start with "Climb", then the altitude and any direct clearance.',
+ 'Repeat the call sign at the end.'
],
fields: [
{
@@ -1749,11 +1749,11 @@ const modules = ref([
{
id: 'center-handoff',
title: 'Center Handoff',
- desc: 'Übergabe an Center bestätigen',
+ desc: 'Acknowledge the handoff to center',
keywords: ['Center', 'Handoff'],
hints: [
- 'Frequenz exakt wiedergeben (mit oder ohne Dezimalpunkt).',
- 'Callsign am Ende platzieren.'
+ 'Repeat the frequency exactly (with or without the decimal point).',
+ 'Place the call sign at the end.'
],
fields: [
{
@@ -1789,7 +1789,7 @@ const modules = ref([
phrase: scenario => `${scenario.radioCall}, contact center ${scenario.centerFreq}.`,
info: scenario => [
`Center: ${scenario.centerFreq} (${scenario.frequencyWords.CTR})`,
- `Nächste Einheit: Center`
+ `Next unit: Center`
],
generate: createBaseScenario
}
@@ -2003,7 +2003,7 @@ function blankStateClass(key: string): string {
}
function fieldLabel(key: string): string {
- return fieldMap.value[key]?.label ?? 'Feld'
+ return fieldMap.value[key]?.label ?? 'Field'
}
function fieldPlaceholder(key: string): string {
@@ -2185,9 +2185,9 @@ function avgScore(modId: string) {
}
const dailies = ref([
- { id: 'd1', title: '3 Readbacks ≥80%', sub: 'Belohnung: +50 XP', reward: 50 },
- { id: 'd2', title: '1 Modul starten', sub: 'Belohnung: +20 XP', reward: 20 },
- { id: 'd3', title: 'Zielphrase abspielen', sub: 'Belohnung: +10 XP', reward: 10 }
+ { id: 'd1', title: '3 readbacks ≥80%', sub: 'Reward: +50 XP', reward: 50 },
+ { id: 'd2', title: 'Start 1 module', sub: 'Reward: +20 XP', reward: 20 },
+ { id: 'd3', title: 'Play the target phrase', sub: 'Reward: +10 XP', reward: 10 }
])
function startDaily(daily: { id: string; reward: number; title: string }) {
diff --git a/app/pages/pm.vue b/app/pages/pm.vue
index fc1ddef..7a136cd 100644
--- a/app/pages/pm.vue
+++ b/app/pages/pm.vue
@@ -22,7 +22,7 @@
VATSIM Integration
-
Gib deine CID ein um deine gefilterten Flugpläne zu laden
+
Enter your CID to load your filtered flight plans.
@@ -801,16 +801,15 @@
- Übertragung als fehlerhaft markieren
+ Mark transmission as faulty
- Hinterlasse optional eine kurze Beschreibung, damit das Dev Team die
- Übertragung nachvollziehen kann.
+ Optionally leave a short note so the dev team can follow up on the transmission.
- Abbrechen
+ Cancel
- Markierung entfernen
+ Remove flag
- Speichern
+ Save
@@ -844,7 +843,7 @@
{
flightPlans.value = response.slice(0, 10)
currentScreen.value = 'flightselect'
} else {
- error.value = 'Keine Flugpläne für diese VATSIM ID gefunden'
+ error.value = 'No flight plans found for this VATSIM ID'
}
} catch (err) {
console.error('Error loading flight plans:', err)
- error.value = 'Fehler beim Laden der Flugpläne. Bitte VATSIM ID prüfen.'
+ error.value = 'Error loading flight plans. Please check the VATSIM ID.'
} finally {
loading.value = false
}
@@ -1947,7 +1946,7 @@ const playAtisBroadcast = async () => {
}
if (!content) {
- setLastTransmission('ATIS: Keine aktuellen Informationen verfügbar')
+ setLastTransmission('ATIS: No current information available')
return
}
diff --git a/shared/data/learnModules.ts b/shared/data/learnModules.ts
index 42c15f2..1d75745 100644
--- a/shared/data/learnModules.ts
+++ b/shared/data/learnModules.ts
@@ -5,7 +5,7 @@ export type Lesson = { id: string; title: string; desc: string; target: string;
export type ModuleDef = { id: string; title: string; subtitle: string; art: string; lessons: Lesson[] }
const modules = ref([
- // NEW: ICAO Kapitel
+ // New: ICAO chapter
{
id: 'icao',
title: 'ICAO Alphabet',
@@ -15,33 +15,33 @@ const modules = ref([
{
id: 'alpha',
title: 'Alphabet A–M',
- desc: 'Alpha bis Mike sprechen.',
+ desc: 'Say Alpha through Mike.',
target: 'Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel India Juliett Kilo Lima Mike.',
- hints: ['konstant sprechen', 'deutlich trennen'],
+ hints: ['Keep a steady pace', 'Separate each word clearly'],
keywords: ['Alpha', 'Bravo', 'Charlie', 'Delta', 'Echo', 'Foxtrot', 'Golf', 'Hotel', 'India', 'Juliett', 'Kilo', 'Lima', 'Mike']
},
{
id: 'alpha2',
title: 'Alphabet N–Z',
- desc: 'November bis Zulu.',
+ desc: 'November through Zulu.',
target: 'November Oscar Papa Quebec Romeo Sierra Tango Uniform Victor Whiskey X-ray Yankee Zulu.',
- hints: ['X-ray mit hyphen', 'Juliett mit zwei t'],
+ hints: ['Spell X-ray with a hyphen', "Juliett has two t's"],
keywords: ['November', 'Oscar', 'Papa', 'Quebec', 'Romeo', 'Sierra', 'Tango', 'Uniform', 'Victor', 'Whiskey', 'X-ray', 'Yankee', 'Zulu']
},
{
id: 'numbers',
- title: 'Zahlen',
- desc: 'ICAO-Nummernlesen.',
+ title: 'Numbers',
+ desc: 'Read ICAO numbers.',
target: 'Tree Fower Fife Six Seven Eight Niner Zero.',
hints: ['Nine → Niner', 'Three → Tree', 'Four → Fower', 'Five → Fife'],
keywords: ['Tree', 'Fower', 'Fife', 'Niner']
},
{
id: 'callsign-icao',
- title: 'Callsign Buchstabieren',
- desc: 'Beispiel-Callsign.',
+ title: 'Spell the call sign',
+ desc: 'Sample call sign.',
target: 'DLH one two three, Lufthansa one two three.',
- hints: ['DLH → Lufthansa', 'Nummern ICAO'],
+ hints: ['DLH → Lufthansa', 'Use ICAO numbers'],
keywords: ['Lufthansa', 'DLH', 'one', 'two', 'three']
}
]
@@ -49,19 +49,19 @@ const modules = ref([
{
id: 'basics',
title: 'Basics',
- subtitle: 'Callsign · Struktur · Zahlen',
+ subtitle: 'Call sign · Structure · Numbers',
art: 'https://images.unsplash.com/photo-1541392822270-85b2ff6c4577?q=80&w=1600&auto=format&fit=crop',
lessons: [
{
id: 'checkin',
title: 'Check-in',
- desc: 'Erster Call korrekt.',
+ desc: 'Make the first call correctly.',
target: 'Frankfurt Ground, Lufthansa one two three at stand A12, request taxi.',
hints: ['Station • Callsign • Position • Intent'],
keywords: ['Frankfurt Ground', 'Lufthansa', 'stand', 'request taxi']
},
{
- id: 'readback', title: 'Short Readback', desc: 'Kurz bestätigen.',
+ id: 'readback', title: 'Short Readback', desc: 'Give a short acknowledgement.',
target: 'Lufthansa one two three, roger.',
hints: ['Callsign + roger/affirm'], keywords: ['roger', 'affirm']
}
@@ -79,7 +79,7 @@ const modules = ref([
hints: ['Taxi to runway • via • hold short'], keywords: ['taxi to runway', 'via', 'hold short']
},
{
- id: 'handoff', title: 'Handoff', desc: 'Frequenzwechsel.',
+ id: 'handoff', title: 'Handoff', desc: 'Frequency change.',
target: 'Contact Tower on one one niner decimal five, Lufthansa one two three.',
hints: ['Contact Tower on … • decimal'], keywords: ['Contact Tower', 'decimal']
}
@@ -92,7 +92,7 @@ const modules = ref([
art: 'https://images.unsplash.com/photo-1494412685616-a5d310fbb07d?q=80&w=1600&auto=format&fit=crop',
lessons: [
{
- id: 'lineup', title: 'Line up', desc: 'Aufrollen und warten.',
+ id: 'lineup', title: 'Line up', desc: 'Line up and wait.',
target: 'Lufthansa one two three, line up and wait runway two five.',
hints: ['line up and wait'], keywords: ['line up and wait']
}
@@ -105,7 +105,7 @@ const modules = ref([
art: 'https://images.unsplash.com/photo-1542089363-07b2d92aacc3?q=80&w=1600&auto=format&fit=crop',
lessons: [
{
- id: 'vacate', title: 'Vacate', desc: 'Verlasse Bahn, melde frei.',
+ id: 'vacate', title: 'Vacate', desc: 'Exit the runway and report clear.',
target: 'Lufthansa one two three, vacated runway two five via A six.',
hints: ['vacated runway • via taxiway'], keywords: ['vacated', 'runway']
}
@@ -118,7 +118,7 @@ const modules = ref([
art: 'https://images.unsplash.com/photo-1508264769638-658b34d79f6e?q=80&w=1600&auto=format&fit=crop',
lessons: [
{
- id: 'checkin', title: 'IFR Check-in', desc: 'Erster Online-Call.',
+ id: 'checkin', title: 'IFR Check-in', desc: 'First online call.',
target: 'Frankfurt Ground, Lufthansa one two three, A320 at stand A12, IFR to Munich, information Bravo, request clearance.',
hints: ['IFR/VFR • ATIS Info • Request'], keywords: ['IFR', 'information', 'request clearance']
}
diff --git a/shared/utils/communicationsEngine.ts b/shared/utils/communicationsEngine.ts
index c1eb26b..2234135 100644
--- a/shared/utils/communicationsEngine.ts
+++ b/shared/utils/communicationsEngine.ts
@@ -1,8 +1,8 @@
-// composables/communicationsEngine.ts
+// communicationsEngine composable
import { ref, computed, readonly } from 'vue'
import atcDecisionTree from "../data/atcDecisionTree";
-// --- DecisionTree-Types (aus ~/data/atcDecisionTree.json abgeleitet) ---
+// --- DecisionTree types (derived from ~/data/atcDecisionTree.json) ---
type Role = 'pilot' | 'atc' | 'system'
type Phase =
| 'Preflight' | 'Clearance' | 'PushStart' | 'TaxiOut' | 'Departure'
@@ -31,8 +31,8 @@ interface DTState {
auto?: 'check_readback' | 'monitor' | 'end' | 'pop_stack_or_route_by_intent'
readback_required?: string[]
actions?: (DTActionSet | string)[]
- frequency?: string // Für einfachere Freq-Zugriffe
- frequencyName?: string // Name der Frequenz (DEL, GND, TWR, etc.)
+ frequency?: string // Shortcut for accessing frequencies
+ frequencyName?: string // Frequency label (DEL, GND, TWR, etc.)
}
interface DecisionTree {
@@ -47,8 +47,8 @@ interface DecisionTree {
emergency_active: boolean
current_unit: string
stack: string[]
- off_schema_count: number // Zähler für Off-Schema Antworten
- radio_checks_done: number // Zähler für Radio Checks
+ off_schema_count: number // Counter for off-schema responses
+ radio_checks_done: number // Counter for radio checks
}
policies: {
timeouts: {
@@ -67,7 +67,7 @@ interface DecisionTree {
states: Record
}
-// Flight Phases und Communication Steps für bessere Integration
+// Flight phases and communication steps for better integration
export const FLIGHT_PHASES = [
{ id: 'clearance', name: 'Clearance Delivery', frequency: '121.900', action: 'Request IFR clearance' },
{ id: 'ground', name: 'Ground Control', frequency: '121.700', action: 'Request pushback and taxi' },
@@ -90,7 +90,7 @@ export const COMMUNICATION_STEPS = [
atc: '{callsign}, cleared to {dest} via {sid} departure, runway {runway}, climb {initial_altitude_ft} feet, squawk {squawk}.',
pilotResponse: '{callsign} cleared {dest} via {sid}, runway {runway}, climb {initial_altitude_ft}, squawk {squawk}.'
}
- // Weitere Steps können hier definiert werden
+ // Additional steps can be defined here
]
type FrequencyVariableKey = 'atis_freq'
@@ -101,7 +101,7 @@ type FrequencyVariableKey = 'atis_freq'
| 'approach_freq'
| 'handoff_freq'
-// --- ATC Decision Tree laden ---
+// --- Load ATC decision tree ---
export interface FlightContext {
callsign: string
aircraft: string
@@ -140,7 +140,7 @@ export interface EngineLog {
offSchema?: boolean
}
-// NATO/ICAO Normalizer (gekürzt für bessere Performance)
+// NATO/ICAO normalizer trimmed for better performance
const NATO_PHONETIC: Record = {
A: 'Alpha', B: 'Bravo', C: 'Charlie', D: 'Delta', E: 'Echo', F: 'Foxtrot',
G: 'Golf', H: 'Hotel', I: 'India', J: 'Juliett', K: 'Kilo', L: 'Lima',
@@ -212,7 +212,7 @@ export default function useCommunicationsEngine() {
const currentStateId = ref(tree.value.start_state)
const communicationLog = ref([])
- // Flight Context für bessere Integration mit pm_alt.vue Stil
+ // Flight context used for pm_alt.vue integration
const flightContext = ref({
callsign: '',
aircraft: 'A320',
@@ -264,7 +264,7 @@ export default function useCommunicationsEngine() {
}
})
- // Current Step für pm_alt.vue Integration
+ // Current step for pm_alt.vue integration
const currentStep = computed(() => {
const phase = flightContext.value.phase
const step = COMMUNICATION_STEPS.find(s => s.phase === phase)
@@ -280,7 +280,7 @@ export default function useCommunicationsEngine() {
})
function initializeFlight(fpl: any) {
- // Variablen setzen
+ // Set variables
variables.value = {
...variables.value,
callsign: fpl.callsign || fpl.callsign,
@@ -313,7 +313,7 @@ export default function useCommunicationsEngine() {
time_now: new Date().toISOString()
}
- // Flight Context aktualisieren
+ // Update flight context
Object.assign(flightContext.value, {
...variables.value,
phase: 'clearance'
@@ -364,11 +364,11 @@ export default function useCommunicationsEngine() {
}
function applyLLMDecision(decision: any) {
- // Updates anwenden
+ // Apply updates
if (decision.updates) Object.assign(variables.value, decision.updates)
if (decision.flags) Object.assign(flags.value, decision.flags)
- // Off-Schema und Radio Check Tracking
+ // Track off-schema responses and radio checks
if (decision.off_schema) {
flags.value.off_schema_count++
console.log(`[Engine] Off-schema response #${flags.value.off_schema_count}`)
@@ -378,7 +378,7 @@ export default function useCommunicationsEngine() {
console.log(`[Engine] Radio check #${flags.value.radio_checks_done}`)
}
- // Controller Response
+ // Controller response
if (decision.controller_say_tpl) {
speak('atc', decision.controller_say_tpl, currentStateId.value, {
radioCheck: decision.radio_check,
@@ -386,7 +386,7 @@ export default function useCommunicationsEngine() {
})
}
- // State Transition (nur wenn nicht Radio Check)
+ // State transition (only when not a radio check)
if (!decision.radio_check) {
moveTo(decision.next_state)
}
@@ -396,7 +396,7 @@ export default function useCommunicationsEngine() {
// Log pilot input
speak('pilot', transcript, currentStateId.value)
- // Radio Check Detection (fallback falls LLM es nicht erkennt)
+ // Radio check detection (fallback if the LLM misses it)
const t = transcript.toLowerCase()
if (t.includes('radio check') || (t.includes('read') && t.includes('check'))) {
const callsign = variables.value.callsign || ''
@@ -417,7 +417,7 @@ export default function useCommunicationsEngine() {
return null
}
- return null // LLM soll entscheiden
+ return null // Let the LLM decide
}
function processUserTransmission(transcript: string): string | null {
@@ -437,7 +437,7 @@ export default function useCommunicationsEngine() {
currentStateId.value = stateId
const s = currentState.value
- // Actions ausführen
+ // Execute actions
for (const act of s.actions ?? []) {
if (typeof act === 'string') continue
if (act.if && !safeEvalBoolean(act.if)) continue
@@ -457,7 +457,7 @@ export default function useCommunicationsEngine() {
speak(s.role, s.say_tpl, s.id!)
}
- // Phase Update für Flight Context
+ // Update flight context phase
updateFlightPhase(s.phase)
}
@@ -599,7 +599,7 @@ export default function useCommunicationsEngine() {
communicationLog: readonly(communicationLog),
clearCommunicationLog: () => { communicationLog.value = [] },
- // pm_alt.vue Integration
+ // pm_alt.vue integration
flightContext: readonly(flightContext),
currentStep,