Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 8 additions & 41 deletions api-spec/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8485,13 +8485,6 @@ components:
Absent when the night requirement is not met, not applicable (no night
privilege) or waived (EASA IR holders under FCL.060(b)(2)(ii)).
example: "2026-10-02"
message:
type: string
deprecated: true
description: >-
DEPRECATED — English (German for UL) fallback text. Render messageKey with
messageParams instead; this field is removed once the web and iOS clients
have adopted the keys.
messageKey:
type: string
description: >-
Expand All @@ -8509,17 +8502,6 @@ components:
type: string
description: Stable key for the passenger currency rule, for client-side localisation of ruleDescription
example: "easa_pax"
passengerPrivilege:
type: object
description: Informational — whether the pilot meets additional requirements to carry passengers (LAPL 10h PIC, SPL 30 launches, etc.)
properties:
eligible:
type: boolean
description: Whether the pilot is eligible to carry passengers (based on total PIC hours/launches since license issue)
message:
type: string
description: Human-readable explanation of passenger privilege status
example: "Eligible to carry passengers (10h PIC completed)"

FlightReviewStatus:
type: object
Expand All @@ -8538,10 +8520,6 @@ components:
type: string
enum: [current, expiring, expired, unknown]
description: Flight review currency status
message:
type: string
deprecated: true
description: DEPRECATED — English fallback text. Render messageKey with messageParams instead.
messageKey:
type: string
description: Stable key identifying which statement is true, for client-side localisation. Catalogued in docs/CURRENCY_MESSAGES.md.
Expand Down Expand Up @@ -8603,12 +8581,6 @@ components:
today does not yet count toward this rating's revalidation, so
`requirements` is omitted and `status` stays `current` until the
window opens.
message:
type: string
deprecated: true
description: >-
DEPRECATED — English (German for UL) fallback text. Render messageKey with
messageParams instead.
messageKey:
type: string
description: Stable key identifying which statement is true, for client-side localisation. Catalogued in docs/CURRENCY_MESSAGES.md.
Expand Down Expand Up @@ -8696,10 +8668,6 @@ components:
met:
type: boolean
description: Whether the requirement is met
message:
type: string
deprecated: true
description: DEPRECATED — English fallback text. Render messageKey from launches/required/method instead.
messageKey:
type: string
description: Always `launch_method.progress`; the client renders launches/required/method.
Expand All @@ -8712,14 +8680,13 @@ components:
- current
- required
- unit
- messageKey
properties:
name:
type: string
deprecated: true
description: >-
DEPRECATED for regulatory requirements — English (German for UL) fallback for
nameKey. Still authoritative for custom currency rules, whose names are
author-supplied user data and carry no nameKey.
Author-supplied requirement name. Present only for custom currency rules,
where the name is user data; regulatory requirements carry nameKey instead.
nameKey:
type: string
description: >-
Expand All @@ -8740,10 +8707,6 @@ components:
unit:
type: string
description: Unit of measurement (e.g., "landings", "hours", "flights")
message:
type: string
deprecated: true
description: DEPRECATED — English fallback text. Render messageKey instead.
messageKey:
type: string
description: >-
Expand Down Expand Up @@ -9595,7 +9558,11 @@ components:
properties:
id:
type: string
description: Unique identifier (UUID for admin announcements, string key for hints)
description: >-
Unique identifier: a UUID for operator-authored announcements, a stable string
key for hints. A hint's id doubles as its client-side localisation key —
`message` is the English source text, to be used as a fallback. Operator
announcements carry author-written text in `message` and are never translated.
message:
type: string
example: Scheduled maintenance on Tuesday 8pm-10pm UTC
Expand Down
35 changes: 30 additions & 5 deletions docs/CURRENCY_MESSAGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,13 @@ that statement needs. The server keeps deciding *what* is true — that is the r
logic, and it must not be duplicated in TypeScript and Swift where it would drift. The
client decides *how to say it*.

