1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
//! WHAT LANGUAGE IT IS IN: the identity a track's words carry, and the vendored registries that
//! identity is read against.
//!
//! The invariant here is a STANDARD — BCP 47, and the three ISO registries under it — and a
//! standard does not arrive one type at a time: a language, a script and a region are three subtags
//! of ONE grammar, and an identity is the three composed. What arrived is ONE invariant with four
//! names, not four inventions; making any of the three subtags a `&str` inside the composite would
//! put three folds somewhere a caller cannot see them.
//!
//! # Where it came from, and what it replaced
//!
//! The family arrived from `ingraph::primitives::lang` under that crate's #428 — whole: the four
//! types, the generated registry table, the generator behind it and every one of its tests, with no
//! behaviour changed. What did NOT come is the SCORING composite (`DetectedLanguage`, an identity
//! with a confidence beside it): identity is a vocabulary question and belongs here, scoring is a
//! retrieval question and stays where the retrieval framework is.
//!
//! It REPLACED a wrapper over `icu_locale_core`'s three subtag types, which validated a whole tag
//! and then discarded everything past the region — so `de-CH-1901` and `de-CH` were one value and a
//! muxer's `ger` was not German at all. Nothing here discards a subtag, and the tail is the seat
//! that says so.
//!
//! # ZERO language knowledge is written here
//!
//! Not a subtag, not a name, not a fold. Two authority files are vendored under `xtask/vendor/` and
//! `cargo xtask gen-lang` turns them into `registry::table`, which is checked in;
//! `cargo xtask check` regenerates it and refuses if the two disagree, beside the pixel-format,
//! colour and codec tables the same command already checks.
//!
//! The rule earns its keep on the case the scene actually presents. A container's language tag is
//! dirty in ways no small hand-written table anticipates — an mkv writes ISO 639-2/B `ger`, an mp4
//! writes 639-2/T `deu`, a muxer writes `iw` for Hebrew forty years after the registry renamed it
//! `he`, and plenty write `und` or nothing at all. Each of those is a row in a published table, and
//! the way to get all of them right is to read the table.
//!
//! # Two files, because one of them cannot answer the question
//!
//! The IANA language-subtag-registry is the BCP 47 authority and feeds everything but ONE column.
//! It cannot fold `ger` or `deu`, because it contains neither word: BCP 47 takes a language's
//! two-letter code where one exists and never registers the three-letter one beside it, so the
//! whole ISO 639-2 alpha-3 space for a major language is absent from it — and that is exactly the
//! space a container writes. The ISO 639-2 registrar's own table is vendored beside it and feeds
//! that column alone. See [`registry`], and the `xtask` crate, where the argument is made in full.
//!
//! # The four types, and which of them a track actually holds
//!
//! ```text
//! Language ScriptSubtag Region one validated subtag each
//! LanguageId the three composed, plus a lossless tail — four seats
//! ```
//!
//! A track's DECLARED language is a [`LanguageId`](crate::lang::LanguageId), or an `Option<LanguageId>` where the file said
//! nothing.
//!
//! The three subtag types are rarely held on their own and are declared as types anyway, which is
//! the same decision `mediatime`'s halves get: each has a grammar, a canonical form and a registry
//! column behind it.
//!
//! # `ScriptSubtag`, and not `Script`
//!
//! The type asks *which script did this tag DECLARE* — a subtag read out of a registry, never off a
//! character. A `Script` in a media vocabulary is the other question, *which writing system is this
//! codepoint in*, asked of text rather than of metadata; the name is left free for it. Every FIELD
//! spelled `script` is untouched: a seat's name is the question it asks.
//!
//! # ONE fold per question, applied at the DOOR
//!
//! Every canonicalisation this house does happens once, when a value is constructed, and nowhere
//! else. That is what makes equality useful: two spellings of one identity become one value before
//! anything compares them.
//!
//! | question | folded by | reading a column of |
//! |---|---|---|
//! | is this the same language? | `alpha3`, then `Preferred-Value` | ISO 639-2, then BCP 47 |
//! | is this the same script? | ASCII case alone | — |
//! | is this the same region? | ASCII case, then `Preferred-Value` | BCP 47 |
//! | is this the same identity? | the three, then `Suppress-Script` | all of them |
//!
//! # The three subtag types are ONE shape, and the shape is where the family words live
//!
//! [`Language`](crate::lang::Language), [`ScriptSubtag`](crate::lang::ScriptSubtag) and [`Region`](crate::lang::Region) each hold a validated `Utf8Bytes` in its own
//! canonical case, and every one of them owes the same eight impls: two renderings, the parse in
//! its three standard spellings, the text out borrowed, and the text out owned in each of the two
//! carriers a caller crosses on. Not one of those eight differs between the three by anything but
//! the type's name, so they are emitted by one `subtag_common!` macro rather than written out three
//! times.
//!
//! What is NOT in the macro is everything a reader comes here to check: the structural grammar each
//! type admits, the case it folds onto, which registry columns it folds THROUGH, and the sentences
//! its door refuses with. Those are the type's subject and each one spells them out.
//!
//! # BOUNDED SEATS ARE INLINE AND `Copy`; the one unbounded seat is not
//!
//! BCP 47 bounds all three subtags — a language at eight ASCII letters, a script at four, a region
//! at two letters or three digits — so each is stored as a fixed byte buffer with a length and
//! nothing else. That makes [`Language`](crate::lang::Language), [`ScriptSubtag`](crate::lang::ScriptSubtag) and [`Region`](crate::lang::Region) `Copy`: a clone is a
//! register move, equality is a fixed-width comparison, and none of the three can allocate. It is
//! also why [`LanguageId`](crate::lang::LanguageId)'s three bounded accessors hand back VALUES rather than borrows.
//!
//! [`LanguageId`](crate::lang::LanguageId)'s tail is the exception and the reason the exception exists: variants, extensions
//! and the private-use sequence have no width the grammar bounds, so that one seat is a
//! `smol_bytes::Utf8Bytes` — the text seat the retrieval layer downstream addresses a row by, which
//! is what lets an identity be keyed there without a copy at the boundary. It is the household's
//! only heap-backed field, and therefore the only reason a `LanguageId` is `Clone` and not `Copy`.
pub use ;
pub use ;
pub use ;
pub use ;
/// Generate the rows that are the same row for all three subtag types, and the reason this house
/// has a macro at all.
///
/// | row | what it says |
/// |---|---|
/// | [`Display`](core::fmt::Display), [`Debug`](core::fmt::Debug) | the canonical subtag, and nothing around it |
/// | [`FromStr`](core::str::FromStr), [`TryFrom`] × 2 | the parse, forwarded to the type's own door — off a `&str` and off the text seat |
/// | [`AsRef<str>`] | the text out, borrowed |
/// | [`From`] × 2 | the text out, owned — as the seat the type holds, and as a `String` |
///
/// The `String` conversion is what gives `String: TryFrom<Language>` — and the same for the other
/// two — through std's blanket, with `Error = Infallible`: a canonical subtag is valid text by
/// construction.
pub use subtag_common;
// Optional `serde` impls for the whole household, grouped in one gated `const` block: every one of
// the four types (de)serializes as its CANONICAL TEXT — `"de"`, `"Hans"`, `"MM"`, `"zh-Hant-TW"` —
// which is the same text `Display` writes and `FromStr` reads back.
//
// Written here rather than in `serde_impls`, which is where this household's predecessor kept its
// own bespoke BCP-47 impl, and for the same reason: `serde_impls` centralises the roster enums'
// slug crossing, and these four are not roster enums.
//
// The READ goes through the type's own door, which is what makes it CANONICALISE rather than merely
// validate: a document written before a registry bump, or by a writer that is not this crate,
// holding `"GER"` reads back as the `de` every other route produces. A text that names no subtag is
// a deserialize error rather than a value whose type says it cannot exist.
const _: = ;