@vocoder/core 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vocoder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # @vocoder/core
2
+
3
+ Shared primitives for the Vocoder i18n SDK.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @vocoder/core
9
+ ```
10
+
11
+ ## When to use this package directly
12
+
13
+ Most users should install `@vocoder/react` instead — it re-exports everything from this package along with the React components and hooks.
14
+
15
+ Use `@vocoder/core` directly only when you are:
16
+
17
+ - Building a custom framework integration that does not use React
18
+ - Writing tooling (extractors, validators, migration scripts) that needs hash generation or ICU formatting without a React dependency
19
+ - Implementing a custom runtime that needs cookie utilities or locale matching
20
+
21
+ ---
22
+
23
+ ## API Reference
24
+
25
+ ### `generateMessageHash(text, context?, formality?)`
26
+
27
+ Generates a stable 7-character base-36 message ID from source text. Produces the same output in Node.js and browsers. Used by the extractor at build time and the runtime in the browser — both always produce the same key for the same input.
28
+
29
+ ```ts
30
+ import { generateMessageHash } from "@vocoder/core";
31
+
32
+ // Deterministic — same input always produces the same hash
33
+ generateMessageHash("Hello, world!"); // "3k2m9q1"
34
+
35
+ // Context disambiguates identical strings with different meanings
36
+ generateMessageHash("Save", "button label"); // different hash
37
+ generateMessageHash("Save", "dialog title"); // different hash
38
+
39
+ // Formality produces separate keys for register variants
40
+ generateMessageHash("Welcome back", undefined, "formal"); // hash A
41
+ generateMessageHash("Welcome back", undefined, "informal"); // hash B
42
+ generateMessageHash("Welcome back", undefined, "auto"); // same as no formality
43
+ ```
44
+
45
+ | Parameter | Type | Required | Description |
46
+ |---|---|---|---|
47
+ | `text` | `string` | yes | Source message text |
48
+ | `context` | `string` | no | Disambiguation string for identical text with different meanings |
49
+ | `formality` | `string` | no | `"formal"` or `"informal"` produce distinct hashes; `"auto"` and `undefined` hash identically |
50
+
51
+ Returns a 7-character `string` (base-36, zero-padded). Collision probability is approximately 0.002% across 10,000 strings.
52
+
53
+ ---
54
+
55
+ ### `formatICU(text, values, locale?)`
56
+
57
+ Formats an ICU MessageFormat string with interpolated values. Returns the raw `text` unchanged if parsing or formatting throws — the caller always receives a string, never an exception.
58
+
59
+ ```ts
60
+ import { formatICU } from "@vocoder/core";
61
+
62
+ // Plural
63
+ formatICU(
64
+ "{count, plural, one {# item} other {# items}}",
65
+ { count: 1 },
66
+ "en"
67
+ ); // "1 item"
68
+
69
+ formatICU(
70
+ "{count, plural, one {# item} other {# items}}",
71
+ { count: 5 },
72
+ "en"
73
+ ); // "5 items"
74
+
75
+ // Select
76
+ formatICU(
77
+ "{gender, select, male {He agreed} female {She agreed} other {They agreed}}",
78
+ { gender: "female" },
79
+ "en"
80
+ ); // "She agreed"
81
+
82
+ // Simple interpolation
83
+ formatICU("Hello, {name}!", { name: "Maria" }, "es"); // "Hello, Maria!"
84
+ ```
85
+
86
+ | Parameter | Type | Required | Description |
87
+ |---|---|---|---|
88
+ | `text` | `string` | yes | ICU MessageFormat string |
89
+ | `values` | `Record<string, any>` | yes | Interpolation values keyed by placeholder name |
90
+ | `locale` | `string` | no | BCP 47 locale tag; defaults to `"en"` |
91
+
92
+ Returns a `string`. Parsed results are cached by `"locale:text"` key.
93
+
94
+ ---
95
+
96
+ ### `rewriteSelectordinalInICU(icu, ordinalForms, values)`
97
+
98
+ Rewrites any embedded `selectordinal` nodes in an ICU string using a locale's `ordinalForms` data before passing the string to `formatICU`. This is required when a `selectordinal` appears inside a larger sentence rather than as the sole top-level element, because translation providers store incorrect ordinal branches in those cases.
99
+
100
+ ```ts
101
+ import { rewriteSelectordinalInICU, formatICU } from "@vocoder/core";
102
+
103
+ const rewritten = rewriteSelectordinalInICU(
104
+ "Congrats on your {year, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} anniversary!",
105
+ ordinalForms, // from locale bundle
106
+ { year: 3 }
107
+ );
108
+ const result = formatICU(rewritten, { year: 3 }, "en");
109
+ // "Congrats on your 3rd anniversary!"
110
+ ```
111
+
112
+ Returns `icu` unchanged when the string contains no `"selectordinal"` substring (fast path) or when parsing throws.
113
+
114
+ This function is called automatically by `@vocoder/react` when rendering ordinals. See the `@vocoder/react` docs for usage via `ordinal()` and the `<T>` component.
115
+
116
+ ---
117
+
118
+ ### `formatValue(value, format, locale, options?)`
119
+
120
+ Formats a number or date value using `Intl.NumberFormat` or `Intl.DateTimeFormat`. Formatters are cached internally.
121
+
122
+ ```ts
123
+ import { formatValue } from "@vocoder/core";
124
+
125
+ formatValue(1234567.89, "number", "de"); // "1.234.567,89"
126
+ formatValue(0.175, "percent", "en"); // "17.5%"
127
+ formatValue(9876543, "compact", "en"); // "9.9M"
128
+ formatValue(42.6, "integer", "fr"); // "43"
129
+ formatValue(49.99, "currency", "en", { currency: "USD" }); // "$49.99"
130
+ formatValue(new Date(), "date", "ja", { dateStyle: "long" }); // "2026年5月9日"
131
+ formatValue(new Date(), "time", "en"); // "3:04 PM"
132
+ formatValue(new Date(), "datetime", "en", { dateStyle: "short", timeStyle: "short" }); // "5/9/26, 3:04 PM"
133
+ ```
134
+
135
+ | Parameter | Type | Required | Description |
136
+ |---|---|---|---|
137
+ | `value` | `string \| number \| Date` | yes | The value to format |
138
+ | `format` | `FormatMode` | yes | One of the format modes listed below |
139
+ | `locale` | `string` | yes | BCP 47 locale tag |
140
+ | `options` | `FormatValueOptions` | no | Additional formatting options |
141
+
142
+ **Format modes:**
143
+
144
+ | Mode | Input type | Description |
145
+ |---|---|---|
146
+ | `"number"` | number | Locale-aware decimal formatting |
147
+ | `"integer"` | number | Rounded to zero decimal places |
148
+ | `"percent"` | number | Multiplies by 100 and appends percent sign |
149
+ | `"compact"` | number | Abbreviated notation (1.2M, 3.4K) |
150
+ | `"currency"` | number | Currency symbol and formatting; requires `options.currency` |
151
+ | `"date"` | Date / string / number | Date portion only |
152
+ | `"time"` | Date / string / number | Time portion only |
153
+ | `"datetime"` | Date / string / number | Date and time |
154
+
155
+ **`FormatValueOptions`:**
156
+
157
+ | Option | Type | Default | Description |
158
+ |---|---|---|---|
159
+ | `currency` | `string` | — | ISO 4217 currency code (e.g. `"USD"`); required when `format` is `"currency"` |
160
+ | `dateStyle` | `"full" \| "long" \| "medium" \| "short"` | `"medium"` | Date display style |
161
+ | `timeStyle` | `"full" \| "long" \| "medium" \| "short"` | `"short"` | Time display style |
162
+
163
+ ---
164
+
165
+ ### `getCookie(name, cookieString?)` / `getBestMatchingLocale(...)` / `setCookie(...)`
166
+
167
+ Cookie utilities and locale negotiation used by the Vocoder provider for locale detection and persistence.
168
+
169
+ ```ts
170
+ import { getCookie, setBestMatchingLocale, setCookie, getBestMatchingLocale } from "@vocoder/core";
171
+
172
+ // Read a cookie (browser or server)
173
+ const locale = getCookie("vocoder_locale"); // browser
174
+ const locale = getCookie("vocoder_locale", req.headers.cookie); // server
175
+
176
+ // Write a cookie (browser only)
177
+ setCookie("vocoder_locale", "fr", { maxAge: 31536000, path: "/" });
178
+
179
+ // Negotiate the best supported locale from a preferred value
180
+ getBestMatchingLocale("en-US", ["en", "fr", "de"], "en"); // "en"
181
+ getBestMatchingLocale("pt-BR", ["pt", "en"], "en"); // "pt"
182
+ getBestMatchingLocale("zh", ["en", "fr"], "en"); // "en" (fallback)
183
+ ```
184
+
185
+ **`getCookie(name, cookieString?)`** — Reads a cookie by name. When `cookieString` is omitted it reads from `document.cookie` in the browser. Returns `null` when the cookie is absent or the environment is not a browser and no string was provided.
186
+
187
+ **`setCookie(name, value, options?)`** — Writes a cookie to `document.cookie`. No-ops in non-browser environments. Defaults: `maxAge` 1 year, `path` `/`, `sameSite` `"Lax"`, `secure` based on current protocol.
188
+
189
+ **`getBestMatchingLocale(preferredLocale, supportedLocales, fallback)`** — Returns the closest supported locale for a given preference. Tries exact match first, then language-only match (`"en-US"` → `"en"`), then any regional variant of the same language, then the fallback.
190
+
191
+ ---
192
+
193
+ ## Types
194
+
195
+ | Type | Description |
196
+ |---|---|
197
+ | `TranslationsMap` | Nested map of `locale → key → translated string` |
198
+ | `OrdinalSuffixes` | CLDR plural-category suffixes (`zero`, `one`, `two`, `few`, `many`, `other`) for suffix-based ordinals |
199
+ | `OrdinalForms` | Discriminated union: `{ type: "suffix"; suffixes: OrdinalSuffixes }` or `{ type: "word"; words: Record<string, Record<number, string>> }` |
200
+ | `LocaleInfo` | Metadata for a single locale: `nativeName`, optional `dir` (`"rtl"`), `currencyCode`, and `ordinalForms` |
201
+ | `LocalesMap` | Map of locale code → `LocaleInfo` |
202
+ | `FormatMode` | Union of valid format mode strings for `formatValue` |
203
+ | `TOptions` | Options for `<T>` and `t()`: `context`, `formality`, and `id` |
204
+ | `VocoderTranslationData` | Canonical translation bundle shape: `config` (source locale, target locales, locale metadata) + `translations` map + `updatedAt` |
205
+ | `FormatValueOptions` | Options for `formatValue`: `currency`, `dateStyle`, `timeStyle` |
206
+
207
+ ---
208
+
209
+ Most users should install `@vocoder/react` which re-exports everything from this package.
@@ -0,0 +1,124 @@
1
+ declare function getCookie(name: string, cookieString?: string): string | null;
2
+ declare function setCookie(name: string, value: string, options?: {
3
+ maxAge?: number;
4
+ path?: string;
5
+ domain?: string;
6
+ sameSite?: "Strict" | "Lax" | "None";
7
+ secure?: boolean;
8
+ }): void;
9
+ /**
10
+ * Find the best matching locale from available options.
11
+ * Handles language codes and regional variants (e.g., 'en-US' -> 'en').
12
+ */
13
+ declare function getBestMatchingLocale(preferredLocale: string, supportedLocales: string[], fallback: string): string;
14
+
15
+ interface TranslationsMap {
16
+ [locale: string]: {
17
+ [key: string]: string;
18
+ };
19
+ }
20
+ interface OrdinalSuffixes {
21
+ zero?: string;
22
+ one?: string;
23
+ two?: string;
24
+ few?: string;
25
+ many?: string;
26
+ other: string;
27
+ }
28
+ /**
29
+ * Discriminated union for locale ordinal data in the translation bundle.
30
+ *
31
+ * - `suffix`: Ordinals formed by number + suffix (e.g. "1st", "1er", "1.").
32
+ * The `#` placeholder is replaced with the rank at runtime.
33
+ * - `word`: Ordinals are full words (Arabic, Hebrew). Gender-keyed maps from rank → word.
34
+ * Ranks not present in the map fall back to String(value).
35
+ */
36
+ type OrdinalForms = {
37
+ type: "suffix";
38
+ suffixes: OrdinalSuffixes;
39
+ } | {
40
+ type: "word";
41
+ words: Record<string, Record<number, string>>;
42
+ };
43
+ interface LocaleInfo {
44
+ nativeName: string;
45
+ dir?: "rtl";
46
+ currencyCode?: string;
47
+ ordinalForms?: OrdinalForms;
48
+ }
49
+ interface LocalesMap {
50
+ [localeCode: string]: LocaleInfo;
51
+ }
52
+ type FormatMode = "number" | "integer" | "percent" | "compact" | "currency" | "date" | "time" | "datetime";
53
+ interface TOptions {
54
+ /** Context string for disambiguation (same text, different meaning). */
55
+ context?: string;
56
+ /** Formality level for translation. */
57
+ formality?: "formal" | "informal" | "auto";
58
+ /** Stable translation key. When provided, used as lookup key instead of hashing the message text. */
59
+ id?: string;
60
+ }
61
+ /**
62
+ * Canonical translation bundle format shared by the build plugin and CLI.
63
+ * Both read and write this shape — keeps cache files identical regardless of
64
+ * which tool produced them.
65
+ *
66
+ * translations: locale → sourceKey (hash) → translated text
67
+ * config.locales: locale metadata snapshot for the runtime
68
+ */
69
+ interface VocoderTranslationData {
70
+ config: {
71
+ sourceLocale: string;
72
+ targetLocales: string[];
73
+ locales: Record<string, LocaleInfo>;
74
+ };
75
+ translations: Record<string, Record<string, string>>;
76
+ updatedAt: string | null;
77
+ }
78
+
79
+ interface FormatValueOptions {
80
+ currency?: string;
81
+ dateStyle?: "full" | "long" | "medium" | "short";
82
+ timeStyle?: "full" | "long" | "medium" | "short";
83
+ }
84
+ declare function formatValue(value: string | number | Date, format: FormatMode, locale: string, options?: FormatValueOptions): string;
85
+
86
+ /**
87
+ * FNV-1a 32-bit hash for generating stable message IDs from source text.
88
+ *
89
+ * Works identically in Node.js and browsers (no platform APIs).
90
+ * Used by the extractor (build time) and the React runtime (browser) — both
91
+ * always produce the same key for the same source text.
92
+ *
93
+ * Output: 7 base-36 chars (~2.2 billion values).
94
+ * Collision probability ≈ 0.002% for 10K strings (birthday problem).
95
+ * Add `context` to disambiguate identical strings with different meanings.
96
+ * Add `formality` ("formal" | "informal") to produce separate keys for
97
+ * register variants — "neutral", "auto", and undefined hash identically.
98
+ *
99
+ * Separators: \x04 (ASCII EOT) for context, \x05 (ASCII ENQ) for formality.
100
+ */
101
+ declare function generateMessageHash(text: string, context?: string, formality?: string): string;
102
+
103
+ /**
104
+ * Format an ICU MessageFormat string with the given values and locale.
105
+ * Returns the raw `text` unchanged if parsing or formatting throws — the
106
+ * caller always gets a string, never an exception.
107
+ */
108
+ declare function formatICU(text: string, values: Record<string, any>, locale?: string): string;
109
+ /**
110
+ * Rewrite any embedded `selectordinal` nodes in an ICU string using
111
+ * `ordinalForms` from the locale bundle, before passing the string to
112
+ * `formatICU`.
113
+ *
114
+ * Returns `icu` unchanged when:
115
+ * - the string contains no "selectordinal" substring (fast path — most strings)
116
+ * - parsing throws (safe fallback to whatever formatICU receives)
117
+ *
118
+ * @param icu - Translated ICU string (may contain garbage ordinal branches from provider)
119
+ * @param ordinalForms - Locale's ordinalForms from the compiled bundle
120
+ * @param values - Runtime interpolation values (needed for word-based rank lookup)
121
+ */
122
+ declare function rewriteSelectordinalInICU(icu: string, ordinalForms: OrdinalForms, values: Record<string, any>): string;
123
+
124
+ export { type FormatMode, type FormatValueOptions, type LocaleInfo, type LocalesMap, type OrdinalForms, type OrdinalSuffixes, type TOptions, type TranslationsMap, type VocoderTranslationData, formatICU, formatValue, generateMessageHash, getBestMatchingLocale, getCookie, rewriteSelectordinalInICU, setCookie };
@@ -0,0 +1,124 @@
1
+ declare function getCookie(name: string, cookieString?: string): string | null;
2
+ declare function setCookie(name: string, value: string, options?: {
3
+ maxAge?: number;
4
+ path?: string;
5
+ domain?: string;
6
+ sameSite?: "Strict" | "Lax" | "None";
7
+ secure?: boolean;
8
+ }): void;
9
+ /**
10
+ * Find the best matching locale from available options.
11
+ * Handles language codes and regional variants (e.g., 'en-US' -> 'en').
12
+ */
13
+ declare function getBestMatchingLocale(preferredLocale: string, supportedLocales: string[], fallback: string): string;
14
+
15
+ interface TranslationsMap {
16
+ [locale: string]: {
17
+ [key: string]: string;
18
+ };
19
+ }
20
+ interface OrdinalSuffixes {
21
+ zero?: string;
22
+ one?: string;
23
+ two?: string;
24
+ few?: string;
25
+ many?: string;
26
+ other: string;
27
+ }
28
+ /**
29
+ * Discriminated union for locale ordinal data in the translation bundle.
30
+ *
31
+ * - `suffix`: Ordinals formed by number + suffix (e.g. "1st", "1er", "1.").
32
+ * The `#` placeholder is replaced with the rank at runtime.
33
+ * - `word`: Ordinals are full words (Arabic, Hebrew). Gender-keyed maps from rank → word.
34
+ * Ranks not present in the map fall back to String(value).
35
+ */
36
+ type OrdinalForms = {
37
+ type: "suffix";
38
+ suffixes: OrdinalSuffixes;
39
+ } | {
40
+ type: "word";
41
+ words: Record<string, Record<number, string>>;
42
+ };
43
+ interface LocaleInfo {
44
+ nativeName: string;
45
+ dir?: "rtl";
46
+ currencyCode?: string;
47
+ ordinalForms?: OrdinalForms;
48
+ }
49
+ interface LocalesMap {
50
+ [localeCode: string]: LocaleInfo;
51
+ }
52
+ type FormatMode = "number" | "integer" | "percent" | "compact" | "currency" | "date" | "time" | "datetime";
53
+ interface TOptions {
54
+ /** Context string for disambiguation (same text, different meaning). */
55
+ context?: string;
56
+ /** Formality level for translation. */
57
+ formality?: "formal" | "informal" | "auto";
58
+ /** Stable translation key. When provided, used as lookup key instead of hashing the message text. */
59
+ id?: string;
60
+ }
61
+ /**
62
+ * Canonical translation bundle format shared by the build plugin and CLI.
63
+ * Both read and write this shape — keeps cache files identical regardless of
64
+ * which tool produced them.
65
+ *
66
+ * translations: locale → sourceKey (hash) → translated text
67
+ * config.locales: locale metadata snapshot for the runtime
68
+ */
69
+ interface VocoderTranslationData {
70
+ config: {
71
+ sourceLocale: string;
72
+ targetLocales: string[];
73
+ locales: Record<string, LocaleInfo>;
74
+ };
75
+ translations: Record<string, Record<string, string>>;
76
+ updatedAt: string | null;
77
+ }
78
+
79
+ interface FormatValueOptions {
80
+ currency?: string;
81
+ dateStyle?: "full" | "long" | "medium" | "short";
82
+ timeStyle?: "full" | "long" | "medium" | "short";
83
+ }
84
+ declare function formatValue(value: string | number | Date, format: FormatMode, locale: string, options?: FormatValueOptions): string;
85
+
86
+ /**
87
+ * FNV-1a 32-bit hash for generating stable message IDs from source text.
88
+ *
89
+ * Works identically in Node.js and browsers (no platform APIs).
90
+ * Used by the extractor (build time) and the React runtime (browser) — both
91
+ * always produce the same key for the same source text.
92
+ *
93
+ * Output: 7 base-36 chars (~2.2 billion values).
94
+ * Collision probability ≈ 0.002% for 10K strings (birthday problem).
95
+ * Add `context` to disambiguate identical strings with different meanings.
96
+ * Add `formality` ("formal" | "informal") to produce separate keys for
97
+ * register variants — "neutral", "auto", and undefined hash identically.
98
+ *
99
+ * Separators: \x04 (ASCII EOT) for context, \x05 (ASCII ENQ) for formality.
100
+ */
101
+ declare function generateMessageHash(text: string, context?: string, formality?: string): string;
102
+
103
+ /**
104
+ * Format an ICU MessageFormat string with the given values and locale.
105
+ * Returns the raw `text` unchanged if parsing or formatting throws — the
106
+ * caller always gets a string, never an exception.
107
+ */
108
+ declare function formatICU(text: string, values: Record<string, any>, locale?: string): string;
109
+ /**
110
+ * Rewrite any embedded `selectordinal` nodes in an ICU string using
111
+ * `ordinalForms` from the locale bundle, before passing the string to
112
+ * `formatICU`.
113
+ *
114
+ * Returns `icu` unchanged when:
115
+ * - the string contains no "selectordinal" substring (fast path — most strings)
116
+ * - parsing throws (safe fallback to whatever formatICU receives)
117
+ *
118
+ * @param icu - Translated ICU string (may contain garbage ordinal branches from provider)
119
+ * @param ordinalForms - Locale's ordinalForms from the compiled bundle
120
+ * @param values - Runtime interpolation values (needed for word-based rank lookup)
121
+ */
122
+ declare function rewriteSelectordinalInICU(icu: string, ordinalForms: OrdinalForms, values: Record<string, any>): string;
123
+
124
+ export { type FormatMode, type FormatValueOptions, type LocaleInfo, type LocalesMap, type OrdinalForms, type OrdinalSuffixes, type TOptions, type TranslationsMap, type VocoderTranslationData, formatICU, formatValue, generateMessageHash, getBestMatchingLocale, getCookie, rewriteSelectordinalInICU, setCookie };
package/dist/index.js ADDED
@@ -0,0 +1,357 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __defProps = Object.defineProperties;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
7
+ var __getOwnPropNames = Object.getOwnPropertyNames;
8
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
9
+ var __getProtoOf = Object.getPrototypeOf;
10
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
11
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
12
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __spreadValues = (a, b) => {
14
+ for (var prop in b || (b = {}))
15
+ if (__hasOwnProp.call(b, prop))
16
+ __defNormalProp(a, prop, b[prop]);
17
+ if (__getOwnPropSymbols)
18
+ for (var prop of __getOwnPropSymbols(b)) {
19
+ if (__propIsEnum.call(b, prop))
20
+ __defNormalProp(a, prop, b[prop]);
21
+ }
22
+ return a;
23
+ };
24
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
25
+ var __export = (target, all) => {
26
+ for (var name in all)
27
+ __defProp(target, name, { get: all[name], enumerable: true });
28
+ };
29
+ var __copyProps = (to, from, except, desc) => {
30
+ if (from && typeof from === "object" || typeof from === "function") {
31
+ for (let key of __getOwnPropNames(from))
32
+ if (!__hasOwnProp.call(to, key) && key !== except)
33
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
34
+ }
35
+ return to;
36
+ };
37
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
38
+ // If the importer is in node compatibility mode or this is not an ESM
39
+ // file that has been converted to a CommonJS file using a Babel-
40
+ // compatible transform (i.e. "__esModule" has not been set), then set
41
+ // "default" to the CommonJS "module.exports" for node compatibility.
42
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
43
+ mod
44
+ ));
45
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
46
+
47
+ // src/index.ts
48
+ var src_exports = {};
49
+ __export(src_exports, {
50
+ formatICU: () => formatICU,
51
+ formatValue: () => formatValue,
52
+ generateMessageHash: () => generateMessageHash,
53
+ getBestMatchingLocale: () => getBestMatchingLocale,
54
+ getCookie: () => getCookie,
55
+ rewriteSelectordinalInICU: () => rewriteSelectordinalInICU,
56
+ setCookie: () => setCookie
57
+ });
58
+ module.exports = __toCommonJS(src_exports);
59
+
60
+ // src/cookies.ts
61
+ function getCookie(name, cookieString) {
62
+ var _a;
63
+ const cookies = cookieString || (typeof document !== "undefined" ? document.cookie : "");
64
+ if (!cookies) {
65
+ return null;
66
+ }
67
+ const value = (_a = cookies.split("; ").find((row) => row.startsWith(`${name}=`))) == null ? void 0 : _a.split("=")[1];
68
+ return value ? decodeURIComponent(value) : null;
69
+ }
70
+ function setCookie(name, value, options = {}) {
71
+ if (typeof document === "undefined") {
72
+ return;
73
+ }
74
+ const {
75
+ maxAge = 365 * 24 * 60 * 60,
76
+ path = "/",
77
+ sameSite = "Lax",
78
+ secure = typeof window !== "undefined" && window.location.protocol === "https:"
79
+ } = options;
80
+ let cookieString = `${name}=${encodeURIComponent(value)}`;
81
+ if (maxAge) {
82
+ cookieString += `; Max-Age=${maxAge}`;
83
+ }
84
+ if (path) {
85
+ cookieString += `; Path=${path}`;
86
+ }
87
+ if (options.domain) {
88
+ cookieString += `; Domain=${options.domain}`;
89
+ }
90
+ if (sameSite) {
91
+ cookieString += `; SameSite=${sameSite}`;
92
+ }
93
+ if (secure) {
94
+ cookieString += "; Secure";
95
+ }
96
+ document.cookie = cookieString;
97
+ }
98
+ function getBestMatchingLocale(preferredLocale, supportedLocales, fallback) {
99
+ if (supportedLocales.includes(preferredLocale)) {
100
+ return preferredLocale;
101
+ }
102
+ const languageCode = preferredLocale.split("-")[0];
103
+ if (languageCode && supportedLocales.includes(languageCode)) {
104
+ return languageCode;
105
+ }
106
+ const similarLocale = supportedLocales.find(
107
+ (locale) => locale.startsWith(`${languageCode}-`)
108
+ );
109
+ if (similarLocale) {
110
+ return similarLocale;
111
+ }
112
+ return fallback;
113
+ }
114
+
115
+ // src/format-value.ts
116
+ var nfCache = /* @__PURE__ */ new Map();
117
+ var dtfCache = /* @__PURE__ */ new Map();
118
+ function getNF(locale, options) {
119
+ var _a, _b, _c, _d;
120
+ const key = `${locale}:${(_a = options.style) != null ? _a : ""}:${(_b = options.currency) != null ? _b : ""}:${(_c = options.notation) != null ? _c : ""}:${(_d = options.maximumFractionDigits) != null ? _d : ""}`;
121
+ let fmt = nfCache.get(key);
122
+ if (!fmt) {
123
+ fmt = new Intl.NumberFormat(locale, options);
124
+ nfCache.set(key, fmt);
125
+ }
126
+ return fmt;
127
+ }
128
+ function getDTF(locale, options) {
129
+ var _a, _b;
130
+ const key = `${locale}:${(_a = options.dateStyle) != null ? _a : ""}:${(_b = options.timeStyle) != null ? _b : ""}`;
131
+ let fmt = dtfCache.get(key);
132
+ if (!fmt) {
133
+ fmt = new Intl.DateTimeFormat(locale, options);
134
+ dtfCache.set(key, fmt);
135
+ }
136
+ return fmt;
137
+ }
138
+ function formatValue(value, format, locale, options = {}) {
139
+ const { currency, dateStyle = "medium", timeStyle = "short" } = options;
140
+ const num = Number(value);
141
+ const date = value instanceof Date ? value : new Date(value);
142
+ switch (format) {
143
+ case "number":
144
+ return getNF(locale, {}).format(num);
145
+ case "integer":
146
+ return getNF(locale, { maximumFractionDigits: 0 }).format(num);
147
+ case "percent":
148
+ return getNF(locale, { style: "percent" }).format(num);
149
+ case "compact":
150
+ return getNF(locale, { notation: "compact" }).format(num);
151
+ case "currency": {
152
+ if (!currency) {
153
+ if (process.env.NODE_ENV === "development") {
154
+ console.warn('[vocoder] format="currency" requires a currency prop');
155
+ }
156
+ return String(value);
157
+ }
158
+ return getNF(locale, { style: "currency", currency }).format(num);
159
+ }
160
+ case "date":
161
+ return getDTF(locale, { dateStyle }).format(date);
162
+ case "time":
163
+ return getDTF(locale, { timeStyle }).format(date);
164
+ case "datetime":
165
+ return getDTF(locale, { dateStyle, timeStyle }).format(date);
166
+ default:
167
+ return String(value);
168
+ }
169
+ }
170
+
171
+ // src/hash.ts
172
+ function generateMessageHash(text, context, formality) {
173
+ let input = context ? `${text}${context}` : text;
174
+ if (formality === "formal" || formality === "informal") {
175
+ input += `${formality}`;
176
+ }
177
+ let h = 2166136261 >>> 0;
178
+ for (let i = 0; i < input.length; i++) {
179
+ h = Math.imul(h ^ input.charCodeAt(i), 16777619) >>> 0;
180
+ }
181
+ return h.toString(36).padStart(7, "0");
182
+ }
183
+
184
+ // src/icu.ts
185
+ var import_intl_messageformat = __toESM(require("intl-messageformat"));
186
+ var import_icu_messageformat_parser = require("@formatjs/icu-messageformat-parser");
187
+ var imfCache = /* @__PURE__ */ new Map();
188
+ function getIMF(text, locale) {
189
+ const key = `${locale}:${text}`;
190
+ let msg = imfCache.get(key);
191
+ if (!msg) {
192
+ msg = new import_intl_messageformat.default(text, locale, void 0, { ignoreTag: true });
193
+ imfCache.set(key, msg);
194
+ }
195
+ return msg;
196
+ }
197
+ function formatICU(text, values, locale = "en") {
198
+ try {
199
+ const result = getIMF(text, locale.toLowerCase()).format(values);
200
+ return typeof result === "string" ? result : result.join("");
201
+ } catch (error) {
202
+ if (process.env.NODE_ENV !== "production") {
203
+ console.error(
204
+ `[vocoder] ICU formatting error for locale "${locale}":`,
205
+ error,
206
+ "\n ICU:",
207
+ text,
208
+ "\n values:",
209
+ values
210
+ );
211
+ }
212
+ return text;
213
+ }
214
+ }
215
+ var CLDR_ORDINAL_ORDER = [
216
+ "zero",
217
+ "one",
218
+ "two",
219
+ "few",
220
+ "many",
221
+ "other"
222
+ ];
223
+ function printICU(elements) {
224
+ return elements.map(printElement).join("");
225
+ }
226
+ function printElement(el) {
227
+ var _a, _b, _c;
228
+ switch (el.type) {
229
+ case import_icu_messageformat_parser.TYPE.literal:
230
+ return el.value;
231
+ case import_icu_messageformat_parser.TYPE.argument:
232
+ return `{${el.value}}`;
233
+ case import_icu_messageformat_parser.TYPE.pound:
234
+ return "#";
235
+ case import_icu_messageformat_parser.TYPE.number: {
236
+ if (!el.style) return `{${el.value}, number}`;
237
+ const style = typeof el.style === "string" ? el.style : `::${(_a = el.style.parsedOptions) != null ? _a : ""}`;
238
+ return `{${el.value}, number, ${style}}`;
239
+ }
240
+ case import_icu_messageformat_parser.TYPE.date: {
241
+ if (!el.style) return `{${el.value}, date}`;
242
+ const style = typeof el.style === "string" ? el.style : `::${(_b = el.style.parsedOptions) != null ? _b : ""}`;
243
+ return `{${el.value}, date, ${style}}`;
244
+ }
245
+ case import_icu_messageformat_parser.TYPE.time: {
246
+ if (!el.style) return `{${el.value}, time}`;
247
+ const style = typeof el.style === "string" ? el.style : `::${(_c = el.style.parsedOptions) != null ? _c : ""}`;
248
+ return `{${el.value}, time, ${style}}`;
249
+ }
250
+ case import_icu_messageformat_parser.TYPE.select: {
251
+ const options = Object.entries(
252
+ el.options
253
+ ).map(([k, v]) => `${k} {${printICU(v.value)}}`).join(" ");
254
+ return `{${el.value}, select, ${options}}`;
255
+ }
256
+ case import_icu_messageformat_parser.TYPE.plural: {
257
+ const pluralEl = el;
258
+ const pluralType = pluralEl.pluralType === "ordinal" ? "selectordinal" : "plural";
259
+ const offset = pluralEl.offset !== 0 ? `offset:${pluralEl.offset} ` : "";
260
+ const options = Object.entries(pluralEl.options).map(([k, v]) => `${k} {${printICU(v.value)}}`).join(" ");
261
+ return `{${pluralEl.value}, ${pluralType}, ${offset}${options}}`;
262
+ }
263
+ case import_icu_messageformat_parser.TYPE.tag: {
264
+ const children = printICU(el.children);
265
+ return `<${el.value}>${children}</${el.value}>`;
266
+ }
267
+ default:
268
+ return "";
269
+ }
270
+ }
271
+ function rewriteElements(elements, forms, values) {
272
+ return elements.flatMap((el) => {
273
+ if ((0, import_icu_messageformat_parser.isPluralElement)(el) && el.pluralType === "ordinal") {
274
+ return [rewriteSelectordinalElement(el, forms, values)];
275
+ }
276
+ if ((0, import_icu_messageformat_parser.isSelectElement)(el)) {
277
+ const options = {};
278
+ for (const [key, opt] of Object.entries(
279
+ el.options
280
+ )) {
281
+ options[key] = { value: rewriteElements(opt.value, forms, values) };
282
+ }
283
+ return [__spreadProps(__spreadValues({}, el), { options })];
284
+ }
285
+ if ((0, import_icu_messageformat_parser.isPluralElement)(el)) {
286
+ const options = {};
287
+ for (const [key, opt] of Object.entries(el.options)) {
288
+ options[key] = { value: rewriteElements(opt.value, forms, values) };
289
+ }
290
+ return [__spreadProps(__spreadValues({}, el), { options })];
291
+ }
292
+ if ((0, import_icu_messageformat_parser.isTagElement)(el)) {
293
+ return [
294
+ __spreadProps(__spreadValues({}, el), {
295
+ children: rewriteElements(
296
+ el.children,
297
+ forms,
298
+ values
299
+ )
300
+ })
301
+ ];
302
+ }
303
+ return [el];
304
+ });
305
+ }
306
+ function rewriteSelectordinalElement(el, forms, values) {
307
+ var _a;
308
+ if (forms.type === "suffix") {
309
+ const newOptions = {};
310
+ for (const cat of CLDR_ORDINAL_ORDER) {
311
+ const pattern = forms.suffixes[cat];
312
+ if (pattern === void 0) continue;
313
+ const poundIdx = pattern.indexOf("#");
314
+ const parts = [];
315
+ if (poundIdx === -1) {
316
+ parts.push({ type: import_icu_messageformat_parser.TYPE.literal, value: pattern });
317
+ } else {
318
+ if (poundIdx > 0)
319
+ parts.push({
320
+ type: import_icu_messageformat_parser.TYPE.literal,
321
+ value: pattern.slice(0, poundIdx)
322
+ });
323
+ parts.push({ type: import_icu_messageformat_parser.TYPE.pound });
324
+ if (poundIdx < pattern.length - 1)
325
+ parts.push({
326
+ type: import_icu_messageformat_parser.TYPE.literal,
327
+ value: pattern.slice(poundIdx + 1)
328
+ });
329
+ }
330
+ newOptions[cat] = { value: parts };
331
+ }
332
+ return __spreadProps(__spreadValues({}, el), { options: newOptions });
333
+ }
334
+ if (forms.type === "word") {
335
+ const rank = values[el.value];
336
+ if (typeof rank === "number") {
337
+ const genderMap = (_a = forms.words["masculine"]) != null ? _a : Object.values(forms.words)[0];
338
+ const word = genderMap == null ? void 0 : genderMap[rank];
339
+ return {
340
+ type: import_icu_messageformat_parser.TYPE.literal,
341
+ value: word != null ? word : String(rank)
342
+ };
343
+ }
344
+ }
345
+ return el;
346
+ }
347
+ function rewriteSelectordinalInICU(icu, ordinalForms, values) {
348
+ if (!icu.includes("selectordinal")) return icu;
349
+ try {
350
+ const ast = (0, import_icu_messageformat_parser.parse)(icu, { captureLocation: false });
351
+ const rewritten = rewriteElements(ast, ordinalForms, values);
352
+ return printICU(rewritten);
353
+ } catch (e) {
354
+ return icu;
355
+ }
356
+ }
357
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/cookies.ts","../src/format-value.ts","../src/hash.ts","../src/icu.ts"],"sourcesContent":["export { getCookie, getBestMatchingLocale, setCookie } from \"./cookies\";\nexport { formatValue } from \"./format-value\";\nexport type { FormatValueOptions } from \"./format-value\";\nexport { generateMessageHash } from \"./hash\";\nexport { formatICU, rewriteSelectordinalInICU } from \"./icu\";\nexport type {\n\tFormatMode,\n\tLocaleInfo,\n\tLocalesMap,\n\tOrdinalForms,\n\tOrdinalSuffixes,\n\tTOptions,\n\tTranslationsMap,\n\tVocoderTranslationData,\n} from \"./types\";\n","export function getCookie(name: string, cookieString?: string): string | null {\n\tconst cookies =\n\t\tcookieString || (typeof document !== \"undefined\" ? document.cookie : \"\");\n\n\tif (!cookies) {\n\t\treturn null;\n\t}\n\n\tconst value = cookies\n\t\t.split(\"; \")\n\t\t.find((row: string) => row.startsWith(`${name}=`))\n\t\t?.split(\"=\")[1];\n\n\treturn value ? decodeURIComponent(value) : null;\n}\n\nexport function setCookie(\n\tname: string,\n\tvalue: string,\n\toptions: {\n\t\tmaxAge?: number;\n\t\tpath?: string;\n\t\tdomain?: string;\n\t\tsameSite?: \"Strict\" | \"Lax\" | \"None\";\n\t\tsecure?: boolean;\n\t} = {},\n): void {\n\tif (typeof document === \"undefined\") {\n\t\treturn;\n\t}\n\n\tconst {\n\t\tmaxAge = 365 * 24 * 60 * 60,\n\t\tpath = \"/\",\n\t\tsameSite = \"Lax\",\n\t\tsecure = typeof window !== \"undefined\" &&\n\t\t\twindow.location.protocol === \"https:\",\n\t} = options;\n\n\tlet cookieString = `${name}=${encodeURIComponent(value)}`;\n\n\tif (maxAge) {\n\t\tcookieString += `; Max-Age=${maxAge}`;\n\t}\n\n\tif (path) {\n\t\tcookieString += `; Path=${path}`;\n\t}\n\n\tif (options.domain) {\n\t\tcookieString += `; Domain=${options.domain}`;\n\t}\n\n\tif (sameSite) {\n\t\tcookieString += `; SameSite=${sameSite}`;\n\t}\n\n\tif (secure) {\n\t\tcookieString += \"; Secure\";\n\t}\n\n\tdocument.cookie = cookieString;\n}\n\n/**\n * Find the best matching locale from available options.\n * Handles language codes and regional variants (e.g., 'en-US' -> 'en').\n */\nexport function getBestMatchingLocale(\n\tpreferredLocale: string,\n\tsupportedLocales: string[],\n\tfallback: string,\n): string {\n\tif (supportedLocales.includes(preferredLocale)) {\n\t\treturn preferredLocale;\n\t}\n\n\tconst languageCode = preferredLocale.split(\"-\")[0];\n\tif (languageCode && supportedLocales.includes(languageCode)) {\n\t\treturn languageCode;\n\t}\n\n\tconst similarLocale = supportedLocales.find((locale: string) =>\n\t\tlocale.startsWith(`${languageCode}-`),\n\t);\n\tif (similarLocale) {\n\t\treturn similarLocale;\n\t}\n\n\treturn fallback;\n}\n","import type { FormatMode } from \"./types\";\n\nexport interface FormatValueOptions {\n\tcurrency?: string;\n\tdateStyle?: \"full\" | \"long\" | \"medium\" | \"short\";\n\ttimeStyle?: \"full\" | \"long\" | \"medium\" | \"short\";\n}\n\nconst nfCache = new Map<string, Intl.NumberFormat>();\nconst dtfCache = new Map<string, Intl.DateTimeFormat>();\n\nfunction getNF(locale: string, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n\tconst key = `${locale}:${options.style ?? \"\"}:${options.currency ?? \"\"}:${options.notation ?? \"\"}:${options.maximumFractionDigits ?? \"\"}`;\n\tlet fmt = nfCache.get(key);\n\tif (!fmt) {\n\t\tfmt = new Intl.NumberFormat(locale, options);\n\t\tnfCache.set(key, fmt);\n\t}\n\treturn fmt;\n}\n\nfunction getDTF(locale: string, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n\tconst key = `${locale}:${options.dateStyle ?? \"\"}:${options.timeStyle ?? \"\"}`;\n\tlet fmt = dtfCache.get(key);\n\tif (!fmt) {\n\t\tfmt = new Intl.DateTimeFormat(locale, options);\n\t\tdtfCache.set(key, fmt);\n\t}\n\treturn fmt;\n}\n\nexport function formatValue(\n\tvalue: string | number | Date,\n\tformat: FormatMode,\n\tlocale: string,\n\toptions: FormatValueOptions = {},\n): string {\n\tconst { currency, dateStyle = \"medium\", timeStyle = \"short\" } = options;\n\tconst num = Number(value);\n\tconst date = value instanceof Date ? value : new Date(value as string | number);\n\n\tswitch (format) {\n\t\tcase \"number\":\n\t\t\treturn getNF(locale, {}).format(num);\n\t\tcase \"integer\":\n\t\t\treturn getNF(locale, { maximumFractionDigits: 0 }).format(num);\n\t\tcase \"percent\":\n\t\t\treturn getNF(locale, { style: \"percent\" }).format(num);\n\t\tcase \"compact\":\n\t\t\treturn getNF(locale, { notation: \"compact\" }).format(num);\n\t\tcase \"currency\": {\n\t\t\tif (!currency) {\n\t\t\t\tif (process.env.NODE_ENV === \"development\") {\n\t\t\t\t\tconsole.warn('[vocoder] format=\"currency\" requires a currency prop');\n\t\t\t\t}\n\t\t\t\treturn String(value);\n\t\t\t}\n\t\t\treturn getNF(locale, { style: \"currency\", currency }).format(num);\n\t\t}\n\t\tcase \"date\":\n\t\t\treturn getDTF(locale, { dateStyle }).format(date);\n\t\tcase \"time\":\n\t\t\treturn getDTF(locale, { timeStyle }).format(date);\n\t\tcase \"datetime\":\n\t\t\treturn getDTF(locale, { dateStyle, timeStyle }).format(date);\n\t\tdefault:\n\t\t\treturn String(value);\n\t}\n}\n","/**\n * FNV-1a 32-bit hash for generating stable message IDs from source text.\n *\n * Works identically in Node.js and browsers (no platform APIs).\n * Used by the extractor (build time) and the React runtime (browser) — both\n * always produce the same key for the same source text.\n *\n * Output: 7 base-36 chars (~2.2 billion values).\n * Collision probability ≈ 0.002% for 10K strings (birthday problem).\n * Add `context` to disambiguate identical strings with different meanings.\n * Add `formality` (\"formal\" | \"informal\") to produce separate keys for\n * register variants — \"neutral\", \"auto\", and undefined hash identically.\n *\n * Separators: \\x04 (ASCII EOT) for context, \\x05 (ASCII ENQ) for formality.\n */\nexport function generateMessageHash(\n\ttext: string,\n\tcontext?: string,\n\tformality?: string,\n): string {\n\tlet input = context ? `${text}\\x04${context}` : text;\n\tif (formality === \"formal\" || formality === \"informal\") {\n\t\tinput += `\\x05${formality}`;\n\t}\n\tlet h = 2166136261 >>> 0;\n\tfor (let i = 0; i < input.length; i++) {\n\t\th = Math.imul(h ^ input.charCodeAt(i), 16777619) >>> 0;\n\t}\n\treturn h.toString(36).padStart(7, \"0\");\n}\n","import IntlMessageFormat from \"intl-messageformat\";\nimport {\n\tisPluralElement,\n\tisSelectElement,\n\tisTagElement,\n\tparse,\n\tTYPE,\n} from \"@formatjs/icu-messageformat-parser\";\nimport type {\n\tLiteralElement,\n\tMessageFormatElement,\n\tPluralElement,\n} from \"@formatjs/icu-messageformat-parser\";\nimport type { OrdinalForms } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// IntlMessageFormat cache — keyed by \"locale:text\"\n// ---------------------------------------------------------------------------\n\nconst imfCache = new Map<string, IntlMessageFormat>();\n\nfunction getIMF(text: string, locale: string): IntlMessageFormat {\n\tconst key = `${locale}:${text}`;\n\tlet msg = imfCache.get(key);\n\tif (!msg) {\n\t\t// ignoreTag: true — component placeholders (<c0>, <c1>) are handled\n\t\t// by formatElements, not by IMF. IMF handles only ICU primitives.\n\t\tmsg = new IntlMessageFormat(text, locale, undefined, { ignoreTag: true });\n\t\timfCache.set(key, msg);\n\t}\n\treturn msg;\n}\n\n/**\n * Format an ICU MessageFormat string with the given values and locale.\n * Returns the raw `text` unchanged if parsing or formatting throws — the\n * caller always gets a string, never an exception.\n */\nexport function formatICU(\n\ttext: string,\n\tvalues: Record<string, any>,\n\tlocale: string = \"en\",\n): string {\n\ttry {\n\t\tconst result = getIMF(text, locale.toLowerCase()).format(values);\n\t\treturn typeof result === \"string\" ? result : (result as unknown[]).join(\"\");\n\t} catch (error) {\n\t\tif (process.env.NODE_ENV !== \"production\") {\n\t\t\tconsole.error(\n\t\t\t\t`[vocoder] ICU formatting error for locale \"${locale}\":`,\n\t\t\t\terror,\n\t\t\t\t\"\\n ICU:\", text,\n\t\t\t\t\"\\n values:\", values,\n\t\t\t);\n\t\t}\n\t\treturn text;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Embedded selectordinal rewriting — Bug 1 fix\n//\n// The translation pipeline's ordinal DB fast path (tryBuildOrdinalFromDB)\n// only applies when a selectordinal is the SOLE top-level element in the\n// ICU string. When it appears embedded inside a larger sentence (e.g.\n// \"Congrats! your {year, selectordinal, ...} anniversary!\"), the pipeline\n// falls through to the translation provider, which stores garbage branches\n// (e.g. \"1el\", \"1th\", \"1الـ\") in the DB.\n//\n// This function fixes the stored translation at render time by rewriting\n// any selectordinal nodes using the locale's ordinalForms data.\n// ---------------------------------------------------------------------------\n\nconst CLDR_ORDINAL_ORDER = [\n\t\"zero\",\n\t\"one\",\n\t\"two\",\n\t\"few\",\n\t\"many\",\n\t\"other\",\n] as const;\n\nfunction printICU(elements: MessageFormatElement[]): string {\n\treturn elements.map(printElement).join(\"\");\n}\n\nfunction printElement(el: MessageFormatElement): string {\n\tswitch (el.type) {\n\t\tcase TYPE.literal:\n\t\t\treturn (el as LiteralElement).value;\n\t\tcase TYPE.argument:\n\t\t\treturn `{${el.value}}`;\n\t\tcase TYPE.pound:\n\t\t\treturn \"#\";\n\t\tcase TYPE.number: {\n\t\t\tif (!el.style) return `{${el.value}, number}`;\n\t\t\tconst style =\n\t\t\t\ttypeof el.style === \"string\"\n\t\t\t\t\t? el.style\n\t\t\t\t\t: `::${(el.style as Record<string, any>).parsedOptions ?? \"\"}`;\n\t\t\treturn `{${el.value}, number, ${style}}`;\n\t\t}\n\t\tcase TYPE.date: {\n\t\t\tif (!el.style) return `{${el.value}, date}`;\n\t\t\tconst style =\n\t\t\t\ttypeof el.style === \"string\"\n\t\t\t\t\t? el.style\n\t\t\t\t\t: `::${(el.style as Record<string, any>).parsedOptions ?? \"\"}`;\n\t\t\treturn `{${el.value}, date, ${style}}`;\n\t\t}\n\t\tcase TYPE.time: {\n\t\t\tif (!el.style) return `{${el.value}, time}`;\n\t\t\tconst style =\n\t\t\t\ttypeof el.style === \"string\"\n\t\t\t\t\t? el.style\n\t\t\t\t\t: `::${(el.style as Record<string, any>).parsedOptions ?? \"\"}`;\n\t\t\treturn `{${el.value}, time, ${style}}`;\n\t\t}\n\t\tcase TYPE.select: {\n\t\t\tconst options = Object.entries(\n\t\t\t\t(el as Record<string, any>).options as Record<\n\t\t\t\t\tstring,\n\t\t\t\t\t{ value: MessageFormatElement[] }\n\t\t\t\t>,\n\t\t\t)\n\t\t\t\t.map(([k, v]) => `${k} {${printICU(v.value)}}`)\n\t\t\t\t.join(\" \");\n\t\t\treturn `{${el.value}, select, ${options}}`;\n\t\t}\n\t\tcase TYPE.plural: {\n\t\t\tconst pluralEl = el as PluralElement;\n\t\t\tconst pluralType =\n\t\t\t\tpluralEl.pluralType === \"ordinal\" ? \"selectordinal\" : \"plural\";\n\t\t\tconst offset =\n\t\t\t\tpluralEl.offset !== 0 ? `offset:${pluralEl.offset} ` : \"\";\n\t\t\tconst options = Object.entries(pluralEl.options)\n\t\t\t\t.map(([k, v]) => `${k} {${printICU(v.value)}}`)\n\t\t\t\t.join(\" \");\n\t\t\treturn `{${pluralEl.value}, ${pluralType}, ${offset}${options}}`;\n\t\t}\n\t\tcase TYPE.tag: {\n\t\t\tconst children = printICU((el as Record<string, any>).children as MessageFormatElement[]);\n\t\t\treturn `<${el.value}>${children}</${el.value}>`;\n\t\t}\n\t\tdefault:\n\t\t\treturn \"\";\n\t}\n}\n\nfunction rewriteElements(\n\telements: MessageFormatElement[],\n\tforms: OrdinalForms,\n\tvalues: Record<string, any>,\n): MessageFormatElement[] {\n\treturn elements.flatMap((el) => {\n\t\tif (isPluralElement(el) && el.pluralType === \"ordinal\") {\n\t\t\treturn [rewriteSelectordinalElement(el, forms, values)];\n\t\t}\n\t\tif (isSelectElement(el)) {\n\t\t\tconst options: Record<string, { value: MessageFormatElement[] }> = {};\n\t\t\tfor (const [key, opt] of Object.entries(\n\t\t\t\tel.options as Record<string, { value: MessageFormatElement[] }>,\n\t\t\t)) {\n\t\t\t\toptions[key] = { value: rewriteElements(opt.value, forms, values) };\n\t\t\t}\n\t\t\treturn [{ ...el, options } as MessageFormatElement];\n\t\t}\n\t\tif (isPluralElement(el)) {\n\t\t\tconst options: Record<string, { value: MessageFormatElement[] }> = {};\n\t\t\tfor (const [key, opt] of Object.entries(el.options)) {\n\t\t\t\toptions[key] = { value: rewriteElements(opt.value, forms, values) };\n\t\t\t}\n\t\t\treturn [{ ...el, options } as MessageFormatElement];\n\t\t}\n\t\tif (isTagElement(el)) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\t...el,\n\t\t\t\t\tchildren: rewriteElements(\n\t\t\t\t\t\t(el as Record<string, any>).children as MessageFormatElement[],\n\t\t\t\t\t\tforms,\n\t\t\t\t\t\tvalues,\n\t\t\t\t\t),\n\t\t\t\t} as MessageFormatElement,\n\t\t\t];\n\t\t}\n\t\treturn [el];\n\t});\n}\n\nfunction rewriteSelectordinalElement(\n\tel: PluralElement,\n\tforms: OrdinalForms,\n\tvalues: Record<string, any>,\n): MessageFormatElement {\n\tif (forms.type === \"suffix\") {\n\t\tconst newOptions: Record<string, { value: MessageFormatElement[] }> = {};\n\t\tfor (const cat of CLDR_ORDINAL_ORDER) {\n\t\t\tconst pattern = forms.suffixes[cat];\n\t\t\tif (pattern === undefined) continue;\n\t\t\tconst poundIdx = pattern.indexOf(\"#\");\n\t\t\tconst parts: MessageFormatElement[] = [];\n\t\t\tif (poundIdx === -1) {\n\t\t\t\tparts.push({ type: TYPE.literal, value: pattern } as LiteralElement);\n\t\t\t} else {\n\t\t\t\tif (poundIdx > 0)\n\t\t\t\t\tparts.push({\n\t\t\t\t\t\ttype: TYPE.literal,\n\t\t\t\t\t\tvalue: pattern.slice(0, poundIdx),\n\t\t\t\t\t} as LiteralElement);\n\t\t\t\tparts.push({ type: TYPE.pound });\n\t\t\t\tif (poundIdx < pattern.length - 1)\n\t\t\t\t\tparts.push({\n\t\t\t\t\t\ttype: TYPE.literal,\n\t\t\t\t\t\tvalue: pattern.slice(poundIdx + 1),\n\t\t\t\t\t} as LiteralElement);\n\t\t\t}\n\t\t\tnewOptions[cat] = { value: parts };\n\t\t}\n\t\treturn { ...el, options: newOptions };\n\t}\n\n\tif (forms.type === \"word\") {\n\t\tconst rank = values[el.value];\n\t\tif (typeof rank === \"number\") {\n\t\t\tconst genderMap =\n\t\t\t\tforms.words[\"masculine\"] ?? Object.values(forms.words)[0];\n\t\t\tconst word = genderMap?.[rank];\n\t\t\treturn {\n\t\t\t\ttype: TYPE.literal,\n\t\t\t\tvalue: word ?? String(rank),\n\t\t\t} as LiteralElement;\n\t\t}\n\t}\n\n\treturn el;\n}\n\n/**\n * Rewrite any embedded `selectordinal` nodes in an ICU string using\n * `ordinalForms` from the locale bundle, before passing the string to\n * `formatICU`.\n *\n * Returns `icu` unchanged when:\n * - the string contains no \"selectordinal\" substring (fast path — most strings)\n * - parsing throws (safe fallback to whatever formatICU receives)\n *\n * @param icu - Translated ICU string (may contain garbage ordinal branches from provider)\n * @param ordinalForms - Locale's ordinalForms from the compiled bundle\n * @param values - Runtime interpolation values (needed for word-based rank lookup)\n */\nexport function rewriteSelectordinalInICU(\n\ticu: string,\n\tordinalForms: OrdinalForms,\n\tvalues: Record<string, any>,\n): string {\n\tif (!icu.includes(\"selectordinal\")) return icu;\n\n\ttry {\n\t\tconst ast = parse(icu, { captureLocation: false });\n\t\tconst rewritten = rewriteElements(ast, ordinalForms, values);\n\t\treturn printICU(rewritten);\n\t} catch {\n\t\treturn icu;\n\t}\n}\n"],"mappings":"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","names":["IntlMessageFormat"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,333 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+
21
+ // src/cookies.ts
22
+ function getCookie(name, cookieString) {
23
+ var _a;
24
+ const cookies = cookieString || (typeof document !== "undefined" ? document.cookie : "");
25
+ if (!cookies) {
26
+ return null;
27
+ }
28
+ const value = (_a = cookies.split("; ").find((row) => row.startsWith(`${name}=`))) == null ? void 0 : _a.split("=")[1];
29
+ return value ? decodeURIComponent(value) : null;
30
+ }
31
+ function setCookie(name, value, options = {}) {
32
+ if (typeof document === "undefined") {
33
+ return;
34
+ }
35
+ const {
36
+ maxAge = 365 * 24 * 60 * 60,
37
+ path = "/",
38
+ sameSite = "Lax",
39
+ secure = typeof window !== "undefined" && window.location.protocol === "https:"
40
+ } = options;
41
+ let cookieString = `${name}=${encodeURIComponent(value)}`;
42
+ if (maxAge) {
43
+ cookieString += `; Max-Age=${maxAge}`;
44
+ }
45
+ if (path) {
46
+ cookieString += `; Path=${path}`;
47
+ }
48
+ if (options.domain) {
49
+ cookieString += `; Domain=${options.domain}`;
50
+ }
51
+ if (sameSite) {
52
+ cookieString += `; SameSite=${sameSite}`;
53
+ }
54
+ if (secure) {
55
+ cookieString += "; Secure";
56
+ }
57
+ document.cookie = cookieString;
58
+ }
59
+ function getBestMatchingLocale(preferredLocale, supportedLocales, fallback) {
60
+ if (supportedLocales.includes(preferredLocale)) {
61
+ return preferredLocale;
62
+ }
63
+ const languageCode = preferredLocale.split("-")[0];
64
+ if (languageCode && supportedLocales.includes(languageCode)) {
65
+ return languageCode;
66
+ }
67
+ const similarLocale = supportedLocales.find(
68
+ (locale) => locale.startsWith(`${languageCode}-`)
69
+ );
70
+ if (similarLocale) {
71
+ return similarLocale;
72
+ }
73
+ return fallback;
74
+ }
75
+
76
+ // src/format-value.ts
77
+ var nfCache = /* @__PURE__ */ new Map();
78
+ var dtfCache = /* @__PURE__ */ new Map();
79
+ function getNF(locale, options) {
80
+ var _a, _b, _c, _d;
81
+ const key = `${locale}:${(_a = options.style) != null ? _a : ""}:${(_b = options.currency) != null ? _b : ""}:${(_c = options.notation) != null ? _c : ""}:${(_d = options.maximumFractionDigits) != null ? _d : ""}`;
82
+ let fmt = nfCache.get(key);
83
+ if (!fmt) {
84
+ fmt = new Intl.NumberFormat(locale, options);
85
+ nfCache.set(key, fmt);
86
+ }
87
+ return fmt;
88
+ }
89
+ function getDTF(locale, options) {
90
+ var _a, _b;
91
+ const key = `${locale}:${(_a = options.dateStyle) != null ? _a : ""}:${(_b = options.timeStyle) != null ? _b : ""}`;
92
+ let fmt = dtfCache.get(key);
93
+ if (!fmt) {
94
+ fmt = new Intl.DateTimeFormat(locale, options);
95
+ dtfCache.set(key, fmt);
96
+ }
97
+ return fmt;
98
+ }
99
+ function formatValue(value, format, locale, options = {}) {
100
+ const { currency, dateStyle = "medium", timeStyle = "short" } = options;
101
+ const num = Number(value);
102
+ const date = value instanceof Date ? value : new Date(value);
103
+ switch (format) {
104
+ case "number":
105
+ return getNF(locale, {}).format(num);
106
+ case "integer":
107
+ return getNF(locale, { maximumFractionDigits: 0 }).format(num);
108
+ case "percent":
109
+ return getNF(locale, { style: "percent" }).format(num);
110
+ case "compact":
111
+ return getNF(locale, { notation: "compact" }).format(num);
112
+ case "currency": {
113
+ if (!currency) {
114
+ if (process.env.NODE_ENV === "development") {
115
+ console.warn('[vocoder] format="currency" requires a currency prop');
116
+ }
117
+ return String(value);
118
+ }
119
+ return getNF(locale, { style: "currency", currency }).format(num);
120
+ }
121
+ case "date":
122
+ return getDTF(locale, { dateStyle }).format(date);
123
+ case "time":
124
+ return getDTF(locale, { timeStyle }).format(date);
125
+ case "datetime":
126
+ return getDTF(locale, { dateStyle, timeStyle }).format(date);
127
+ default:
128
+ return String(value);
129
+ }
130
+ }
131
+
132
+ // src/hash.ts
133
+ function generateMessageHash(text, context, formality) {
134
+ let input = context ? `${text}${context}` : text;
135
+ if (formality === "formal" || formality === "informal") {
136
+ input += `${formality}`;
137
+ }
138
+ let h = 2166136261 >>> 0;
139
+ for (let i = 0; i < input.length; i++) {
140
+ h = Math.imul(h ^ input.charCodeAt(i), 16777619) >>> 0;
141
+ }
142
+ return h.toString(36).padStart(7, "0");
143
+ }
144
+
145
+ // src/icu.ts
146
+ import IntlMessageFormat from "intl-messageformat";
147
+ import {
148
+ isPluralElement,
149
+ isSelectElement,
150
+ isTagElement,
151
+ parse,
152
+ TYPE
153
+ } from "@formatjs/icu-messageformat-parser";
154
+ var imfCache = /* @__PURE__ */ new Map();
155
+ function getIMF(text, locale) {
156
+ const key = `${locale}:${text}`;
157
+ let msg = imfCache.get(key);
158
+ if (!msg) {
159
+ msg = new IntlMessageFormat(text, locale, void 0, { ignoreTag: true });
160
+ imfCache.set(key, msg);
161
+ }
162
+ return msg;
163
+ }
164
+ function formatICU(text, values, locale = "en") {
165
+ try {
166
+ const result = getIMF(text, locale.toLowerCase()).format(values);
167
+ return typeof result === "string" ? result : result.join("");
168
+ } catch (error) {
169
+ if (process.env.NODE_ENV !== "production") {
170
+ console.error(
171
+ `[vocoder] ICU formatting error for locale "${locale}":`,
172
+ error,
173
+ "\n ICU:",
174
+ text,
175
+ "\n values:",
176
+ values
177
+ );
178
+ }
179
+ return text;
180
+ }
181
+ }
182
+ var CLDR_ORDINAL_ORDER = [
183
+ "zero",
184
+ "one",
185
+ "two",
186
+ "few",
187
+ "many",
188
+ "other"
189
+ ];
190
+ function printICU(elements) {
191
+ return elements.map(printElement).join("");
192
+ }
193
+ function printElement(el) {
194
+ var _a, _b, _c;
195
+ switch (el.type) {
196
+ case TYPE.literal:
197
+ return el.value;
198
+ case TYPE.argument:
199
+ return `{${el.value}}`;
200
+ case TYPE.pound:
201
+ return "#";
202
+ case TYPE.number: {
203
+ if (!el.style) return `{${el.value}, number}`;
204
+ const style = typeof el.style === "string" ? el.style : `::${(_a = el.style.parsedOptions) != null ? _a : ""}`;
205
+ return `{${el.value}, number, ${style}}`;
206
+ }
207
+ case TYPE.date: {
208
+ if (!el.style) return `{${el.value}, date}`;
209
+ const style = typeof el.style === "string" ? el.style : `::${(_b = el.style.parsedOptions) != null ? _b : ""}`;
210
+ return `{${el.value}, date, ${style}}`;
211
+ }
212
+ case TYPE.time: {
213
+ if (!el.style) return `{${el.value}, time}`;
214
+ const style = typeof el.style === "string" ? el.style : `::${(_c = el.style.parsedOptions) != null ? _c : ""}`;
215
+ return `{${el.value}, time, ${style}}`;
216
+ }
217
+ case TYPE.select: {
218
+ const options = Object.entries(
219
+ el.options
220
+ ).map(([k, v]) => `${k} {${printICU(v.value)}}`).join(" ");
221
+ return `{${el.value}, select, ${options}}`;
222
+ }
223
+ case TYPE.plural: {
224
+ const pluralEl = el;
225
+ const pluralType = pluralEl.pluralType === "ordinal" ? "selectordinal" : "plural";
226
+ const offset = pluralEl.offset !== 0 ? `offset:${pluralEl.offset} ` : "";
227
+ const options = Object.entries(pluralEl.options).map(([k, v]) => `${k} {${printICU(v.value)}}`).join(" ");
228
+ return `{${pluralEl.value}, ${pluralType}, ${offset}${options}}`;
229
+ }
230
+ case TYPE.tag: {
231
+ const children = printICU(el.children);
232
+ return `<${el.value}>${children}</${el.value}>`;
233
+ }
234
+ default:
235
+ return "";
236
+ }
237
+ }
238
+ function rewriteElements(elements, forms, values) {
239
+ return elements.flatMap((el) => {
240
+ if (isPluralElement(el) && el.pluralType === "ordinal") {
241
+ return [rewriteSelectordinalElement(el, forms, values)];
242
+ }
243
+ if (isSelectElement(el)) {
244
+ const options = {};
245
+ for (const [key, opt] of Object.entries(
246
+ el.options
247
+ )) {
248
+ options[key] = { value: rewriteElements(opt.value, forms, values) };
249
+ }
250
+ return [__spreadProps(__spreadValues({}, el), { options })];
251
+ }
252
+ if (isPluralElement(el)) {
253
+ const options = {};
254
+ for (const [key, opt] of Object.entries(el.options)) {
255
+ options[key] = { value: rewriteElements(opt.value, forms, values) };
256
+ }
257
+ return [__spreadProps(__spreadValues({}, el), { options })];
258
+ }
259
+ if (isTagElement(el)) {
260
+ return [
261
+ __spreadProps(__spreadValues({}, el), {
262
+ children: rewriteElements(
263
+ el.children,
264
+ forms,
265
+ values
266
+ )
267
+ })
268
+ ];
269
+ }
270
+ return [el];
271
+ });
272
+ }
273
+ function rewriteSelectordinalElement(el, forms, values) {
274
+ var _a;
275
+ if (forms.type === "suffix") {
276
+ const newOptions = {};
277
+ for (const cat of CLDR_ORDINAL_ORDER) {
278
+ const pattern = forms.suffixes[cat];
279
+ if (pattern === void 0) continue;
280
+ const poundIdx = pattern.indexOf("#");
281
+ const parts = [];
282
+ if (poundIdx === -1) {
283
+ parts.push({ type: TYPE.literal, value: pattern });
284
+ } else {
285
+ if (poundIdx > 0)
286
+ parts.push({
287
+ type: TYPE.literal,
288
+ value: pattern.slice(0, poundIdx)
289
+ });
290
+ parts.push({ type: TYPE.pound });
291
+ if (poundIdx < pattern.length - 1)
292
+ parts.push({
293
+ type: TYPE.literal,
294
+ value: pattern.slice(poundIdx + 1)
295
+ });
296
+ }
297
+ newOptions[cat] = { value: parts };
298
+ }
299
+ return __spreadProps(__spreadValues({}, el), { options: newOptions });
300
+ }
301
+ if (forms.type === "word") {
302
+ const rank = values[el.value];
303
+ if (typeof rank === "number") {
304
+ const genderMap = (_a = forms.words["masculine"]) != null ? _a : Object.values(forms.words)[0];
305
+ const word = genderMap == null ? void 0 : genderMap[rank];
306
+ return {
307
+ type: TYPE.literal,
308
+ value: word != null ? word : String(rank)
309
+ };
310
+ }
311
+ }
312
+ return el;
313
+ }
314
+ function rewriteSelectordinalInICU(icu, ordinalForms, values) {
315
+ if (!icu.includes("selectordinal")) return icu;
316
+ try {
317
+ const ast = parse(icu, { captureLocation: false });
318
+ const rewritten = rewriteElements(ast, ordinalForms, values);
319
+ return printICU(rewritten);
320
+ } catch (e) {
321
+ return icu;
322
+ }
323
+ }
324
+ export {
325
+ formatICU,
326
+ formatValue,
327
+ generateMessageHash,
328
+ getBestMatchingLocale,
329
+ getCookie,
330
+ rewriteSelectordinalInICU,
331
+ setCookie
332
+ };
333
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cookies.ts","../src/format-value.ts","../src/hash.ts","../src/icu.ts"],"sourcesContent":["export function getCookie(name: string, cookieString?: string): string | null {\n\tconst cookies =\n\t\tcookieString || (typeof document !== \"undefined\" ? document.cookie : \"\");\n\n\tif (!cookies) {\n\t\treturn null;\n\t}\n\n\tconst value = cookies\n\t\t.split(\"; \")\n\t\t.find((row: string) => row.startsWith(`${name}=`))\n\t\t?.split(\"=\")[1];\n\n\treturn value ? decodeURIComponent(value) : null;\n}\n\nexport function setCookie(\n\tname: string,\n\tvalue: string,\n\toptions: {\n\t\tmaxAge?: number;\n\t\tpath?: string;\n\t\tdomain?: string;\n\t\tsameSite?: \"Strict\" | \"Lax\" | \"None\";\n\t\tsecure?: boolean;\n\t} = {},\n): void {\n\tif (typeof document === \"undefined\") {\n\t\treturn;\n\t}\n\n\tconst {\n\t\tmaxAge = 365 * 24 * 60 * 60,\n\t\tpath = \"/\",\n\t\tsameSite = \"Lax\",\n\t\tsecure = typeof window !== \"undefined\" &&\n\t\t\twindow.location.protocol === \"https:\",\n\t} = options;\n\n\tlet cookieString = `${name}=${encodeURIComponent(value)}`;\n\n\tif (maxAge) {\n\t\tcookieString += `; Max-Age=${maxAge}`;\n\t}\n\n\tif (path) {\n\t\tcookieString += `; Path=${path}`;\n\t}\n\n\tif (options.domain) {\n\t\tcookieString += `; Domain=${options.domain}`;\n\t}\n\n\tif (sameSite) {\n\t\tcookieString += `; SameSite=${sameSite}`;\n\t}\n\n\tif (secure) {\n\t\tcookieString += \"; Secure\";\n\t}\n\n\tdocument.cookie = cookieString;\n}\n\n/**\n * Find the best matching locale from available options.\n * Handles language codes and regional variants (e.g., 'en-US' -> 'en').\n */\nexport function getBestMatchingLocale(\n\tpreferredLocale: string,\n\tsupportedLocales: string[],\n\tfallback: string,\n): string {\n\tif (supportedLocales.includes(preferredLocale)) {\n\t\treturn preferredLocale;\n\t}\n\n\tconst languageCode = preferredLocale.split(\"-\")[0];\n\tif (languageCode && supportedLocales.includes(languageCode)) {\n\t\treturn languageCode;\n\t}\n\n\tconst similarLocale = supportedLocales.find((locale: string) =>\n\t\tlocale.startsWith(`${languageCode}-`),\n\t);\n\tif (similarLocale) {\n\t\treturn similarLocale;\n\t}\n\n\treturn fallback;\n}\n","import type { FormatMode } from \"./types\";\n\nexport interface FormatValueOptions {\n\tcurrency?: string;\n\tdateStyle?: \"full\" | \"long\" | \"medium\" | \"short\";\n\ttimeStyle?: \"full\" | \"long\" | \"medium\" | \"short\";\n}\n\nconst nfCache = new Map<string, Intl.NumberFormat>();\nconst dtfCache = new Map<string, Intl.DateTimeFormat>();\n\nfunction getNF(locale: string, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n\tconst key = `${locale}:${options.style ?? \"\"}:${options.currency ?? \"\"}:${options.notation ?? \"\"}:${options.maximumFractionDigits ?? \"\"}`;\n\tlet fmt = nfCache.get(key);\n\tif (!fmt) {\n\t\tfmt = new Intl.NumberFormat(locale, options);\n\t\tnfCache.set(key, fmt);\n\t}\n\treturn fmt;\n}\n\nfunction getDTF(locale: string, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n\tconst key = `${locale}:${options.dateStyle ?? \"\"}:${options.timeStyle ?? \"\"}`;\n\tlet fmt = dtfCache.get(key);\n\tif (!fmt) {\n\t\tfmt = new Intl.DateTimeFormat(locale, options);\n\t\tdtfCache.set(key, fmt);\n\t}\n\treturn fmt;\n}\n\nexport function formatValue(\n\tvalue: string | number | Date,\n\tformat: FormatMode,\n\tlocale: string,\n\toptions: FormatValueOptions = {},\n): string {\n\tconst { currency, dateStyle = \"medium\", timeStyle = \"short\" } = options;\n\tconst num = Number(value);\n\tconst date = value instanceof Date ? value : new Date(value as string | number);\n\n\tswitch (format) {\n\t\tcase \"number\":\n\t\t\treturn getNF(locale, {}).format(num);\n\t\tcase \"integer\":\n\t\t\treturn getNF(locale, { maximumFractionDigits: 0 }).format(num);\n\t\tcase \"percent\":\n\t\t\treturn getNF(locale, { style: \"percent\" }).format(num);\n\t\tcase \"compact\":\n\t\t\treturn getNF(locale, { notation: \"compact\" }).format(num);\n\t\tcase \"currency\": {\n\t\t\tif (!currency) {\n\t\t\t\tif (process.env.NODE_ENV === \"development\") {\n\t\t\t\t\tconsole.warn('[vocoder] format=\"currency\" requires a currency prop');\n\t\t\t\t}\n\t\t\t\treturn String(value);\n\t\t\t}\n\t\t\treturn getNF(locale, { style: \"currency\", currency }).format(num);\n\t\t}\n\t\tcase \"date\":\n\t\t\treturn getDTF(locale, { dateStyle }).format(date);\n\t\tcase \"time\":\n\t\t\treturn getDTF(locale, { timeStyle }).format(date);\n\t\tcase \"datetime\":\n\t\t\treturn getDTF(locale, { dateStyle, timeStyle }).format(date);\n\t\tdefault:\n\t\t\treturn String(value);\n\t}\n}\n","/**\n * FNV-1a 32-bit hash for generating stable message IDs from source text.\n *\n * Works identically in Node.js and browsers (no platform APIs).\n * Used by the extractor (build time) and the React runtime (browser) — both\n * always produce the same key for the same source text.\n *\n * Output: 7 base-36 chars (~2.2 billion values).\n * Collision probability ≈ 0.002% for 10K strings (birthday problem).\n * Add `context` to disambiguate identical strings with different meanings.\n * Add `formality` (\"formal\" | \"informal\") to produce separate keys for\n * register variants — \"neutral\", \"auto\", and undefined hash identically.\n *\n * Separators: \\x04 (ASCII EOT) for context, \\x05 (ASCII ENQ) for formality.\n */\nexport function generateMessageHash(\n\ttext: string,\n\tcontext?: string,\n\tformality?: string,\n): string {\n\tlet input = context ? `${text}\\x04${context}` : text;\n\tif (formality === \"formal\" || formality === \"informal\") {\n\t\tinput += `\\x05${formality}`;\n\t}\n\tlet h = 2166136261 >>> 0;\n\tfor (let i = 0; i < input.length; i++) {\n\t\th = Math.imul(h ^ input.charCodeAt(i), 16777619) >>> 0;\n\t}\n\treturn h.toString(36).padStart(7, \"0\");\n}\n","import IntlMessageFormat from \"intl-messageformat\";\nimport {\n\tisPluralElement,\n\tisSelectElement,\n\tisTagElement,\n\tparse,\n\tTYPE,\n} from \"@formatjs/icu-messageformat-parser\";\nimport type {\n\tLiteralElement,\n\tMessageFormatElement,\n\tPluralElement,\n} from \"@formatjs/icu-messageformat-parser\";\nimport type { OrdinalForms } from \"./types\";\n\n// ---------------------------------------------------------------------------\n// IntlMessageFormat cache — keyed by \"locale:text\"\n// ---------------------------------------------------------------------------\n\nconst imfCache = new Map<string, IntlMessageFormat>();\n\nfunction getIMF(text: string, locale: string): IntlMessageFormat {\n\tconst key = `${locale}:${text}`;\n\tlet msg = imfCache.get(key);\n\tif (!msg) {\n\t\t// ignoreTag: true — component placeholders (<c0>, <c1>) are handled\n\t\t// by formatElements, not by IMF. IMF handles only ICU primitives.\n\t\tmsg = new IntlMessageFormat(text, locale, undefined, { ignoreTag: true });\n\t\timfCache.set(key, msg);\n\t}\n\treturn msg;\n}\n\n/**\n * Format an ICU MessageFormat string with the given values and locale.\n * Returns the raw `text` unchanged if parsing or formatting throws — the\n * caller always gets a string, never an exception.\n */\nexport function formatICU(\n\ttext: string,\n\tvalues: Record<string, any>,\n\tlocale: string = \"en\",\n): string {\n\ttry {\n\t\tconst result = getIMF(text, locale.toLowerCase()).format(values);\n\t\treturn typeof result === \"string\" ? result : (result as unknown[]).join(\"\");\n\t} catch (error) {\n\t\tif (process.env.NODE_ENV !== \"production\") {\n\t\t\tconsole.error(\n\t\t\t\t`[vocoder] ICU formatting error for locale \"${locale}\":`,\n\t\t\t\terror,\n\t\t\t\t\"\\n ICU:\", text,\n\t\t\t\t\"\\n values:\", values,\n\t\t\t);\n\t\t}\n\t\treturn text;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Embedded selectordinal rewriting — Bug 1 fix\n//\n// The translation pipeline's ordinal DB fast path (tryBuildOrdinalFromDB)\n// only applies when a selectordinal is the SOLE top-level element in the\n// ICU string. When it appears embedded inside a larger sentence (e.g.\n// \"Congrats! your {year, selectordinal, ...} anniversary!\"), the pipeline\n// falls through to the translation provider, which stores garbage branches\n// (e.g. \"1el\", \"1th\", \"1الـ\") in the DB.\n//\n// This function fixes the stored translation at render time by rewriting\n// any selectordinal nodes using the locale's ordinalForms data.\n// ---------------------------------------------------------------------------\n\nconst CLDR_ORDINAL_ORDER = [\n\t\"zero\",\n\t\"one\",\n\t\"two\",\n\t\"few\",\n\t\"many\",\n\t\"other\",\n] as const;\n\nfunction printICU(elements: MessageFormatElement[]): string {\n\treturn elements.map(printElement).join(\"\");\n}\n\nfunction printElement(el: MessageFormatElement): string {\n\tswitch (el.type) {\n\t\tcase TYPE.literal:\n\t\t\treturn (el as LiteralElement).value;\n\t\tcase TYPE.argument:\n\t\t\treturn `{${el.value}}`;\n\t\tcase TYPE.pound:\n\t\t\treturn \"#\";\n\t\tcase TYPE.number: {\n\t\t\tif (!el.style) return `{${el.value}, number}`;\n\t\t\tconst style =\n\t\t\t\ttypeof el.style === \"string\"\n\t\t\t\t\t? el.style\n\t\t\t\t\t: `::${(el.style as Record<string, any>).parsedOptions ?? \"\"}`;\n\t\t\treturn `{${el.value}, number, ${style}}`;\n\t\t}\n\t\tcase TYPE.date: {\n\t\t\tif (!el.style) return `{${el.value}, date}`;\n\t\t\tconst style =\n\t\t\t\ttypeof el.style === \"string\"\n\t\t\t\t\t? el.style\n\t\t\t\t\t: `::${(el.style as Record<string, any>).parsedOptions ?? \"\"}`;\n\t\t\treturn `{${el.value}, date, ${style}}`;\n\t\t}\n\t\tcase TYPE.time: {\n\t\t\tif (!el.style) return `{${el.value}, time}`;\n\t\t\tconst style =\n\t\t\t\ttypeof el.style === \"string\"\n\t\t\t\t\t? el.style\n\t\t\t\t\t: `::${(el.style as Record<string, any>).parsedOptions ?? \"\"}`;\n\t\t\treturn `{${el.value}, time, ${style}}`;\n\t\t}\n\t\tcase TYPE.select: {\n\t\t\tconst options = Object.entries(\n\t\t\t\t(el as Record<string, any>).options as Record<\n\t\t\t\t\tstring,\n\t\t\t\t\t{ value: MessageFormatElement[] }\n\t\t\t\t>,\n\t\t\t)\n\t\t\t\t.map(([k, v]) => `${k} {${printICU(v.value)}}`)\n\t\t\t\t.join(\" \");\n\t\t\treturn `{${el.value}, select, ${options}}`;\n\t\t}\n\t\tcase TYPE.plural: {\n\t\t\tconst pluralEl = el as PluralElement;\n\t\t\tconst pluralType =\n\t\t\t\tpluralEl.pluralType === \"ordinal\" ? \"selectordinal\" : \"plural\";\n\t\t\tconst offset =\n\t\t\t\tpluralEl.offset !== 0 ? `offset:${pluralEl.offset} ` : \"\";\n\t\t\tconst options = Object.entries(pluralEl.options)\n\t\t\t\t.map(([k, v]) => `${k} {${printICU(v.value)}}`)\n\t\t\t\t.join(\" \");\n\t\t\treturn `{${pluralEl.value}, ${pluralType}, ${offset}${options}}`;\n\t\t}\n\t\tcase TYPE.tag: {\n\t\t\tconst children = printICU((el as Record<string, any>).children as MessageFormatElement[]);\n\t\t\treturn `<${el.value}>${children}</${el.value}>`;\n\t\t}\n\t\tdefault:\n\t\t\treturn \"\";\n\t}\n}\n\nfunction rewriteElements(\n\telements: MessageFormatElement[],\n\tforms: OrdinalForms,\n\tvalues: Record<string, any>,\n): MessageFormatElement[] {\n\treturn elements.flatMap((el) => {\n\t\tif (isPluralElement(el) && el.pluralType === \"ordinal\") {\n\t\t\treturn [rewriteSelectordinalElement(el, forms, values)];\n\t\t}\n\t\tif (isSelectElement(el)) {\n\t\t\tconst options: Record<string, { value: MessageFormatElement[] }> = {};\n\t\t\tfor (const [key, opt] of Object.entries(\n\t\t\t\tel.options as Record<string, { value: MessageFormatElement[] }>,\n\t\t\t)) {\n\t\t\t\toptions[key] = { value: rewriteElements(opt.value, forms, values) };\n\t\t\t}\n\t\t\treturn [{ ...el, options } as MessageFormatElement];\n\t\t}\n\t\tif (isPluralElement(el)) {\n\t\t\tconst options: Record<string, { value: MessageFormatElement[] }> = {};\n\t\t\tfor (const [key, opt] of Object.entries(el.options)) {\n\t\t\t\toptions[key] = { value: rewriteElements(opt.value, forms, values) };\n\t\t\t}\n\t\t\treturn [{ ...el, options } as MessageFormatElement];\n\t\t}\n\t\tif (isTagElement(el)) {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\t...el,\n\t\t\t\t\tchildren: rewriteElements(\n\t\t\t\t\t\t(el as Record<string, any>).children as MessageFormatElement[],\n\t\t\t\t\t\tforms,\n\t\t\t\t\t\tvalues,\n\t\t\t\t\t),\n\t\t\t\t} as MessageFormatElement,\n\t\t\t];\n\t\t}\n\t\treturn [el];\n\t});\n}\n\nfunction rewriteSelectordinalElement(\n\tel: PluralElement,\n\tforms: OrdinalForms,\n\tvalues: Record<string, any>,\n): MessageFormatElement {\n\tif (forms.type === \"suffix\") {\n\t\tconst newOptions: Record<string, { value: MessageFormatElement[] }> = {};\n\t\tfor (const cat of CLDR_ORDINAL_ORDER) {\n\t\t\tconst pattern = forms.suffixes[cat];\n\t\t\tif (pattern === undefined) continue;\n\t\t\tconst poundIdx = pattern.indexOf(\"#\");\n\t\t\tconst parts: MessageFormatElement[] = [];\n\t\t\tif (poundIdx === -1) {\n\t\t\t\tparts.push({ type: TYPE.literal, value: pattern } as LiteralElement);\n\t\t\t} else {\n\t\t\t\tif (poundIdx > 0)\n\t\t\t\t\tparts.push({\n\t\t\t\t\t\ttype: TYPE.literal,\n\t\t\t\t\t\tvalue: pattern.slice(0, poundIdx),\n\t\t\t\t\t} as LiteralElement);\n\t\t\t\tparts.push({ type: TYPE.pound });\n\t\t\t\tif (poundIdx < pattern.length - 1)\n\t\t\t\t\tparts.push({\n\t\t\t\t\t\ttype: TYPE.literal,\n\t\t\t\t\t\tvalue: pattern.slice(poundIdx + 1),\n\t\t\t\t\t} as LiteralElement);\n\t\t\t}\n\t\t\tnewOptions[cat] = { value: parts };\n\t\t}\n\t\treturn { ...el, options: newOptions };\n\t}\n\n\tif (forms.type === \"word\") {\n\t\tconst rank = values[el.value];\n\t\tif (typeof rank === \"number\") {\n\t\t\tconst genderMap =\n\t\t\t\tforms.words[\"masculine\"] ?? Object.values(forms.words)[0];\n\t\t\tconst word = genderMap?.[rank];\n\t\t\treturn {\n\t\t\t\ttype: TYPE.literal,\n\t\t\t\tvalue: word ?? String(rank),\n\t\t\t} as LiteralElement;\n\t\t}\n\t}\n\n\treturn el;\n}\n\n/**\n * Rewrite any embedded `selectordinal` nodes in an ICU string using\n * `ordinalForms` from the locale bundle, before passing the string to\n * `formatICU`.\n *\n * Returns `icu` unchanged when:\n * - the string contains no \"selectordinal\" substring (fast path — most strings)\n * - parsing throws (safe fallback to whatever formatICU receives)\n *\n * @param icu - Translated ICU string (may contain garbage ordinal branches from provider)\n * @param ordinalForms - Locale's ordinalForms from the compiled bundle\n * @param values - Runtime interpolation values (needed for word-based rank lookup)\n */\nexport function rewriteSelectordinalInICU(\n\ticu: string,\n\tordinalForms: OrdinalForms,\n\tvalues: Record<string, any>,\n): string {\n\tif (!icu.includes(\"selectordinal\")) return icu;\n\n\ttry {\n\t\tconst ast = parse(icu, { captureLocation: false });\n\t\tconst rewritten = rewriteElements(ast, ordinalForms, values);\n\t\treturn printICU(rewritten);\n\t} catch {\n\t\treturn icu;\n\t}\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@vocoder/core",
3
+ "version": "0.2.0",
4
+ "description": "Shared primitives for the Vocoder i18n SDK — hash, ICU formatting, locale utilities, and types",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/vocoder/vocoder-sdk.git",
25
+ "directory": "packages/core"
26
+ },
27
+ "keywords": [
28
+ "i18n",
29
+ "internationalization",
30
+ "translation",
31
+ "icu",
32
+ "messageformat",
33
+ "vocoder"
34
+ ],
35
+ "author": "Vocoder <admin@vocoder.app>",
36
+ "license": "MIT",
37
+ "homepage": "https://github.com/vocoder/vocoder-sdk#readme",
38
+ "bugs": {
39
+ "url": "https://github.com/vocoder/vocoder-sdk/issues"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "dependencies": {
45
+ "@formatjs/icu-messageformat-parser": "^2.11.4",
46
+ "intl-messageformat": "^11.1.2"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^20.19.9",
50
+ "tsup": "^8.0.0",
51
+ "typescript": "^5.4.0",
52
+ "vitest": "^1.0.0"
53
+ },
54
+ "scripts": {
55
+ "build": "tsup",
56
+ "dev": "tsup --watch --no-dts --clean=false",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest --watch",
59
+ "typecheck": "tsc --noEmit"
60
+ }
61
+ }