@voxendar/shared 0.3.0 → 0.4.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/dist/constants.d.ts +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/i18n/copy/ar-SA.d.ts +176 -0
- package/dist/i18n/copy/ar-SA.d.ts.map +1 -0
- package/dist/i18n/copy/ar-SA.js +194 -0
- package/dist/i18n/copy/ar-SA.js.map +1 -0
- package/dist/i18n/copy/de-DE.d.ts +176 -0
- package/dist/i18n/copy/de-DE.d.ts.map +1 -0
- package/dist/i18n/copy/de-DE.js +196 -0
- package/dist/i18n/copy/de-DE.js.map +1 -0
- package/dist/i18n/copy/en-US.d.ts +190 -0
- package/dist/i18n/copy/en-US.d.ts.map +1 -0
- package/dist/i18n/copy/en-US.js +223 -0
- package/dist/i18n/copy/en-US.js.map +1 -0
- package/dist/i18n/copy/es-MX.d.ts +176 -0
- package/dist/i18n/copy/es-MX.d.ts.map +1 -0
- package/dist/i18n/copy/es-MX.js +189 -0
- package/dist/i18n/copy/es-MX.js.map +1 -0
- package/dist/i18n/copy/fr-FR.d.ts +176 -0
- package/dist/i18n/copy/fr-FR.d.ts.map +1 -0
- package/dist/i18n/copy/fr-FR.js +190 -0
- package/dist/i18n/copy/fr-FR.js.map +1 -0
- package/dist/i18n/copy/he-IL.d.ts +176 -0
- package/dist/i18n/copy/he-IL.d.ts.map +1 -0
- package/dist/i18n/copy/he-IL.js +190 -0
- package/dist/i18n/copy/he-IL.js.map +1 -0
- package/dist/i18n/copy/hi-IN.d.ts +176 -0
- package/dist/i18n/copy/hi-IN.d.ts.map +1 -0
- package/dist/i18n/copy/hi-IN.js +191 -0
- package/dist/i18n/copy/hi-IN.js.map +1 -0
- package/dist/i18n/copy/ja-JP.d.ts +176 -0
- package/dist/i18n/copy/ja-JP.d.ts.map +1 -0
- package/dist/i18n/copy/ja-JP.js +192 -0
- package/dist/i18n/copy/ja-JP.js.map +1 -0
- package/dist/i18n/copy/ko-KR.d.ts +176 -0
- package/dist/i18n/copy/ko-KR.d.ts.map +1 -0
- package/dist/i18n/copy/ko-KR.js +192 -0
- package/dist/i18n/copy/ko-KR.js.map +1 -0
- package/dist/i18n/copy/pt-BR.d.ts +176 -0
- package/dist/i18n/copy/pt-BR.d.ts.map +1 -0
- package/dist/i18n/copy/pt-BR.js +187 -0
- package/dist/i18n/copy/pt-BR.js.map +1 -0
- package/dist/i18n/copy/ru-RU.d.ts +176 -0
- package/dist/i18n/copy/ru-RU.d.ts.map +1 -0
- package/dist/i18n/copy/ru-RU.js +191 -0
- package/dist/i18n/copy/ru-RU.js.map +1 -0
- package/dist/i18n/copy/zh-CN.d.ts +176 -0
- package/dist/i18n/copy/zh-CN.d.ts.map +1 -0
- package/dist/i18n/copy/zh-CN.js +189 -0
- package/dist/i18n/copy/zh-CN.js.map +1 -0
- package/dist/i18n/copy/zh-TW.d.ts +176 -0
- package/dist/i18n/copy/zh-TW.d.ts.map +1 -0
- package/dist/i18n/copy/zh-TW.js +196 -0
- package/dist/i18n/copy/zh-TW.js.map +1 -0
- package/dist/i18n/rtl.d.ts +27 -0
- package/dist/i18n/rtl.d.ts.map +1 -0
- package/dist/i18n/rtl.js +47 -0
- package/dist/i18n/rtl.js.map +1 -0
- package/dist/i18n/server-messages.d.ts +56 -0
- package/dist/i18n/server-messages.d.ts.map +1 -0
- package/dist/i18n/server-messages.js +163 -0
- package/dist/i18n/server-messages.js.map +1 -0
- package/dist/i18n/templates.d.ts +5 -0
- package/dist/i18n/templates.d.ts.map +1 -1
- package/dist/i18n/templates.js +5 -0
- package/dist/i18n/templates.js.map +1 -1
- package/dist/i18n/ui.d.ts +85 -0
- package/dist/i18n/ui.d.ts.map +1 -0
- package/dist/i18n/ui.js +151 -0
- package/dist/i18n/ui.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas/enums.d.ts +2 -0
- package/dist/schemas/enums.d.ts.map +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KNOWN_SERVER_MESSAGES = void 0;
|
|
4
|
+
exports.stripHttpStatus = stripHttpStatus;
|
|
5
|
+
exports.localizeServerMessage = localizeServerMessage;
|
|
6
|
+
exports.isMappedServerMessage = isMappedServerMessage;
|
|
7
|
+
/**
|
|
8
|
+
* The server's sentences, in the member's language (Milestone 1.9.7).
|
|
9
|
+
*
|
|
10
|
+
* Every callable in `functions/src` refuses with an `HttpsError`, and those sentences were
|
|
11
|
+
* written for the member — `That note is no longer there`, `I could not hear anything in
|
|
12
|
+
* that recording`. The app renders them directly (`captureErrorMessage`), which was right
|
|
13
|
+
* while the interface was English: the sentence and the screen spoke the same language.
|
|
14
|
+
* M1.9.7 breaks that coincidence, and this file is how it is repaired.
|
|
15
|
+
*
|
|
16
|
+
* Two rules, and one deliberate limitation:
|
|
17
|
+
*
|
|
18
|
+
* 1. **The key is the exact message text.** We own these strings; the guard test
|
|
19
|
+
* (`tests/functions/i18n-messages.test.js`) scans `functions/src` for every literal
|
|
20
|
+
* `HttpsError` message and fails when one is neither mapped here nor marked internal.
|
|
21
|
+
* That is the point: the mapping can only stay correct if something breaks when the
|
|
22
|
+
* sentence changes. (A template-literal message is out of the scan's reach and lands
|
|
23
|
+
* on the generic sentence — a gap worth knowing about, not worth closing by regex.)
|
|
24
|
+
*
|
|
25
|
+
* "Exact" needs one caveat, and it hid every refusal behind the generic sentence until it
|
|
26
|
+
* was found (2026-10-01): the client never receives a byte-identical sentence.
|
|
27
|
+
* `@firebase/functions` builds its error as `` `${message} [${httpStatus}]` ``, so the
|
|
28
|
+
* status travels welded to the sentence and *every* lookup in the table missed — a
|
|
29
|
+
* refused `createCircle` read as the generic "could not create your calendar" instead of
|
|
30
|
+
* "please sign in again". The guard test below cannot see this: the mangling happens in
|
|
31
|
+
* the SDK, outside the source it scans. So the lookup strips the suffix first
|
|
32
|
+
* (`stripHttpStatus`), and `server-messages.test.ts` pins the behaviour.
|
|
33
|
+
* 2. **`null` means "never show this to a member."** Internal faults — a schema that
|
|
34
|
+
* failed validation, a stack-trace-shaped message — get `error.generic` instead of a
|
|
35
|
+
* translation, because a member cannot act on them and the wording may leak internals.
|
|
36
|
+
* 3. **Unmapped messages also get `error.generic`, never the raw English.** A member
|
|
37
|
+
* should not read prose in a language they did not choose *because something failed*.
|
|
38
|
+
* `localizeServerMessage` reports whether it fell back, so the caller can log the
|
|
39
|
+
* original for the console — where it belongs.
|
|
40
|
+
*/
|
|
41
|
+
const ui_1 = require("./ui");
|
|
42
|
+
/** Map a server sentence to a copy key, or to `null` when a member must not see it. */
|
|
43
|
+
const SERVER_MESSAGES = {
|
|
44
|
+
// ── Network / provider faults: the member can retry ───────────────────────
|
|
45
|
+
"Could not reach the Google Calendar API": "error.connection",
|
|
46
|
+
"Could not reach the Google OAuth token endpoint": "error.connection",
|
|
47
|
+
"Could not reach the extraction model": "error.connection",
|
|
48
|
+
"Could not reach the transcription service": "error.connection",
|
|
49
|
+
"Could not reach the voice provider": "error.connection",
|
|
50
|
+
"The transcription service rejected the key — check ASR_API_KEY in the server environment.": null,
|
|
51
|
+
"The extraction model rejected the key — check LLM_API_KEY in the server environment.": null,
|
|
52
|
+
"The voice provider rejected the key — check TTS_API_KEY in the server environment.": null,
|
|
53
|
+
"Voice playback is not configured (TTS_API_KEY is unset)": null,
|
|
54
|
+
"TOKEN_ENCRYPTION_KEY is not configured on the server (see .env.example).": null,
|
|
55
|
+
"GOOGLE_OAUTH_CLIENT_ID is not configured on the server.": null,
|
|
56
|
+
// ── Capture: the recording itself ─────────────────────────────────────────
|
|
57
|
+
"I could not hear anything in that recording": "error.noAudio",
|
|
58
|
+
"The uploaded recording is empty": "error.noAudio",
|
|
59
|
+
"That recording is too long — please keep it short": "error.tooLong",
|
|
60
|
+
"The recording is too large to transcribe": "error.tooLong",
|
|
61
|
+
"That recording is no longer there — please record again": "error.recordingGone",
|
|
62
|
+
"That recording does not belong to you": "error.notYours",
|
|
63
|
+
"Invalid recording path": "error.notYours",
|
|
64
|
+
// ── Capture: the note and its decision ────────────────────────────────────
|
|
65
|
+
"There was nothing to extract": "error.notAnEvent",
|
|
66
|
+
"There is nothing to read back": "error.notAnEvent",
|
|
67
|
+
"The extraction model returned an empty answer": "error.notAnEvent",
|
|
68
|
+
"That is not a capture decision": "error.badDecision",
|
|
69
|
+
"That date and time could not be read — please check them": "error.badDateTime",
|
|
70
|
+
"That note is no longer there": "error.noteGone",
|
|
71
|
+
"That note has no event to confirm": "error.noEvent",
|
|
72
|
+
"That note belongs to someone else": "error.notYours",
|
|
73
|
+
// ── Calendar write (M1.6's confirmEvent) ──────────────────────────────────
|
|
74
|
+
"That note has not been confirmed yet": null,
|
|
75
|
+
"Google Calendar would not accept the event": "error.calendarSave",
|
|
76
|
+
"Google Calendar did not return the saved event": null,
|
|
77
|
+
"Google would not renew the calendar connection": "error.signIn",
|
|
78
|
+
"The circle's calendar could not be read": null,
|
|
79
|
+
"Invalid confirm payload": null,
|
|
80
|
+
// ── Setup, membership, sign-in ────────────────────────────────────────────
|
|
81
|
+
"Invalid invite code": "error.invalidInvite",
|
|
82
|
+
"You've already completed setup": "error.alreadySetUp",
|
|
83
|
+
"Member doc is missing — re-run setup": "error.profileMissing",
|
|
84
|
+
"Your member profile could not be read": "error.profileMissing",
|
|
85
|
+
"Finish setting up your calendar before capturing an event": "error.profileMissing",
|
|
86
|
+
"Finish setting up your calendar before confirming an event": "error.profileMissing",
|
|
87
|
+
"No setup on file — finish setting up before editing settings": "error.profileMissing",
|
|
88
|
+
"Sign in with Firebase first (native Google Sign-In → idToken → signInWithCredential)": "error.signIn",
|
|
89
|
+
"No Google authorization on file — the client must complete exchangeGoogleCode first": "error.signIn",
|
|
90
|
+
"This group uses the owner's personal calendar and cannot accept members": "error.groupNotJoinable",
|
|
91
|
+
"Your Firebase account has no Google email — sign in with Google to join a group": "error.noGoogleEmail",
|
|
92
|
+
"Another member of your group is already using that color": "error.colorTaken",
|
|
93
|
+
"I could not turn that note into an event — please try rephrasing it": "error.notAnEvent",
|
|
94
|
+
"Please sign in first": "error.signIn",
|
|
95
|
+
"Sign in before capturing an event": "error.signIn",
|
|
96
|
+
"Sign in before changing settings": "error.signIn",
|
|
97
|
+
"Sign in before choosing a voice": "error.signIn",
|
|
98
|
+
"Sign in before confirming an event": "error.signIn",
|
|
99
|
+
"Sign in before hearing a voice": "error.signIn",
|
|
100
|
+
"Sign in before joining a group": "error.signIn",
|
|
101
|
+
// ── Internal: a member can do nothing with these, and they name internals ──
|
|
102
|
+
"Calendar API returned no calendar id": null,
|
|
103
|
+
"Invalid event draft": null,
|
|
104
|
+
"Invalid join payload": null,
|
|
105
|
+
"Invalid listEvents payload": null,
|
|
106
|
+
"Invalid preview request": null,
|
|
107
|
+
"Invalid settings payload": null,
|
|
108
|
+
"Invalid setup payload": null,
|
|
109
|
+
"Invalid updateEvent payload": null,
|
|
110
|
+
"Invalid voice request": null,
|
|
111
|
+
"Stored token payload has an unexpected format": null,
|
|
112
|
+
"That note could not be read": null,
|
|
113
|
+
"This group has no recorded owner": null,
|
|
114
|
+
"Send either a recording path (storagePath: audio/<uid>/...) or text": null,
|
|
115
|
+
"Transcription is not configured (ASR_API_KEY is unset)": null,
|
|
116
|
+
"Event extraction is not configured (LLM_API_KEY is unset)": null,
|
|
117
|
+
"capture doc failed schema validation": null,
|
|
118
|
+
"authorizationCode is required": null,
|
|
119
|
+
"Google did not return a refresh token. Ensure the native SDK is configured ": null,
|
|
120
|
+
"googleEventId is required": null,
|
|
121
|
+
"Invalid rescheduleOrCancelEvent payload": null,
|
|
122
|
+
"oneTimeCode must be 6 characters": null,
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* The HTTP status the JavaScript SDK welds onto a callable's sentence.
|
|
126
|
+
*
|
|
127
|
+
* `@firebase/functions` builds its error as `` `${description} [${httpStatus}]` ``
|
|
128
|
+
* (`codeForResponse` in its dist), so a member's client sees
|
|
129
|
+
* `"Please sign in first [401]"`, never `"Please sign in first"`. Since the table above keys
|
|
130
|
+
* the exact sentence — deliberately, so the guard test can hold it to the backend's source —
|
|
131
|
+
* the suffix has to come off before the lookup, or nothing ever matches.
|
|
132
|
+
*
|
|
133
|
+
* Only the SDK's own shape is removed: three digits in brackets, at the end. A sentence that
|
|
134
|
+
* happens to contain brackets elsewhere is left exactly as it is.
|
|
135
|
+
*/
|
|
136
|
+
const TRAILING_HTTP_STATUS = / \[\d{3}\]$/;
|
|
137
|
+
/** A server sentence as the client receives it, i.e. without the SDK's ` [400]` tail. */
|
|
138
|
+
function stripHttpStatus(message) {
|
|
139
|
+
return message.replace(TRAILING_HTTP_STATUS, "");
|
|
140
|
+
}
|
|
141
|
+
/** The sentence a member reads for a server refusal. */
|
|
142
|
+
function localizeServerMessage(language, message, params) {
|
|
143
|
+
const key = SERVER_MESSAGES[stripHttpStatus(message)];
|
|
144
|
+
// An unmapped sentence and one we marked internal land in the same place — the
|
|
145
|
+
// difference is only that one of them was a decision.
|
|
146
|
+
if (typeof key !== "string") {
|
|
147
|
+
return { text: (0, ui_1.translate)(language, "error.generic", params), mapped: false };
|
|
148
|
+
}
|
|
149
|
+
return { text: (0, ui_1.translate)(language, key, params), mapped: true };
|
|
150
|
+
}
|
|
151
|
+
/** True when this exact server sentence is known to be safe to show a member. */
|
|
152
|
+
function isMappedServerMessage(message) {
|
|
153
|
+
return typeof SERVER_MESSAGES[stripHttpStatus(message)] === "string";
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Every sentence this file has an opinion about — mapped or marked internal. Exported for
|
|
157
|
+
* the guard test in `tests/functions/i18n-messages.test.js`, which scans `functions/src`
|
|
158
|
+
* for literal `HttpsError` messages and fails when one is not in this list. That test is
|
|
159
|
+
* the whole reason the mapping can be trusted: without it, renaming a server sentence
|
|
160
|
+
* would silently downgrade a member's message to the generic one.
|
|
161
|
+
*/
|
|
162
|
+
exports.KNOWN_SERVER_MESSAGES = Object.freeze(Object.keys(SERVER_MESSAGES).sort());
|
|
163
|
+
//# sourceMappingURL=server-messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-messages.js","sourceRoot":"","sources":["../../src/i18n/server-messages.ts"],"names":[],"mappings":";;;AAsJA,0CAEC;AAGD,sDAYC;AAGD,sDAEC;AA5KD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,6BAA2E;AAE3E,uFAAuF;AACvF,MAAM,eAAe,GAAiC;IACpD,6EAA6E;IAC7E,yCAAyC,EAAE,kBAAkB;IAC7D,iDAAiD,EAAE,kBAAkB;IACrE,sCAAsC,EAAE,kBAAkB;IAC1D,2CAA2C,EAAE,kBAAkB;IAC/D,oCAAoC,EAAE,kBAAkB;IACxD,2FAA2F,EAAE,IAAI;IACjG,sFAAsF,EAAE,IAAI;IAC5F,oFAAoF,EAAE,IAAI;IAC1F,yDAAyD,EAAE,IAAI;IAC/D,0EAA0E,EAAE,IAAI;IAChF,yDAAyD,EAAE,IAAI;IAE/D,6EAA6E;IAC7E,6CAA6C,EAAE,eAAe;IAC9D,iCAAiC,EAAE,eAAe;IAClD,mDAAmD,EAAE,eAAe;IACpE,0CAA0C,EAAE,eAAe;IAC3D,yDAAyD,EAAE,qBAAqB;IAChF,uCAAuC,EAAE,gBAAgB;IACzD,wBAAwB,EAAE,gBAAgB;IAE1C,6EAA6E;IAC7E,8BAA8B,EAAE,kBAAkB;IAClD,+BAA+B,EAAE,kBAAkB;IACnD,+CAA+C,EAAE,kBAAkB;IACnE,gCAAgC,EAAE,mBAAmB;IACrD,0DAA0D,EAAE,mBAAmB;IAC/E,8BAA8B,EAAE,gBAAgB;IAChD,mCAAmC,EAAE,eAAe;IACpD,mCAAmC,EAAE,gBAAgB;IAErD,6EAA6E;IAC7E,sCAAsC,EAAE,IAAI;IAC5C,4CAA4C,EAAE,oBAAoB;IAClE,gDAAgD,EAAE,IAAI;IACtD,gDAAgD,EAAE,cAAc;IAChE,yCAAyC,EAAE,IAAI;IAC/C,yBAAyB,EAAE,IAAI;IAE/B,6EAA6E;IAC7E,qBAAqB,EAAE,qBAAqB;IAC5C,gCAAgC,EAAE,oBAAoB;IACtD,sCAAsC,EAAE,sBAAsB;IAC9D,uCAAuC,EAAE,sBAAsB;IAC/D,2DAA2D,EAAE,sBAAsB;IACnF,4DAA4D,EAAE,sBAAsB;IACpF,8DAA8D,EAAE,sBAAsB;IACtF,sFAAsF,EACpF,cAAc;IAChB,qFAAqF,EACnF,cAAc;IAChB,yEAAyE,EACvE,wBAAwB;IAC1B,iFAAiF,EAC/E,qBAAqB;IACvB,0DAA0D,EAAE,kBAAkB;IAC9E,qEAAqE,EAAE,kBAAkB;IACzF,sBAAsB,EAAE,cAAc;IACtC,mCAAmC,EAAE,cAAc;IACnD,kCAAkC,EAAE,cAAc;IAClD,iCAAiC,EAAE,cAAc;IACjD,oCAAoC,EAAE,cAAc;IACpD,gCAAgC,EAAE,cAAc;IAChD,gCAAgC,EAAE,cAAc;IAEhD,8EAA8E;IAC9E,sCAAsC,EAAE,IAAI;IAC5C,qBAAqB,EAAE,IAAI;IAC3B,sBAAsB,EAAE,IAAI;IAC5B,4BAA4B,EAAE,IAAI;IAClC,yBAAyB,EAAE,IAAI;IAC/B,0BAA0B,EAAE,IAAI;IAChC,uBAAuB,EAAE,IAAI;IAC7B,6BAA6B,EAAE,IAAI;IACnC,uBAAuB,EAAE,IAAI;IAC7B,+CAA+C,EAAE,IAAI;IACrD,6BAA6B,EAAE,IAAI;IACnC,kCAAkC,EAAE,IAAI;IACxC,qEAAqE,EAAE,IAAI;IAC3E,wDAAwD,EAAE,IAAI;IAC9D,2DAA2D,EAAE,IAAI;IACjE,sCAAsC,EAAE,IAAI;IAC5C,+BAA+B,EAAE,IAAI;IACrC,6EAA6E,EAAE,IAAI;IACnF,2BAA2B,EAAE,IAAI;IACjC,yCAAyC,EAAE,IAAI;IAC/C,kCAAkC,EAAE,IAAI;CACzC,CAAC;AASF;;;;;;;;;;;GAWG;AACH,MAAM,oBAAoB,GAAG,aAAa,CAAC;AAE3C,yFAAyF;AACzF,SAAgB,eAAe,CAAC,OAAe;IAC7C,OAAO,OAAO,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,wDAAwD;AACxD,SAAgB,qBAAqB,CACnC,QAA8C,EAC9C,OAAe,EACf,MAAiB;IAEjB,MAAM,GAAG,GAAG,eAAe,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,+EAA+E;IAC/E,sDAAsD;IACtD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,EAAE,IAAI,EAAE,IAAA,cAAS,EAAC,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC/E,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAA,cAAS,EAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAClE,CAAC;AAED,iFAAiF;AACjF,SAAgB,qBAAqB,CAAC,OAAe;IACnD,OAAO,OAAO,eAAe,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,KAAK,QAAQ,CAAC;AACvE,CAAC;AAED;;;;;;GAMG;AACU,QAAA,qBAAqB,GAAsB,MAAM,CAAC,MAAM,CACnE,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,EAAE,CACpC,CAAC"}
|
package/dist/i18n/templates.d.ts
CHANGED
|
@@ -37,6 +37,11 @@ declare const COPY: {
|
|
|
37
37
|
readonly join: ",";
|
|
38
38
|
readonly location: "(📍 {location})";
|
|
39
39
|
};
|
|
40
|
+
readonly "zh-TW": {
|
|
41
|
+
readonly lead: "⏰ 提醒:";
|
|
42
|
+
readonly join: ",";
|
|
43
|
+
readonly location: "(📍 {location})";
|
|
44
|
+
};
|
|
40
45
|
readonly "es-MX": {
|
|
41
46
|
readonly lead: "⏰ Recordatorio: ";
|
|
42
47
|
readonly join: " — ";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/i18n/templates.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,qBAAqB;IACpC,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;;;;;GAcG;AACH,QAAA,MAAM,IAAI
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/i18n/templates.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,qBAAqB;IACpC,mBAAmB;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED;;;;;;;;;;;;;;GAcG;AACH,QAAA,MAAM,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuB2E,CAAC;AAEtF,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,IAAI,CAAC;AAE5C;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAQhE;AAED,+DAA+D;AAC/D,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAI5F"}
|
package/dist/i18n/templates.js
CHANGED
|
@@ -25,6 +25,11 @@ exports.buildReminderMessage = buildReminderMessage;
|
|
|
25
25
|
const COPY = {
|
|
26
26
|
"en-US": { lead: "⏰ Reminder: ", join: " — ", location: " (📍 {location})" },
|
|
27
27
|
"zh-CN": { lead: "⏰ 提醒:", join: ",", location: "(📍 {location})" },
|
|
28
|
+
// Identical characters to zh-CN by necessity: the skeleton is 提醒 + full-width punctuation,
|
|
29
|
+
// and none of it differs between the two scripts. The row exists anyway because
|
|
30
|
+
// `templates.test.ts` requires every launch language to resolve to *itself* — otherwise a
|
|
31
|
+
// future zh-CN edit would silently rewrite zh-TW reminders too.
|
|
32
|
+
"zh-TW": { lead: "⏰ 提醒:", join: ",", location: "(📍 {location})" },
|
|
28
33
|
"es-MX": { lead: "⏰ Recordatorio: ", join: " — ", location: " (📍 {location})" },
|
|
29
34
|
// French puts a space before the colon — `Rappel :` — not `Rappel:`.
|
|
30
35
|
"fr-FR": { lead: "⏰ Rappel : ", join: " — ", location: " (📍 {location})" },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/i18n/templates.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/i18n/templates.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI copy (Milestone 1.9.7) — the mechanism behind the app's own words.
|
|
3
|
+
*
|
|
4
|
+
* Until this existed, `language` drove ASR, the LLM's output language, the spoken
|
|
5
|
+
* confirmation, the reminder templates and the voice ranking — and **nothing about the
|
|
6
|
+
* interface**. The M1.5 walkthrough showed the seam on screen: the confirmation was
|
|
7
|
+
* spoken in French (*"Demain, à quatorze heures… C'est bien ça ?"*) while the card's
|
|
8
|
+
* labels and its red `400` were English. This is that seam.
|
|
9
|
+
*
|
|
10
|
+
* Three properties are deliberate:
|
|
11
|
+
*
|
|
12
|
+
* 1. **The English table is the key set.** `UiKey` is derived from it (`./copy/en-US.ts`)
|
|
13
|
+
* and every other language is typed `Record<UiKey, string>`, so a missing key is a
|
|
14
|
+
* **compile error**, not a sentence that quietly turns English — the same rule the
|
|
15
|
+
* reminder templates enforce with a test, applied here by the type system.
|
|
16
|
+
* 2. **One file per language** in `./copy/`; this module is only the mechanism
|
|
17
|
+
* (resolution, lookup, interpolation, the two allowlists). A language is therefore
|
|
18
|
+
* added, reviewed, or handed to a translator on its own — and the tables live in
|
|
19
|
+
* `shared` rather than in the app because the server also writes sentences that reach
|
|
20
|
+
* the screen (`./server-messages.ts`, next door).
|
|
21
|
+
* 3. **Every string the member reads is in those tables**, including the labels that feel
|
|
22
|
+
* like data — card field names, the Today/Tomorrow chips, colour names, avatar names.
|
|
23
|
+
* A person should not read the app's sentence about their own event in a language they
|
|
24
|
+
* did not choose, and "Lavender — light periwinkle" is such a sentence.
|
|
25
|
+
*
|
|
26
|
+
* And nothing here falls back to English silently for a language we ship: see
|
|
27
|
+
* `PENDING_UI_LANGUAGES`, which names the ones still missing, and `ui.test.ts`, which
|
|
28
|
+
* asserts that list is exactly the gap between `SUPPORTED_LANGUAGES` and `UI_COPY`.
|
|
29
|
+
*/
|
|
30
|
+
import { SUPPORTED_LANGUAGES } from "../constants";
|
|
31
|
+
import { type CopyTable, type UiKey } from "./copy/en-US";
|
|
32
|
+
export type Language = (typeof SUPPORTED_LANGUAGES)[number];
|
|
33
|
+
export type { CopyTable, UiKey };
|
|
34
|
+
export type UiParams = Record<string, string | number>;
|
|
35
|
+
/**
|
|
36
|
+
* Keys where English is the right answer in *every* language: brand names, an example
|
|
37
|
+
* invite code, and a developer diagnostic. The guard both skips these and asserts they
|
|
38
|
+
* really do match English everywhere — which is why the list must stay short.
|
|
39
|
+
*/
|
|
40
|
+
export declare const UNTRANSLATED_BY_DESIGN: readonly UiKey[];
|
|
41
|
+
/**
|
|
42
|
+
* Keys where a *specific* language may legitimately coincide with English: French says
|
|
43
|
+
* *Date* and *Notes* exactly as English does, German says *Neutral* and *Person*. Keyed by
|
|
44
|
+
* language on purpose — a global allowlist would excuse a copy-paste in Spanish or Korean
|
|
45
|
+
* too, and the point of the guard is to catch exactly that.
|
|
46
|
+
*
|
|
47
|
+
* Adding an entry is a claim about that language, so it is reviewed like the copy is.
|
|
48
|
+
*/
|
|
49
|
+
export declare const MAY_READ_AS_ENGLISH: Readonly<Partial<Record<Language, readonly UiKey[]>>>;
|
|
50
|
+
/** The English table: the copy of record, and what every lookup falls back to. */
|
|
51
|
+
export declare const UI_COPY_EN: CopyTable;
|
|
52
|
+
/** Which language has which table. `en-US` is the floor; see `PENDING_UI_LANGUAGES`. */
|
|
53
|
+
export declare const UI_COPY: Partial<Record<Language, CopyTable>>;
|
|
54
|
+
/**
|
|
55
|
+
* Launch languages whose UI copy is not written yet. They fall back to English until their
|
|
56
|
+
* tables land — a decision the diff shows rather than a silent fallback, because the guard
|
|
57
|
+
* test asserts this list is exactly the gap between `SUPPORTED_LANGUAGES` and `UI_COPY`.
|
|
58
|
+
*
|
|
59
|
+
* **Empty since 2026-09-29**, when wave 2 filled the last nine tables. It stays exported,
|
|
60
|
+
* and the guard stays switched on, because the *thirteenth* language is the one this list
|
|
61
|
+
* exists for — and on **2026-10-21 that happened**: `zh-TW` arrived with its table in the
|
|
62
|
+
* same commit, so this list stayed empty. The mechanism was never needed; the guard still
|
|
63
|
+
* proved the gap was zero rather than assumed it.
|
|
64
|
+
*/
|
|
65
|
+
export declare const PENDING_UI_LANGUAGES: readonly Language[];
|
|
66
|
+
/**
|
|
67
|
+
* The mechanism. `resolveUiLanguage` deliberately mirrors `resolveTemplateKey` in
|
|
68
|
+
* `templates.ts`: exact tag → base language → `en-US`. Two tables with two different
|
|
69
|
+
* fallback policies would be a bug waiting for the first `pt-PT` member.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveUiLanguage(language: string | null | undefined): Language | null;
|
|
72
|
+
/**
|
|
73
|
+
* The copy table for a language, complete or English. A language listed in
|
|
74
|
+
* `PENDING_UI_LANGUAGES` has no table yet and lands on English — the guard test asserts
|
|
75
|
+
* that list is explicit, so this can never be the silent path it looks like.
|
|
76
|
+
*/
|
|
77
|
+
export declare function uiCopyFor(language: string | null | undefined): CopyTable;
|
|
78
|
+
/** One sentence, in one language. */
|
|
79
|
+
export declare function translate(language: string | null | undefined, key: UiKey, params?: UiParams): string;
|
|
80
|
+
/**
|
|
81
|
+
* Bind a language once and reuse it — what the app's `useT()` wraps, so a screen writes
|
|
82
|
+
* `t("card.confirm")` and never threads the language through every call.
|
|
83
|
+
*/
|
|
84
|
+
export declare function createTranslator(language: string | null | undefined): (key: UiKey, params?: UiParams) => string;
|
|
85
|
+
//# sourceMappingURL=ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/i18n/ui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAM,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAgB9D,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AACjC,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,SAAS,KAAK,EAMlD,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,KAAK,EAAE,CAAC,CAAC,CAGrF,CAAC;AAEF,kFAAkF;AAClF,eAAO,MAAM,UAAU,EAAE,SAAc,CAAC;AAExC,wFAAwF;AACxF,eAAO,MAAM,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAcxD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,QAAQ,EAAO,CAAC;AAE5D;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,QAAQ,GAAG,IAAI,CAStF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,SAAS,CAGxE;AAUD,qCAAqC;AACrC,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,GAAG,EAAE,KAAK,EACV,MAAM,CAAC,EAAE,QAAQ,GAChB,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,QAAQ,KAAK,MAAM,CAG3C"}
|
package/dist/i18n/ui.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PENDING_UI_LANGUAGES = exports.UI_COPY = exports.UI_COPY_EN = exports.MAY_READ_AS_ENGLISH = exports.UNTRANSLATED_BY_DESIGN = void 0;
|
|
4
|
+
exports.resolveUiLanguage = resolveUiLanguage;
|
|
5
|
+
exports.uiCopyFor = uiCopyFor;
|
|
6
|
+
exports.translate = translate;
|
|
7
|
+
exports.createTranslator = createTranslator;
|
|
8
|
+
/**
|
|
9
|
+
* UI copy (Milestone 1.9.7) — the mechanism behind the app's own words.
|
|
10
|
+
*
|
|
11
|
+
* Until this existed, `language` drove ASR, the LLM's output language, the spoken
|
|
12
|
+
* confirmation, the reminder templates and the voice ranking — and **nothing about the
|
|
13
|
+
* interface**. The M1.5 walkthrough showed the seam on screen: the confirmation was
|
|
14
|
+
* spoken in French (*"Demain, à quatorze heures… C'est bien ça ?"*) while the card's
|
|
15
|
+
* labels and its red `400` were English. This is that seam.
|
|
16
|
+
*
|
|
17
|
+
* Three properties are deliberate:
|
|
18
|
+
*
|
|
19
|
+
* 1. **The English table is the key set.** `UiKey` is derived from it (`./copy/en-US.ts`)
|
|
20
|
+
* and every other language is typed `Record<UiKey, string>`, so a missing key is a
|
|
21
|
+
* **compile error**, not a sentence that quietly turns English — the same rule the
|
|
22
|
+
* reminder templates enforce with a test, applied here by the type system.
|
|
23
|
+
* 2. **One file per language** in `./copy/`; this module is only the mechanism
|
|
24
|
+
* (resolution, lookup, interpolation, the two allowlists). A language is therefore
|
|
25
|
+
* added, reviewed, or handed to a translator on its own — and the tables live in
|
|
26
|
+
* `shared` rather than in the app because the server also writes sentences that reach
|
|
27
|
+
* the screen (`./server-messages.ts`, next door).
|
|
28
|
+
* 3. **Every string the member reads is in those tables**, including the labels that feel
|
|
29
|
+
* like data — card field names, the Today/Tomorrow chips, colour names, avatar names.
|
|
30
|
+
* A person should not read the app's sentence about their own event in a language they
|
|
31
|
+
* did not choose, and "Lavender — light periwinkle" is such a sentence.
|
|
32
|
+
*
|
|
33
|
+
* And nothing here falls back to English silently for a language we ship: see
|
|
34
|
+
* `PENDING_UI_LANGUAGES`, which names the ones still missing, and `ui.test.ts`, which
|
|
35
|
+
* asserts that list is exactly the gap between `SUPPORTED_LANGUAGES` and `UI_COPY`.
|
|
36
|
+
*/
|
|
37
|
+
const constants_1 = require("../constants");
|
|
38
|
+
const en_US_1 = require("./copy/en-US");
|
|
39
|
+
const fr_FR_1 = require("./copy/fr-FR");
|
|
40
|
+
const zh_CN_1 = require("./copy/zh-CN");
|
|
41
|
+
// zh-TW sits after zh-CN everywhere for the same reason: `resolveUiLanguage("zh")` picks the
|
|
42
|
+
// *first* `zh-` entry, and a bare `zh` has always meant Simplified. Order is the fallback.
|
|
43
|
+
const zh_TW_1 = require("./copy/zh-TW");
|
|
44
|
+
const es_MX_1 = require("./copy/es-MX");
|
|
45
|
+
const de_DE_1 = require("./copy/de-DE");
|
|
46
|
+
const pt_BR_1 = require("./copy/pt-BR");
|
|
47
|
+
const ru_RU_1 = require("./copy/ru-RU");
|
|
48
|
+
const ar_SA_1 = require("./copy/ar-SA");
|
|
49
|
+
const hi_IN_1 = require("./copy/hi-IN");
|
|
50
|
+
const he_IL_1 = require("./copy/he-IL");
|
|
51
|
+
const ja_JP_1 = require("./copy/ja-JP");
|
|
52
|
+
const ko_KR_1 = require("./copy/ko-KR");
|
|
53
|
+
/**
|
|
54
|
+
* Keys where English is the right answer in *every* language: brand names, an example
|
|
55
|
+
* invite code, and a developer diagnostic. The guard both skips these and asserts they
|
|
56
|
+
* really do match English everywhere — which is why the list must stay short.
|
|
57
|
+
*/
|
|
58
|
+
exports.UNTRANSLATED_BY_DESIGN = [
|
|
59
|
+
"login.configWarning",
|
|
60
|
+
"setup.invitePlaceholder",
|
|
61
|
+
"channel.sms",
|
|
62
|
+
"channel.whatsapp",
|
|
63
|
+
"channel.telegram",
|
|
64
|
+
];
|
|
65
|
+
/**
|
|
66
|
+
* Keys where a *specific* language may legitimately coincide with English: French says
|
|
67
|
+
* *Date* and *Notes* exactly as English does, German says *Neutral* and *Person*. Keyed by
|
|
68
|
+
* language on purpose — a global allowlist would excuse a copy-paste in Spanish or Korean
|
|
69
|
+
* too, and the point of the guard is to catch exactly that.
|
|
70
|
+
*
|
|
71
|
+
* Adding an entry is a claim about that language, so it is reviewed like the copy is.
|
|
72
|
+
*/
|
|
73
|
+
exports.MAY_READ_AS_ENGLISH = {
|
|
74
|
+
"fr-FR": ["card.date", "card.notes"],
|
|
75
|
+
"de-DE": ["avatar.generic_01", "avatar.generic_02"],
|
|
76
|
+
};
|
|
77
|
+
/** The English table: the copy of record, and what every lookup falls back to. */
|
|
78
|
+
exports.UI_COPY_EN = en_US_1.EN;
|
|
79
|
+
/** Which language has which table. `en-US` is the floor; see `PENDING_UI_LANGUAGES`. */
|
|
80
|
+
exports.UI_COPY = {
|
|
81
|
+
"en-US": en_US_1.EN,
|
|
82
|
+
"zh-CN": zh_CN_1.ZH,
|
|
83
|
+
"zh-TW": zh_TW_1.TW,
|
|
84
|
+
"es-MX": es_MX_1.ES,
|
|
85
|
+
"fr-FR": fr_FR_1.FR,
|
|
86
|
+
"de-DE": de_DE_1.DE,
|
|
87
|
+
"pt-BR": pt_BR_1.PT,
|
|
88
|
+
"ru-RU": ru_RU_1.RU,
|
|
89
|
+
"ar-SA": ar_SA_1.AR,
|
|
90
|
+
"hi-IN": hi_IN_1.HI,
|
|
91
|
+
"he-IL": he_IL_1.HE,
|
|
92
|
+
"ja-JP": ja_JP_1.JA,
|
|
93
|
+
"ko-KR": ko_KR_1.KO,
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Launch languages whose UI copy is not written yet. They fall back to English until their
|
|
97
|
+
* tables land — a decision the diff shows rather than a silent fallback, because the guard
|
|
98
|
+
* test asserts this list is exactly the gap between `SUPPORTED_LANGUAGES` and `UI_COPY`.
|
|
99
|
+
*
|
|
100
|
+
* **Empty since 2026-09-29**, when wave 2 filled the last nine tables. It stays exported,
|
|
101
|
+
* and the guard stays switched on, because the *thirteenth* language is the one this list
|
|
102
|
+
* exists for — and on **2026-10-21 that happened**: `zh-TW` arrived with its table in the
|
|
103
|
+
* same commit, so this list stayed empty. The mechanism was never needed; the guard still
|
|
104
|
+
* proved the gap was zero rather than assumed it.
|
|
105
|
+
*/
|
|
106
|
+
exports.PENDING_UI_LANGUAGES = [];
|
|
107
|
+
/**
|
|
108
|
+
* The mechanism. `resolveUiLanguage` deliberately mirrors `resolveTemplateKey` in
|
|
109
|
+
* `templates.ts`: exact tag → base language → `en-US`. Two tables with two different
|
|
110
|
+
* fallback policies would be a bug waiting for the first `pt-PT` member.
|
|
111
|
+
*/
|
|
112
|
+
function resolveUiLanguage(language) {
|
|
113
|
+
if (!language)
|
|
114
|
+
return null;
|
|
115
|
+
const exact = constants_1.SUPPORTED_LANGUAGES.find((candidate) => candidate === language);
|
|
116
|
+
if (exact)
|
|
117
|
+
return exact;
|
|
118
|
+
const base = language.split("-")[0] ?? "";
|
|
119
|
+
if (base.length >= 2) {
|
|
120
|
+
return constants_1.SUPPORTED_LANGUAGES.find((candidate) => candidate.startsWith(`${base}-`)) ?? null;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The copy table for a language, complete or English. A language listed in
|
|
126
|
+
* `PENDING_UI_LANGUAGES` has no table yet and lands on English — the guard test asserts
|
|
127
|
+
* that list is explicit, so this can never be the silent path it looks like.
|
|
128
|
+
*/
|
|
129
|
+
function uiCopyFor(language) {
|
|
130
|
+
const resolved = resolveUiLanguage(language);
|
|
131
|
+
return (resolved ? exports.UI_COPY[resolved] : undefined) ?? exports.UI_COPY_EN;
|
|
132
|
+
}
|
|
133
|
+
/** `{name}` → the value. A placeholder with no value is left visible, not blanked. */
|
|
134
|
+
function interpolate(text, params) {
|
|
135
|
+
if (!params)
|
|
136
|
+
return text;
|
|
137
|
+
return text.replace(/\{(\w+)\}/g, (whole, key) => key in params ? String(params[key]) : whole);
|
|
138
|
+
}
|
|
139
|
+
/** One sentence, in one language. */
|
|
140
|
+
function translate(language, key, params) {
|
|
141
|
+
return interpolate(uiCopyFor(language)[key], params);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Bind a language once and reuse it — what the app's `useT()` wraps, so a screen writes
|
|
145
|
+
* `t("card.confirm")` and never threads the language through every call.
|
|
146
|
+
*/
|
|
147
|
+
function createTranslator(language) {
|
|
148
|
+
const copy = uiCopyFor(language);
|
|
149
|
+
return (key, params) => interpolate(copy[key], params);
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/i18n/ui.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,4 +11,7 @@ export * from "./capture/audio";
|
|
|
11
11
|
export * from "./schemas/reminder";
|
|
12
12
|
export * from "./reminders/math";
|
|
13
13
|
export * from "./i18n/templates";
|
|
14
|
+
export * from "./i18n/ui";
|
|
15
|
+
export * from "./i18n/rtl";
|
|
16
|
+
export * from "./i18n/server-messages";
|
|
14
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -27,4 +27,7 @@ __exportStar(require("./capture/audio"), exports);
|
|
|
27
27
|
__exportStar(require("./schemas/reminder"), exports);
|
|
28
28
|
__exportStar(require("./reminders/math"), exports);
|
|
29
29
|
__exportStar(require("./i18n/templates"), exports);
|
|
30
|
+
__exportStar(require("./i18n/ui"), exports);
|
|
31
|
+
__exportStar(require("./i18n/rtl"), exports);
|
|
32
|
+
__exportStar(require("./i18n/server-messages"), exports);
|
|
30
33
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA4B;AAC5B,kDAAgC;AAChC,uDAAqC;AACrC,kDAAgC;AAChC,kDAAgC;AAChC,mDAAiC;AACjC,mDAAiC;AACjC,oDAAkC;AAClC,mDAAiC;AACjC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,mDAAiC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8CAA4B;AAC5B,kDAAgC;AAChC,uDAAqC;AACrC,kDAAgC;AAChC,kDAAgC;AAChC,mDAAiC;AACjC,mDAAiC;AACjC,oDAAkC;AAClC,mDAAiC;AACjC,kDAAgC;AAChC,qDAAmC;AACnC,mDAAiC;AACjC,mDAAiC;AACjC,4CAA0B;AAC1B,6CAA2B;AAC3B,yDAAuC"}
|
package/dist/schemas/enums.d.ts
CHANGED
|
@@ -8,4 +8,6 @@ export declare const CONFIDENCE_LEVELS: readonly ["high", "medium", "low"];
|
|
|
8
8
|
export declare const NOTIFY_CHANNELS: readonly ["sms", "whatsapp", "telegram"];
|
|
9
9
|
export declare const REMINDER_STATUSES: readonly ["sent", "failed", "skipped"];
|
|
10
10
|
export declare const VOICE_STYLE_PRESETS: readonly ["professional", "warm_casual", "friendly_brief"];
|
|
11
|
+
/** The preset a member chose; `member.ts` validates against the list above. */
|
|
12
|
+
export type VoiceStylePreset = (typeof VOICE_STYLE_PRESETS)[number];
|
|
11
13
|
//# sourceMappingURL=enums.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/schemas/enums.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAE7E,eAAO,MAAM,UAAU,gCAAiC,CAAC;AACzD,eAAO,MAAM,eAAe,qCAAsC,CAAC;AACnE,eAAO,MAAM,gBAAgB,6DAA8D,CAAC;AAC5F,eAAO,MAAM,gBAAgB,4CAA6C,CAAC;AAC3E,eAAO,MAAM,YAAY,qCAAsC,CAAC;AAChE,eAAO,MAAM,iBAAiB,oCAAqC,CAAC;AACpE,eAAO,MAAM,eAAe,0CAA2C,CAAC;AACxE,eAAO,MAAM,iBAAiB,wCAAyC,CAAC;AACxE,eAAO,MAAM,mBAAmB,4DAA6D,CAAC"}
|
|
1
|
+
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/schemas/enums.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAE7E,eAAO,MAAM,UAAU,gCAAiC,CAAC;AACzD,eAAO,MAAM,eAAe,qCAAsC,CAAC;AACnE,eAAO,MAAM,gBAAgB,6DAA8D,CAAC;AAC5F,eAAO,MAAM,gBAAgB,4CAA6C,CAAC;AAC3E,eAAO,MAAM,YAAY,qCAAsC,CAAC;AAChE,eAAO,MAAM,iBAAiB,oCAAqC,CAAC;AACpE,eAAO,MAAM,eAAe,0CAA2C,CAAC;AACxE,eAAO,MAAM,iBAAiB,wCAAyC,CAAC;AACxE,eAAO,MAAM,mBAAmB,4DAA6D,CAAC;AAC9F,+EAA+E;AAC/E,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voxendar/shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"private": false,
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
5
8
|
"description": "Shared contracts, schemas, constants and pure logic for Voxendar/Tellie (consumed by functions/ and app/)",
|
|
6
9
|
"main": "dist/index.js",
|
|
7
10
|
"types": "dist/index.d.ts",
|
|
@@ -19,7 +22,7 @@
|
|
|
19
22
|
"zod": "^3.23.8"
|
|
20
23
|
},
|
|
21
24
|
"devDependencies": {
|
|
22
|
-
"@types/node": "^20.
|
|
25
|
+
"@types/node": "^22.20.5",
|
|
23
26
|
"typescript": "^5.5.4",
|
|
24
27
|
"vitest": "^2.1.9"
|
|
25
28
|
}
|