@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.
Files changed (79) hide show
  1. package/dist/constants.d.ts +1 -1
  2. package/dist/constants.d.ts.map +1 -1
  3. package/dist/constants.js +1 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/i18n/copy/ar-SA.d.ts +176 -0
  6. package/dist/i18n/copy/ar-SA.d.ts.map +1 -0
  7. package/dist/i18n/copy/ar-SA.js +194 -0
  8. package/dist/i18n/copy/ar-SA.js.map +1 -0
  9. package/dist/i18n/copy/de-DE.d.ts +176 -0
  10. package/dist/i18n/copy/de-DE.d.ts.map +1 -0
  11. package/dist/i18n/copy/de-DE.js +196 -0
  12. package/dist/i18n/copy/de-DE.js.map +1 -0
  13. package/dist/i18n/copy/en-US.d.ts +190 -0
  14. package/dist/i18n/copy/en-US.d.ts.map +1 -0
  15. package/dist/i18n/copy/en-US.js +223 -0
  16. package/dist/i18n/copy/en-US.js.map +1 -0
  17. package/dist/i18n/copy/es-MX.d.ts +176 -0
  18. package/dist/i18n/copy/es-MX.d.ts.map +1 -0
  19. package/dist/i18n/copy/es-MX.js +189 -0
  20. package/dist/i18n/copy/es-MX.js.map +1 -0
  21. package/dist/i18n/copy/fr-FR.d.ts +176 -0
  22. package/dist/i18n/copy/fr-FR.d.ts.map +1 -0
  23. package/dist/i18n/copy/fr-FR.js +190 -0
  24. package/dist/i18n/copy/fr-FR.js.map +1 -0
  25. package/dist/i18n/copy/he-IL.d.ts +176 -0
  26. package/dist/i18n/copy/he-IL.d.ts.map +1 -0
  27. package/dist/i18n/copy/he-IL.js +190 -0
  28. package/dist/i18n/copy/he-IL.js.map +1 -0
  29. package/dist/i18n/copy/hi-IN.d.ts +176 -0
  30. package/dist/i18n/copy/hi-IN.d.ts.map +1 -0
  31. package/dist/i18n/copy/hi-IN.js +191 -0
  32. package/dist/i18n/copy/hi-IN.js.map +1 -0
  33. package/dist/i18n/copy/ja-JP.d.ts +176 -0
  34. package/dist/i18n/copy/ja-JP.d.ts.map +1 -0
  35. package/dist/i18n/copy/ja-JP.js +192 -0
  36. package/dist/i18n/copy/ja-JP.js.map +1 -0
  37. package/dist/i18n/copy/ko-KR.d.ts +176 -0
  38. package/dist/i18n/copy/ko-KR.d.ts.map +1 -0
  39. package/dist/i18n/copy/ko-KR.js +192 -0
  40. package/dist/i18n/copy/ko-KR.js.map +1 -0
  41. package/dist/i18n/copy/pt-BR.d.ts +176 -0
  42. package/dist/i18n/copy/pt-BR.d.ts.map +1 -0
  43. package/dist/i18n/copy/pt-BR.js +187 -0
  44. package/dist/i18n/copy/pt-BR.js.map +1 -0
  45. package/dist/i18n/copy/ru-RU.d.ts +176 -0
  46. package/dist/i18n/copy/ru-RU.d.ts.map +1 -0
  47. package/dist/i18n/copy/ru-RU.js +191 -0
  48. package/dist/i18n/copy/ru-RU.js.map +1 -0
  49. package/dist/i18n/copy/zh-CN.d.ts +176 -0
  50. package/dist/i18n/copy/zh-CN.d.ts.map +1 -0
  51. package/dist/i18n/copy/zh-CN.js +189 -0
  52. package/dist/i18n/copy/zh-CN.js.map +1 -0
  53. package/dist/i18n/copy/zh-TW.d.ts +176 -0
  54. package/dist/i18n/copy/zh-TW.d.ts.map +1 -0
  55. package/dist/i18n/copy/zh-TW.js +196 -0
  56. package/dist/i18n/copy/zh-TW.js.map +1 -0
  57. package/dist/i18n/rtl.d.ts +27 -0
  58. package/dist/i18n/rtl.d.ts.map +1 -0
  59. package/dist/i18n/rtl.js +47 -0
  60. package/dist/i18n/rtl.js.map +1 -0
  61. package/dist/i18n/server-messages.d.ts +56 -0
  62. package/dist/i18n/server-messages.d.ts.map +1 -0
  63. package/dist/i18n/server-messages.js +163 -0
  64. package/dist/i18n/server-messages.js.map +1 -0
  65. package/dist/i18n/templates.d.ts +5 -0
  66. package/dist/i18n/templates.d.ts.map +1 -1
  67. package/dist/i18n/templates.js +5 -0
  68. package/dist/i18n/templates.js.map +1 -1
  69. package/dist/i18n/ui.d.ts +85 -0
  70. package/dist/i18n/ui.d.ts.map +1 -0
  71. package/dist/i18n/ui.js +151 -0
  72. package/dist/i18n/ui.js.map +1 -0
  73. package/dist/index.d.ts +3 -0
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +3 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/schemas/enums.d.ts +2 -0
  78. package/dist/schemas/enums.d.ts.map +1 -1
  79. 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":"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"}
@@ -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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkB2E,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"}
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"}
@@ -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;;AA0DH,gDAQC;AAGD,oDAIC;AA9DD;;;;;;;;;;;;;;GAcG;AACH,MAAM,IAAI,GAAG;IACX,OAAO,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IAC5E,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,iBAAiB,EAAE;IAClE,OAAO,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IAChF,qEAAqE;IACrE,OAAO,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IAC3E,OAAO,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IAC9E,OAAO,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IAC5E,OAAO,EAAE,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IAC/E,8EAA8E;IAC9E,mEAAmE;IACnE,OAAO,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IACzE,OAAO,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IAC5E,OAAO,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;IAC1E,wEAAwE;IACxE,8DAA8D;IAC9D,OAAO,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE;IACxE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE;CACa,CAAC;AAItF;;;;;;;;;GASG;AACH,SAAgB,kBAAkB,CAAC,QAAgB;IACjD,IAAI,QAAQ,IAAI,IAAI;QAAE,OAAO,QAAuB,CAAC;IACrD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC1C,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,OAAO,GAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAmB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;QAC/F,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;IAC9B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,+DAA+D;AAC/D,SAAgB,oBAAoB,CAAC,QAAgB,EAAE,MAA6B;IAClF,MAAM,IAAI,GAAG,IAAI,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IACrE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9F,CAAC"}
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"}
@@ -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
@@ -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"}
@@ -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.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.14.0",
25
+ "@types/node": "^22.20.5",
23
26
  "typescript": "^5.5.4",
24
27
  "vitest": "^2.1.9"
25
28
  }