`message` still carries the old English text during the deprecation window. It is
marked `deprecated` in the spec and will be removed once both clients render keys.
The `message` and `name` text fields are **gone**. Clients render keys; there is no
English fallback in the payload. The one exception is `CurrencyRequirement.name` on
custom currency rules, which is pilot-authored user data.

Server-sent notification emails render the same catalogue per locale in
`pkg/email/currency_messages.go`, chosen by the user's `PreferredLocale` — before this,
a German email template interpolated the API's English sentence.

## Deprecating a field

Expand All @@ -41,10 +46,25 @@ TypeScript hides this failure rather than avoiding it — `openapi-typescript` e
required property, but nothing validates at runtime, so the web client keeps working with
a type that has quietly become a lie.

## Removed fields

Clients still rendering these must drop the code; the API no longer sends them.

| Field | Replaced by |
| --- | --- |
| `message` on rating / passenger / flight-review / requirement / launch-method results | `messageKey` + `messageParams` |
| `name` on regulatory requirements | `nameKey` (custom-rule `name` stays — it is user data) |
| `passengerPrivilege` on `PassengerCurrency` | nothing — no code path ever populated it, so no response content changes |

`passengerPrivilege` is called out because the web client renders it today. The branch
was unreachable, so deleting that markup changes nothing a user sees, but it should go
with the rest — along with any test fixture that supplies the field, since such a
fixture keeps passing while covering a surface the API cannot produce.

## Rules

1. **Keys are plain strings, not an enum.** Adding a key is not a breaking change.
A client that meets an unrecognised key falls back to `message`.
A client that meets an unrecognised key should render its own generic wording.
2. **Params never repeat fields the object already carries.** `classType`,
`regulatoryAuthority`, `licenseType`, `expiryDate`, `current`/`required`/`unit`,
`launches`/`method`, `dayExpiresOn`/`nightExpiresOn` are all fields; the client
Expand All @@ -53,7 +73,10 @@ a type that has quietly become a lie.
all report `rating.recency_current`; which regulation to cite comes from
`ruleDescriptionKey`.
4. **User data is never keyed.** Custom currency rule requirement names are written by
the pilot and are returned in `name` with no `nameKey`. Render them as-is.
the pilot and are returned in `name` with no `nameKey`. Render them as-is. The same
split applies outside currency: the hints from `GET /announcements` carry stable string
`id`s that double as localisation keys, while operator-authored announcements carry
author-written text in `message` and are never translated.
5. `unknown` status is not self-explanatory — the key disambiguates
`rating.no_expiry_date` (the user must enter data), `rating.evaluation_failed`
(backend problem, retry) and `rating.ir_not_applicable` (structurally N/A).
Expand Down Expand Up @@ -136,4 +159,6 @@ render them yourself. See [DOMAIN.md](./DOMAIN.md#passenger-currency-expiry-daye
`TestEveryRatingResultCarriesAKey` fails on any emitted key not in the catalogue.
3. Add a row here.
4. Add the string to `ninerlog-frontend/src/i18n/locales/{en,de}/currency.json` under
`messages.` and to the iOS catalogue. Until then it renders its English fallback.
`messages.`, to the iOS catalogue, and — if the key can reach a notification email —
to both maps in `pkg/email/currency_messages.go`, which
`TestCurrencyMessageCataloguesMatch` keeps in step.
4 changes: 2 additions & 2 deletions docs/DOMAIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,8 +337,8 @@ rating is a `Status` (`internal/service/currency/types.go`):
The response also carries per-requirement progress, so the UI can show exactly what
remains. Every user-facing string is emitted as a stable **message key** plus its params
rather than as English prose — see [CURRENCY_MESSAGES.md](./CURRENCY_MESSAGES.md), the
cross-repo contract with the web and iOS clients. The `message` / `name` text fields are
deprecated fallbacks kept only until both clients render keys.
cross-repo contract with the web and iOS clients. The payload carries no English prose:
`name` survives only on custom currency rules, where it is pilot-authored user data.

### Passenger currency expiry (`dayExpiresOn` / `nightExpiresOn`)

Expand Down
Loading