@mentra/cloud-protocol 0.1.0-beta.0 → 3.1.0-dev.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mentra/cloud-protocol",
3
- "version": "0.1.0-beta.0",
3
+ "version": "3.1.0-dev.1",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "exports": {
package/src/index.ts CHANGED
@@ -13,5 +13,6 @@ export * from "./control";
13
13
  export * from "./errors";
14
14
  export * from "./messages";
15
15
  export * from "./audio";
16
+ export * from "./languages";
16
17
  export * from "./camera";
17
18
  export * from "./maps";
@@ -0,0 +1,209 @@
1
+ /**
2
+ * @fileoverview The canonical language registry (issue 021, WP1).
3
+ *
4
+ * Single source of truth for every language value that crosses a layer
5
+ * boundary: miniapp SDK types, miniapp UI pickers, engine validation, cloud
6
+ * subscription validation, and STT provider config all derive from THIS
7
+ * module. Per-miniapp mapping tables are banned; the captions bugbash
8
+ * (OS-1746) traced directly to two hand-maintained registries disagreeing.
9
+ *
10
+ * Two vocabularies exist on purpose:
11
+ * - `TranscriptionLanguage`: BCP-47 tags ("en-US") used in subscriptions and
12
+ * stream keys. What a miniapp asks for.
13
+ * - `LanguageHint`: bare ISO 639-1 codes ("en") — the only format the STT
14
+ * provider (Soniox) accepts as language hints. Passing a BCP-47 tag kills
15
+ * the session with "Invalid language hint." (verified live 2026-07-21).
16
+ *
17
+ * Design doc: docs/issues/021-typed-language-subscriptions/README.md
18
+ */
19
+
20
+ /**
21
+ * Bare ISO 639-1 code -> canonical default BCP-47 tag. One entry per language
22
+ * the platform offers. The key set doubles as the supported-hints list; the
23
+ * value set doubles as the supported-subscription-tags list.
24
+ *
25
+ * Seeded from the 63 languages the captions UI offers (all Soniox stt-rt-v4
26
+ * supported); regional variants beyond the default (e.g. "en-GB", "zh-TW",
27
+ * "pt-PT") are listed in EXTRA_REGIONAL_TAGS below.
28
+ */
29
+ const CANONICAL_TAG_BY_CODE = {
30
+ af: "af-ZA",
31
+ am: "am-ET",
32
+ ar: "ar-AE",
33
+ az: "az-AZ",
34
+ be: "be-BY",
35
+ bg: "bg-BG",
36
+ bn: "bn-IN",
37
+ bs: "bs-BA",
38
+ ca: "ca-ES",
39
+ cs: "cs-CZ",
40
+ cy: "cy-GB",
41
+ da: "da-DK",
42
+ de: "de-DE",
43
+ el: "el-GR",
44
+ en: "en-US",
45
+ es: "es-ES",
46
+ et: "et-EE",
47
+ eu: "eu-ES",
48
+ fa: "fa-IR",
49
+ fi: "fi-FI",
50
+ fr: "fr-FR",
51
+ gl: "gl-ES",
52
+ gu: "gu-IN",
53
+ he: "he-IL",
54
+ hi: "hi-IN",
55
+ hr: "hr-HR",
56
+ hu: "hu-HU",
57
+ hy: "hy-AM",
58
+ id: "id-ID",
59
+ it: "it-IT",
60
+ ja: "ja-JP",
61
+ ka: "ka-GE",
62
+ kk: "kk-KZ",
63
+ kn: "kn-IN",
64
+ ko: "ko-KR",
65
+ lt: "lt-LT",
66
+ lv: "lv-LV",
67
+ mk: "mk-MK",
68
+ ml: "ml-IN",
69
+ mr: "mr-IN",
70
+ ms: "ms-MY",
71
+ nb: "nb-NO",
72
+ ne: "ne-NP",
73
+ nl: "nl-NL",
74
+ no: "nb-NO",
75
+ pa: "pa-IN",
76
+ pl: "pl-PL",
77
+ pt: "pt-BR",
78
+ ro: "ro-RO",
79
+ ru: "ru-RU",
80
+ sk: "sk-SK",
81
+ sl: "sl-SI",
82
+ sq: "sq-AL",
83
+ sr: "sr-RS",
84
+ sv: "sv-SE",
85
+ sw: "sw-KE",
86
+ ta: "ta-IN",
87
+ te: "te-IN",
88
+ th: "th-TH",
89
+ tl: "fil-PH",
90
+ tr: "tr-TR",
91
+ uk: "uk-UA",
92
+ ur: "ur-IN",
93
+ vi: "vi-VN",
94
+ zh: "zh-CN",
95
+ } as const;
96
+
97
+ /**
98
+ * Regional variants accepted IN ADDITION to the canonical defaults above.
99
+ * A tag listed here is a valid `TranscriptionLanguage` but is never produced
100
+ * by bare-code canonicalization.
101
+ */
102
+ const EXTRA_REGIONAL_TAGS = [
103
+ "ar-EG",
104
+ "ar-SA",
105
+ "de-AT",
106
+ "de-CH",
107
+ "en-AU",
108
+ "en-CA",
109
+ "en-GB",
110
+ "en-IN",
111
+ "es-419",
112
+ "es-MX",
113
+ "es-US",
114
+ "fr-CA",
115
+ "nl-BE",
116
+ "pt-PT",
117
+ "zh-HK",
118
+ "zh-TW",
119
+ ] as const;
120
+
121
+ type CodeMap = typeof CANONICAL_TAG_BY_CODE;
122
+
123
+ /** Bare ISO 639-1 hint codes the STT provider accepts ("en", "fr", ...). */
124
+ export type LanguageHint = keyof CodeMap;
125
+
126
+ /** BCP-47 tags valid in transcription subscriptions and stream keys. */
127
+ export type TranscriptionLanguage =
128
+ | CodeMap[LanguageHint]
129
+ | (typeof EXTRA_REGIONAL_TAGS)[number];
130
+
131
+ /** Every supported hint code, sorted, deduped. */
132
+ export const SUPPORTED_LANGUAGE_HINTS = Object.freeze(
133
+ [...new Set(Object.keys(CANONICAL_TAG_BY_CODE))].sort(),
134
+ ) as readonly LanguageHint[];
135
+
136
+ /** Every supported subscription tag, sorted, deduped. */
137
+ export const SUPPORTED_TRANSCRIPTION_LANGUAGES = Object.freeze(
138
+ [
139
+ ...new Set<string>([
140
+ ...Object.values(CANONICAL_TAG_BY_CODE),
141
+ ...EXTRA_REGIONAL_TAGS,
142
+ ]),
143
+ ].sort(),
144
+ ) as readonly TranscriptionLanguage[];
145
+
146
+ const TAG_SET = new Set<string>(SUPPORTED_TRANSCRIPTION_LANGUAGES);
147
+ const HINT_SET = new Set<string>(SUPPORTED_LANGUAGE_HINTS);
148
+
149
+ /** Type guard: is `value` a supported subscription tag? */
150
+ export function isTranscriptionLanguage(
151
+ value: string,
152
+ ): value is TranscriptionLanguage {
153
+ return TAG_SET.has(value);
154
+ }
155
+
156
+ /** Type guard: is `value` a supported bare hint code? */
157
+ export function isLanguageHint(value: string): value is LanguageHint {
158
+ return HINT_SET.has(value);
159
+ }
160
+
161
+ /**
162
+ * Canonicalize developer input to a subscription tag:
163
+ * - a supported tag passes through ("fr-FR" -> "fr-FR")
164
+ * - a bare supported code maps to its canonical default ("fr" -> "fr-FR")
165
+ * - anything else returns null (caller decides how loudly to fail)
166
+ *
167
+ * Per issue 021 open question 1, bare codes are accepted and canonicalized
168
+ * rather than rejected: it matches user intent and the canonicalization table
169
+ * lives HERE, nowhere else.
170
+ */
171
+ export function toTranscriptionLanguage(
172
+ value: string,
173
+ ): TranscriptionLanguage | null {
174
+ if (TAG_SET.has(value)) return value as TranscriptionLanguage;
175
+ const canonical = (CANONICAL_TAG_BY_CODE as Record<string, string>)[
176
+ value.toLowerCase()
177
+ ];
178
+ return (canonical as TranscriptionLanguage) ?? null;
179
+ }
180
+
181
+ /**
182
+ * Reduce a subscription tag (or bare code) to the hint format the STT
183
+ * provider accepts. "en-US" -> "en", "fil-PH" -> "tl" is NOT attempted (the
184
+ * primary subtag is what providers key on), so "fil-PH" -> "fil" falls back
185
+ * to null and the caller simply omits the hint. Null means "no valid hint";
186
+ * never pass the raw value through on null (that is the exact Soniox
187
+ * session-killer this registry exists to prevent).
188
+ */
189
+ export function toLanguageHint(value: string): LanguageHint | null {
190
+ const primary = value.split("-")[0].toLowerCase();
191
+ return HINT_SET.has(primary) ? (primary as LanguageHint) : null;
192
+ }
193
+
194
+ /**
195
+ * Nearest valid tag for an invalid input, for error messages
196
+ * ("unknown language \"fr_FR\", did you mean \"fr-FR\"?"). Cheap heuristics
197
+ * only: underscore/case fixes, then primary-subtag canonical default.
198
+ */
199
+ export function suggestTranscriptionLanguage(
200
+ value: string,
201
+ ): TranscriptionLanguage | null {
202
+ const normalized = value.replace(/_/g, "-");
203
+ const parts = normalized.split("-");
204
+ const recased =
205
+ parts.length >= 2
206
+ ? `${parts[0].toLowerCase()}-${parts[1].toUpperCase()}`
207
+ : normalized.toLowerCase();
208
+ return toTranscriptionLanguage(recased);
209
+ }