@coherent.js/i18n 1.0.0-beta.6 → 1.0.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/formatters.js +321 -0
- package/dist/formatters.js.map +7 -0
- package/dist/index.js.map +7 -0
- package/dist/locale.js +215 -0
- package/dist/locale.js.map +7 -0
- package/dist/translator.js +220 -0
- package/dist/translator.js.map +7 -0
- package/package.json +13 -3
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
// src/formatters.js
|
|
2
|
+
var DateFormatter = class {
|
|
3
|
+
constructor(locale = "en") {
|
|
4
|
+
this.locale = locale;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Format a date
|
|
8
|
+
*
|
|
9
|
+
* @param {Date|string|number} date - Date to format
|
|
10
|
+
* @param {Object} [options] - Intl.DateTimeFormat options
|
|
11
|
+
* @returns {string} Formatted date
|
|
12
|
+
*/
|
|
13
|
+
format(date, options = {}) {
|
|
14
|
+
const dateObj = date instanceof Date ? date : new Date(date);
|
|
15
|
+
if (typeof Intl !== "undefined" && Intl.DateTimeFormat) {
|
|
16
|
+
const formatter = new Intl.DateTimeFormat(this.locale, options);
|
|
17
|
+
return formatter.format(dateObj);
|
|
18
|
+
}
|
|
19
|
+
return dateObj.toLocaleDateString();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Format date as short (e.g., 1/1/2024)
|
|
23
|
+
*/
|
|
24
|
+
short(date) {
|
|
25
|
+
return this.format(date, {
|
|
26
|
+
year: "numeric",
|
|
27
|
+
month: "numeric",
|
|
28
|
+
day: "numeric"
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Format date as medium (e.g., Jan 1, 2024)
|
|
33
|
+
*/
|
|
34
|
+
medium(date) {
|
|
35
|
+
return this.format(date, {
|
|
36
|
+
year: "numeric",
|
|
37
|
+
month: "short",
|
|
38
|
+
day: "numeric"
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Format date as long (e.g., January 1, 2024)
|
|
43
|
+
*/
|
|
44
|
+
long(date) {
|
|
45
|
+
return this.format(date, {
|
|
46
|
+
year: "numeric",
|
|
47
|
+
month: "long",
|
|
48
|
+
day: "numeric"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Format date as full (e.g., Monday, January 1, 2024)
|
|
53
|
+
*/
|
|
54
|
+
full(date) {
|
|
55
|
+
return this.format(date, {
|
|
56
|
+
weekday: "long",
|
|
57
|
+
year: "numeric",
|
|
58
|
+
month: "long",
|
|
59
|
+
day: "numeric"
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Format time
|
|
64
|
+
*/
|
|
65
|
+
time(date, options = {}) {
|
|
66
|
+
return this.format(date, {
|
|
67
|
+
hour: "numeric",
|
|
68
|
+
minute: "numeric",
|
|
69
|
+
...options
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Format date and time
|
|
74
|
+
*/
|
|
75
|
+
dateTime(date, options = {}) {
|
|
76
|
+
return this.format(date, {
|
|
77
|
+
year: "numeric",
|
|
78
|
+
month: "short",
|
|
79
|
+
day: "numeric",
|
|
80
|
+
hour: "numeric",
|
|
81
|
+
minute: "numeric",
|
|
82
|
+
...options
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Format relative time (e.g., "2 days ago")
|
|
87
|
+
*/
|
|
88
|
+
relative(date) {
|
|
89
|
+
const dateObj = date instanceof Date ? date : new Date(date);
|
|
90
|
+
const now = /* @__PURE__ */ new Date();
|
|
91
|
+
const diffMs = now - dateObj;
|
|
92
|
+
const diffSec = Math.floor(diffMs / 1e3);
|
|
93
|
+
const diffMin = Math.floor(diffSec / 60);
|
|
94
|
+
const diffHour = Math.floor(diffMin / 60);
|
|
95
|
+
const diffDay = Math.floor(diffHour / 24);
|
|
96
|
+
if (typeof Intl !== "undefined" && Intl.RelativeTimeFormat) {
|
|
97
|
+
const rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: "auto" });
|
|
98
|
+
if (diffDay > 0) {
|
|
99
|
+
return rtf.format(-diffDay, "day");
|
|
100
|
+
} else if (diffHour > 0) {
|
|
101
|
+
return rtf.format(-diffHour, "hour");
|
|
102
|
+
} else if (diffMin > 0) {
|
|
103
|
+
return rtf.format(-diffMin, "minute");
|
|
104
|
+
} else {
|
|
105
|
+
return rtf.format(-diffSec, "second");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (diffDay > 0) {
|
|
109
|
+
return `${diffDay} day${diffDay > 1 ? "s" : ""} ago`;
|
|
110
|
+
} else if (diffHour > 0) {
|
|
111
|
+
return `${diffHour} hour${diffHour > 1 ? "s" : ""} ago`;
|
|
112
|
+
} else if (diffMin > 0) {
|
|
113
|
+
return `${diffMin} minute${diffMin > 1 ? "s" : ""} ago`;
|
|
114
|
+
} else {
|
|
115
|
+
return "just now";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var NumberFormatter = class {
|
|
120
|
+
constructor(locale = "en") {
|
|
121
|
+
this.locale = locale;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Format a number
|
|
125
|
+
*
|
|
126
|
+
* @param {number} value - Number to format
|
|
127
|
+
* @param {Object} [options] - Intl.NumberFormat options
|
|
128
|
+
* @returns {string} Formatted number
|
|
129
|
+
*/
|
|
130
|
+
format(value, options = {}) {
|
|
131
|
+
if (typeof Intl !== "undefined" && Intl.NumberFormat) {
|
|
132
|
+
const formatter = new Intl.NumberFormat(this.locale, options);
|
|
133
|
+
return formatter.format(value);
|
|
134
|
+
}
|
|
135
|
+
return value.toLocaleString();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Format as decimal
|
|
139
|
+
*/
|
|
140
|
+
decimal(value, decimals = 2) {
|
|
141
|
+
return this.format(value, {
|
|
142
|
+
minimumFractionDigits: decimals,
|
|
143
|
+
maximumFractionDigits: decimals
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Format as percentage
|
|
148
|
+
*/
|
|
149
|
+
percent(value, decimals = 0) {
|
|
150
|
+
return this.format(value, {
|
|
151
|
+
style: "percent",
|
|
152
|
+
minimumFractionDigits: decimals,
|
|
153
|
+
maximumFractionDigits: decimals
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Format as compact (e.g., 1.2K, 3.4M)
|
|
158
|
+
*/
|
|
159
|
+
compact(value) {
|
|
160
|
+
if (typeof Intl !== "undefined" && Intl.NumberFormat) {
|
|
161
|
+
try {
|
|
162
|
+
const formatter = new Intl.NumberFormat(this.locale, {
|
|
163
|
+
notation: "compact",
|
|
164
|
+
compactDisplay: "short"
|
|
165
|
+
});
|
|
166
|
+
return formatter.format(value);
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (value >= 1e9) {
|
|
171
|
+
return `${(value / 1e9).toFixed(1)}B`;
|
|
172
|
+
} else if (value >= 1e6) {
|
|
173
|
+
return `${(value / 1e6).toFixed(1)}M`;
|
|
174
|
+
} else if (value >= 1e3) {
|
|
175
|
+
return `${(value / 1e3).toFixed(1)}K`;
|
|
176
|
+
}
|
|
177
|
+
return String(value);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Format with units
|
|
181
|
+
*/
|
|
182
|
+
unit(value, unit, options = {}) {
|
|
183
|
+
if (typeof Intl !== "undefined" && Intl.NumberFormat) {
|
|
184
|
+
try {
|
|
185
|
+
const formatter = new Intl.NumberFormat(this.locale, {
|
|
186
|
+
style: "unit",
|
|
187
|
+
unit,
|
|
188
|
+
...options
|
|
189
|
+
});
|
|
190
|
+
return formatter.format(value);
|
|
191
|
+
} catch {
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return `${value} ${unit}`;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
var CurrencyFormatter = class {
|
|
198
|
+
constructor(locale = "en", defaultCurrency = "USD") {
|
|
199
|
+
this.locale = locale;
|
|
200
|
+
this.defaultCurrency = defaultCurrency;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Format a currency value
|
|
204
|
+
*
|
|
205
|
+
* @param {number} value - Amount to format
|
|
206
|
+
* @param {string} [currency] - Currency code (e.g., 'USD', 'EUR')
|
|
207
|
+
* @param {Object} [options] - Additional options
|
|
208
|
+
* @returns {string} Formatted currency
|
|
209
|
+
*/
|
|
210
|
+
format(value, currency = null, options = {}) {
|
|
211
|
+
const currencyCode = currency || this.defaultCurrency;
|
|
212
|
+
if (typeof Intl !== "undefined" && Intl.NumberFormat) {
|
|
213
|
+
const formatter = new Intl.NumberFormat(this.locale, {
|
|
214
|
+
style: "currency",
|
|
215
|
+
currency: currencyCode,
|
|
216
|
+
...options
|
|
217
|
+
});
|
|
218
|
+
return formatter.format(value);
|
|
219
|
+
}
|
|
220
|
+
return `${currencyCode} ${value.toFixed(2)}`;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Format without decimal places
|
|
224
|
+
*/
|
|
225
|
+
whole(value, currency = null) {
|
|
226
|
+
return this.format(value, currency, {
|
|
227
|
+
minimumFractionDigits: 0,
|
|
228
|
+
maximumFractionDigits: 0
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Format with symbol only (no code)
|
|
233
|
+
*/
|
|
234
|
+
symbol(value, currency = null) {
|
|
235
|
+
return this.format(value, currency, {
|
|
236
|
+
currencyDisplay: "symbol"
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Format with narrow symbol
|
|
241
|
+
*/
|
|
242
|
+
narrowSymbol(value, currency = null) {
|
|
243
|
+
return this.format(value, currency, {
|
|
244
|
+
currencyDisplay: "narrowSymbol"
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Format with code (e.g., USD 100.00)
|
|
249
|
+
*/
|
|
250
|
+
code(value, currency = null) {
|
|
251
|
+
return this.format(value, currency, {
|
|
252
|
+
currencyDisplay: "code"
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
var ListFormatter = class {
|
|
257
|
+
constructor(locale = "en") {
|
|
258
|
+
this.locale = locale;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Format a list
|
|
262
|
+
*
|
|
263
|
+
* @param {Array} items - Items to format
|
|
264
|
+
* @param {Object} [options] - Formatting options
|
|
265
|
+
* @returns {string} Formatted list
|
|
266
|
+
*/
|
|
267
|
+
format(items, options = {}) {
|
|
268
|
+
if (typeof Intl !== "undefined" && Intl.ListFormat) {
|
|
269
|
+
const formatter = new Intl.ListFormat(this.locale, options);
|
|
270
|
+
return formatter.format(items);
|
|
271
|
+
}
|
|
272
|
+
if (items.length === 0) return "";
|
|
273
|
+
if (items.length === 1) return items[0];
|
|
274
|
+
if (items.length === 2) return `${items[0]} and ${items[1]}`;
|
|
275
|
+
const last = items[items.length - 1];
|
|
276
|
+
const rest = items.slice(0, -1);
|
|
277
|
+
return `${rest.join(", ")}, and ${last}`;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Format as conjunction (and)
|
|
281
|
+
*/
|
|
282
|
+
and(items) {
|
|
283
|
+
return this.format(items, { type: "conjunction" });
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Format as disjunction (or)
|
|
287
|
+
*/
|
|
288
|
+
or(items) {
|
|
289
|
+
return this.format(items, { type: "disjunction" });
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Format as unit list
|
|
293
|
+
*/
|
|
294
|
+
unit(items) {
|
|
295
|
+
return this.format(items, { type: "unit" });
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
function createFormatters(locale = "en", options = {}) {
|
|
299
|
+
return {
|
|
300
|
+
date: new DateFormatter(locale),
|
|
301
|
+
number: new NumberFormatter(locale),
|
|
302
|
+
currency: new CurrencyFormatter(locale, options.defaultCurrency),
|
|
303
|
+
list: new ListFormatter(locale)
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
var formatters_default = {
|
|
307
|
+
DateFormatter,
|
|
308
|
+
NumberFormatter,
|
|
309
|
+
CurrencyFormatter,
|
|
310
|
+
ListFormatter,
|
|
311
|
+
createFormatters
|
|
312
|
+
};
|
|
313
|
+
export {
|
|
314
|
+
CurrencyFormatter,
|
|
315
|
+
DateFormatter,
|
|
316
|
+
ListFormatter,
|
|
317
|
+
NumberFormatter,
|
|
318
|
+
createFormatters,
|
|
319
|
+
formatters_default as default
|
|
320
|
+
};
|
|
321
|
+
//# sourceMappingURL=formatters.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/formatters.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Coherent.js I18n Formatters\n * \n * Locale-aware formatting for dates, numbers, and currencies\n * \n * @module i18n/formatters\n */\n\n/**\n * Date Formatter\n * Formats dates according to locale\n */\nexport class DateFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a date\n * \n * @param {Date|string|number} date - Date to format\n * @param {Object} [options] - Intl.DateTimeFormat options\n * @returns {string} Formatted date\n */\n format(date, options = {}) {\n const dateObj = date instanceof Date ? date : new Date(date);\n \n if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {\n const formatter = new Intl.DateTimeFormat(this.locale, options);\n return formatter.format(dateObj);\n }\n \n // Fallback\n return dateObj.toLocaleDateString();\n }\n\n /**\n * Format date as short (e.g., 1/1/2024)\n */\n short(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as medium (e.g., Jan 1, 2024)\n */\n medium(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'short',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as long (e.g., January 1, 2024)\n */\n long(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as full (e.g., Monday, January 1, 2024)\n */\n full(date) {\n return this.format(date, {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n }\n\n /**\n * Format time\n */\n time(date, options = {}) {\n return this.format(date, {\n hour: 'numeric',\n minute: 'numeric',\n ...options\n });\n }\n\n /**\n * Format date and time\n */\n dateTime(date, options = {}) {\n return this.format(date, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n ...options\n });\n }\n\n /**\n * Format relative time (e.g., \"2 days ago\")\n */\n relative(date) {\n const dateObj = date instanceof Date ? date : new Date(date);\n const now = new Date();\n const diffMs = now - dateObj;\n const diffSec = Math.floor(diffMs / 1000);\n const diffMin = Math.floor(diffSec / 60);\n const diffHour = Math.floor(diffMin / 60);\n const diffDay = Math.floor(diffHour / 24);\n\n if (typeof Intl !== 'undefined' && Intl.RelativeTimeFormat) {\n const rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: 'auto' });\n \n if (diffDay > 0) {\n return rtf.format(-diffDay, 'day');\n } else if (diffHour > 0) {\n return rtf.format(-diffHour, 'hour');\n } else if (diffMin > 0) {\n return rtf.format(-diffMin, 'minute');\n } else {\n return rtf.format(-diffSec, 'second');\n }\n }\n\n // Fallback\n if (diffDay > 0) {\n return `${diffDay} day${diffDay > 1 ? 's' : ''} ago`;\n } else if (diffHour > 0) {\n return `${diffHour} hour${diffHour > 1 ? 's' : ''} ago`;\n } else if (diffMin > 0) {\n return `${diffMin} minute${diffMin > 1 ? 's' : ''} ago`;\n } else {\n return 'just now';\n }\n }\n}\n\n/**\n * Number Formatter\n * Formats numbers according to locale\n */\nexport class NumberFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a number\n * \n * @param {number} value - Number to format\n * @param {Object} [options] - Intl.NumberFormat options\n * @returns {string} Formatted number\n */\n format(value, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n const formatter = new Intl.NumberFormat(this.locale, options);\n return formatter.format(value);\n }\n \n // Fallback\n return value.toLocaleString();\n }\n\n /**\n * Format as decimal\n */\n decimal(value, decimals = 2) {\n return this.format(value, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals\n });\n }\n\n /**\n * Format as percentage\n */\n percent(value, decimals = 0) {\n return this.format(value, {\n style: 'percent',\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals\n });\n }\n\n /**\n * Format as compact (e.g., 1.2K, 3.4M)\n */\n compact(value) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n try {\n const formatter = new Intl.NumberFormat(this.locale, {\n notation: 'compact',\n compactDisplay: 'short'\n });\n return formatter.format(value);\n } catch {\n // Fallback for older browsers\n }\n }\n \n // Manual compact formatting\n if (value >= 1e9) {\n return `${(value / 1e9).toFixed(1) }B`;\n } else if (value >= 1e6) {\n return `${(value / 1e6).toFixed(1) }M`;\n } else if (value >= 1e3) {\n return `${(value / 1e3).toFixed(1) }K`;\n }\n \n return String(value);\n }\n\n /**\n * Format with units\n */\n unit(value, unit, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n try {\n const formatter = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit,\n ...options\n });\n return formatter.format(value);\n } catch {\n // Fallback\n }\n }\n \n return `${value} ${unit}`;\n }\n}\n\n/**\n * Currency Formatter\n * Formats currency values according to locale\n */\nexport class CurrencyFormatter {\n constructor(locale = 'en', defaultCurrency = 'USD') {\n this.locale = locale;\n this.defaultCurrency = defaultCurrency;\n }\n\n /**\n * Format a currency value\n * \n * @param {number} value - Amount to format\n * @param {string} [currency] - Currency code (e.g., 'USD', 'EUR')\n * @param {Object} [options] - Additional options\n * @returns {string} Formatted currency\n */\n format(value, currency = null, options = {}) {\n const currencyCode = currency || this.defaultCurrency;\n \n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n const formatter = new Intl.NumberFormat(this.locale, {\n style: 'currency',\n currency: currencyCode,\n ...options\n });\n return formatter.format(value);\n }\n \n // Fallback\n return `${currencyCode} ${value.toFixed(2)}`;\n }\n\n /**\n * Format without decimal places\n */\n whole(value, currency = null) {\n return this.format(value, currency, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n });\n }\n\n /**\n * Format with symbol only (no code)\n */\n symbol(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'symbol'\n });\n }\n\n /**\n * Format with narrow symbol\n */\n narrowSymbol(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'narrowSymbol'\n });\n }\n\n /**\n * Format with code (e.g., USD 100.00)\n */\n code(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'code'\n });\n }\n}\n\n/**\n * List Formatter\n * Formats lists according to locale\n */\nexport class ListFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a list\n * \n * @param {Array} items - Items to format\n * @param {Object} [options] - Formatting options\n * @returns {string} Formatted list\n */\n format(items, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.ListFormat) {\n const formatter = new Intl.ListFormat(this.locale, options);\n return formatter.format(items);\n }\n \n // Fallback\n if (items.length === 0) return '';\n if (items.length === 1) return items[0];\n if (items.length === 2) return `${items[0]} and ${items[1]}`;\n \n const last = items[items.length - 1];\n const rest = items.slice(0, -1);\n return `${rest.join(', ')}, and ${last}`;\n }\n\n /**\n * Format as conjunction (and)\n */\n and(items) {\n return this.format(items, { type: 'conjunction' });\n }\n\n /**\n * Format as disjunction (or)\n */\n or(items) {\n return this.format(items, { type: 'disjunction' });\n }\n\n /**\n * Format as unit list\n */\n unit(items) {\n return this.format(items, { type: 'unit' });\n }\n}\n\n/**\n * Create formatters for a locale\n * \n * @param {string} locale - Locale code\n * @param {Object} [options] - Formatter options\n * @returns {Object} Formatter instances\n */\nexport function createFormatters(locale = 'en', options = {}) {\n return {\n date: new DateFormatter(locale),\n number: new NumberFormatter(locale),\n currency: new CurrencyFormatter(locale, options.defaultCurrency),\n list: new ListFormatter(locale)\n };\n}\n\nexport default {\n DateFormatter,\n NumberFormatter,\n CurrencyFormatter,\n ListFormatter,\n createFormatters\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/translator.js", "../src/formatters.js", "../src/locale.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Coherent.js Translator\n * \n * Handles translation of strings with interpolation and pluralization\n * \n * @module i18n/translator\n */\n\n/**\n * Translator\n * Manages translations and locale switching\n */\nexport class Translator {\n constructor(options = {}) {\n this.options = {\n defaultLocale: 'en',\n fallbackLocale: 'en',\n missingKeyHandler: null,\n interpolation: {\n prefix: '{{',\n suffix: '}}'\n },\n ...options\n };\n \n this.translations = new Map();\n this.currentLocale = this.options.defaultLocale;\n this.loadedLocales = new Set();\n }\n\n /**\n * Add translations for a locale\n * \n * @param {string} locale - Locale code (e.g., 'en', 'fr', 'es')\n * @param {Object} translations - Translation object\n */\n addTranslations(locale, translations) {\n if (!this.translations.has(locale)) {\n this.translations.set(locale, {});\n }\n \n const existing = this.translations.get(locale);\n this.translations.set(locale, this.deepMerge(existing, translations));\n this.loadedLocales.add(locale);\n }\n\n /**\n * Deep merge objects\n */\n deepMerge(target, source) {\n const result = { ...target };\n \n for (const [key, value] of Object.entries(source)) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = this.deepMerge(result[key] || {}, value);\n } else {\n result[key] = value;\n }\n }\n \n return result;\n }\n\n /**\n * Set current locale\n * \n * @param {string} locale - Locale code\n */\n setLocale(locale) {\n if (!this.loadedLocales.has(locale)) {\n console.warn(`Locale ${locale} not loaded, using fallback`);\n this.currentLocale = this.options.fallbackLocale;\n } else {\n this.currentLocale = locale;\n }\n }\n\n /**\n * Get current locale\n * \n * @returns {string} Current locale code\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Translate a key\n * \n * @param {string} key - Translation key (supports dot notation)\n * @param {Object} [params] - Interpolation parameters\n * @param {string} [locale] - Override locale\n * @returns {string} Translated string\n */\n t(key, params = {}, locale = null) {\n const targetLocale = locale || this.currentLocale;\n \n // Get translation\n let translation = this.getTranslation(key, targetLocale);\n \n // Fallback to default locale\n if (translation === null && targetLocale !== this.options.fallbackLocale) {\n translation = this.getTranslation(key, this.options.fallbackLocale);\n }\n \n // Handle missing translation\n if (translation === null) {\n if (this.options.missingKeyHandler) {\n return this.options.missingKeyHandler(key, targetLocale);\n }\n return key;\n }\n \n // Handle pluralization\n if (typeof translation === 'object' && params.count !== undefined) {\n translation = this.selectPlural(translation, params.count, targetLocale);\n }\n \n // Interpolate parameters\n if (typeof translation === 'string') {\n return this.interpolate(translation, params);\n }\n \n return String(translation);\n }\n\n /**\n * Get translation from nested object\n */\n getTranslation(key, locale) {\n const translations = this.translations.get(locale);\n if (!translations) return null;\n \n const keys = key.split('.');\n let value = translations;\n \n for (const k of keys) {\n if (value && typeof value === 'object' && k in value) {\n value = value[k];\n } else {\n return null;\n }\n }\n \n return value;\n }\n\n /**\n * Select plural form\n */\n selectPlural(pluralObject, count, locale) {\n // Check for explicit zero first (takes precedence over Intl rules)\n if (count === 0 && pluralObject.zero) {\n return pluralObject.zero;\n }\n \n // Use Intl.PluralRules for locale-specific pluralization\n if (typeof Intl !== 'undefined' && Intl.PluralRules) {\n const rules = new Intl.PluralRules(locale);\n const rule = rules.select(count);\n \n if (pluralObject[rule]) {\n return pluralObject[rule];\n }\n }\n \n // Fallback to simple rules\n if (count === 1 && pluralObject.one) {\n return pluralObject.one;\n } else if (pluralObject.other) {\n return pluralObject.other;\n }\n \n return pluralObject.one || pluralObject.other || '';\n }\n\n /**\n * Interpolate parameters into string\n */\n interpolate(str, params) {\n const { prefix, suffix } = this.options.interpolation;\n let result = str;\n \n for (const [key, value] of Object.entries(params)) {\n const placeholder = `${prefix}${key}${suffix}`;\n result = result.replace(new RegExp(placeholder, 'g'), String(value));\n }\n \n return result;\n }\n\n /**\n * Check if translation exists\n * \n * @param {string} key - Translation key\n * @param {string} [locale] - Locale to check\n * @returns {boolean} True if translation exists\n */\n has(key, locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.getTranslation(key, targetLocale) !== null;\n }\n\n /**\n * Get all translations for current locale\n * \n * @returns {Object} All translations\n */\n getTranslations(locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.translations.get(targetLocale) || {};\n }\n\n /**\n * Get all loaded locales\n * \n * @returns {Array<string>} Array of locale codes\n */\n getLoadedLocales() {\n return Array.from(this.loadedLocales);\n }\n\n /**\n * Remove translations for a locale\n * \n * @param {string} locale - Locale code\n */\n removeLocale(locale) {\n this.translations.delete(locale);\n this.loadedLocales.delete(locale);\n \n if (this.currentLocale === locale) {\n this.currentLocale = this.options.defaultLocale;\n }\n }\n\n /**\n * Clear all translations\n */\n clear() {\n this.translations.clear();\n this.loadedLocales.clear();\n this.currentLocale = this.options.defaultLocale;\n }\n}\n\n/**\n * Create a translator instance\n * \n * @param {Object} [options] - Translator options\n * @returns {Translator} Translator instance\n */\nexport function createTranslator(options = {}) {\n return new Translator(options);\n}\n\n/**\n * Create a scoped translator\n * Automatically prefixes all keys with a namespace\n * \n * @param {Translator} translator - Base translator\n * @param {string} namespace - Namespace prefix\n * @returns {Object} Scoped translator\n */\nexport function createScopedTranslator(translator, namespace) {\n return {\n t: (key, params, locale) => {\n return translator.t(`${namespace}.${key}`, params, locale);\n },\n has: (key, locale) => {\n return translator.has(`${namespace}.${key}`, locale);\n },\n getLocale: () => translator.getLocale(),\n setLocale: (locale) => translator.setLocale(locale)\n };\n}\n\nexport default {\n Translator,\n createTranslator,\n createScopedTranslator\n};\n", "/**\n * Coherent.js I18n Formatters\n * \n * Locale-aware formatting for dates, numbers, and currencies\n * \n * @module i18n/formatters\n */\n\n/**\n * Date Formatter\n * Formats dates according to locale\n */\nexport class DateFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a date\n * \n * @param {Date|string|number} date - Date to format\n * @param {Object} [options] - Intl.DateTimeFormat options\n * @returns {string} Formatted date\n */\n format(date, options = {}) {\n const dateObj = date instanceof Date ? date : new Date(date);\n \n if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {\n const formatter = new Intl.DateTimeFormat(this.locale, options);\n return formatter.format(dateObj);\n }\n \n // Fallback\n return dateObj.toLocaleDateString();\n }\n\n /**\n * Format date as short (e.g., 1/1/2024)\n */\n short(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'numeric',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as medium (e.g., Jan 1, 2024)\n */\n medium(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'short',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as long (e.g., January 1, 2024)\n */\n long(date) {\n return this.format(date, {\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n }\n\n /**\n * Format date as full (e.g., Monday, January 1, 2024)\n */\n full(date) {\n return this.format(date, {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric'\n });\n }\n\n /**\n * Format time\n */\n time(date, options = {}) {\n return this.format(date, {\n hour: 'numeric',\n minute: 'numeric',\n ...options\n });\n }\n\n /**\n * Format date and time\n */\n dateTime(date, options = {}) {\n return this.format(date, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n ...options\n });\n }\n\n /**\n * Format relative time (e.g., \"2 days ago\")\n */\n relative(date) {\n const dateObj = date instanceof Date ? date : new Date(date);\n const now = new Date();\n const diffMs = now - dateObj;\n const diffSec = Math.floor(diffMs / 1000);\n const diffMin = Math.floor(diffSec / 60);\n const diffHour = Math.floor(diffMin / 60);\n const diffDay = Math.floor(diffHour / 24);\n\n if (typeof Intl !== 'undefined' && Intl.RelativeTimeFormat) {\n const rtf = new Intl.RelativeTimeFormat(this.locale, { numeric: 'auto' });\n \n if (diffDay > 0) {\n return rtf.format(-diffDay, 'day');\n } else if (diffHour > 0) {\n return rtf.format(-diffHour, 'hour');\n } else if (diffMin > 0) {\n return rtf.format(-diffMin, 'minute');\n } else {\n return rtf.format(-diffSec, 'second');\n }\n }\n\n // Fallback\n if (diffDay > 0) {\n return `${diffDay} day${diffDay > 1 ? 's' : ''} ago`;\n } else if (diffHour > 0) {\n return `${diffHour} hour${diffHour > 1 ? 's' : ''} ago`;\n } else if (diffMin > 0) {\n return `${diffMin} minute${diffMin > 1 ? 's' : ''} ago`;\n } else {\n return 'just now';\n }\n }\n}\n\n/**\n * Number Formatter\n * Formats numbers according to locale\n */\nexport class NumberFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a number\n * \n * @param {number} value - Number to format\n * @param {Object} [options] - Intl.NumberFormat options\n * @returns {string} Formatted number\n */\n format(value, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n const formatter = new Intl.NumberFormat(this.locale, options);\n return formatter.format(value);\n }\n \n // Fallback\n return value.toLocaleString();\n }\n\n /**\n * Format as decimal\n */\n decimal(value, decimals = 2) {\n return this.format(value, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals\n });\n }\n\n /**\n * Format as percentage\n */\n percent(value, decimals = 0) {\n return this.format(value, {\n style: 'percent',\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals\n });\n }\n\n /**\n * Format as compact (e.g., 1.2K, 3.4M)\n */\n compact(value) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n try {\n const formatter = new Intl.NumberFormat(this.locale, {\n notation: 'compact',\n compactDisplay: 'short'\n });\n return formatter.format(value);\n } catch {\n // Fallback for older browsers\n }\n }\n \n // Manual compact formatting\n if (value >= 1e9) {\n return `${(value / 1e9).toFixed(1) }B`;\n } else if (value >= 1e6) {\n return `${(value / 1e6).toFixed(1) }M`;\n } else if (value >= 1e3) {\n return `${(value / 1e3).toFixed(1) }K`;\n }\n \n return String(value);\n }\n\n /**\n * Format with units\n */\n unit(value, unit, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n try {\n const formatter = new Intl.NumberFormat(this.locale, {\n style: 'unit',\n unit,\n ...options\n });\n return formatter.format(value);\n } catch {\n // Fallback\n }\n }\n \n return `${value} ${unit}`;\n }\n}\n\n/**\n * Currency Formatter\n * Formats currency values according to locale\n */\nexport class CurrencyFormatter {\n constructor(locale = 'en', defaultCurrency = 'USD') {\n this.locale = locale;\n this.defaultCurrency = defaultCurrency;\n }\n\n /**\n * Format a currency value\n * \n * @param {number} value - Amount to format\n * @param {string} [currency] - Currency code (e.g., 'USD', 'EUR')\n * @param {Object} [options] - Additional options\n * @returns {string} Formatted currency\n */\n format(value, currency = null, options = {}) {\n const currencyCode = currency || this.defaultCurrency;\n \n if (typeof Intl !== 'undefined' && Intl.NumberFormat) {\n const formatter = new Intl.NumberFormat(this.locale, {\n style: 'currency',\n currency: currencyCode,\n ...options\n });\n return formatter.format(value);\n }\n \n // Fallback\n return `${currencyCode} ${value.toFixed(2)}`;\n }\n\n /**\n * Format without decimal places\n */\n whole(value, currency = null) {\n return this.format(value, currency, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0\n });\n }\n\n /**\n * Format with symbol only (no code)\n */\n symbol(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'symbol'\n });\n }\n\n /**\n * Format with narrow symbol\n */\n narrowSymbol(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'narrowSymbol'\n });\n }\n\n /**\n * Format with code (e.g., USD 100.00)\n */\n code(value, currency = null) {\n return this.format(value, currency, {\n currencyDisplay: 'code'\n });\n }\n}\n\n/**\n * List Formatter\n * Formats lists according to locale\n */\nexport class ListFormatter {\n constructor(locale = 'en') {\n this.locale = locale;\n }\n\n /**\n * Format a list\n * \n * @param {Array} items - Items to format\n * @param {Object} [options] - Formatting options\n * @returns {string} Formatted list\n */\n format(items, options = {}) {\n if (typeof Intl !== 'undefined' && Intl.ListFormat) {\n const formatter = new Intl.ListFormat(this.locale, options);\n return formatter.format(items);\n }\n \n // Fallback\n if (items.length === 0) return '';\n if (items.length === 1) return items[0];\n if (items.length === 2) return `${items[0]} and ${items[1]}`;\n \n const last = items[items.length - 1];\n const rest = items.slice(0, -1);\n return `${rest.join(', ')}, and ${last}`;\n }\n\n /**\n * Format as conjunction (and)\n */\n and(items) {\n return this.format(items, { type: 'conjunction' });\n }\n\n /**\n * Format as disjunction (or)\n */\n or(items) {\n return this.format(items, { type: 'disjunction' });\n }\n\n /**\n * Format as unit list\n */\n unit(items) {\n return this.format(items, { type: 'unit' });\n }\n}\n\n/**\n * Create formatters for a locale\n * \n * @param {string} locale - Locale code\n * @param {Object} [options] - Formatter options\n * @returns {Object} Formatter instances\n */\nexport function createFormatters(locale = 'en', options = {}) {\n return {\n date: new DateFormatter(locale),\n number: new NumberFormatter(locale),\n currency: new CurrencyFormatter(locale, options.defaultCurrency),\n list: new ListFormatter(locale)\n };\n}\n\nexport default {\n DateFormatter,\n NumberFormatter,\n CurrencyFormatter,\n ListFormatter,\n createFormatters\n};\n", "/**\n * Coherent.js Locale Utilities\n * \n * Utilities for locale detection and management\n * \n * @module i18n/locale\n */\n\n/**\n * Detect browser locale\n * \n * @returns {string} Detected locale code\n */\nexport function detectLocale() {\n if (typeof navigator !== 'undefined') {\n // Try navigator.language first\n if (navigator.language) {\n return normalizeLocale(navigator.language);\n }\n \n // Try navigator.languages array\n if (navigator.languages && navigator.languages.length > 0) {\n return normalizeLocale(navigator.languages[0]);\n }\n \n // Fallback to userLanguage (IE)\n if (navigator.userLanguage) {\n return normalizeLocale(navigator.userLanguage);\n }\n }\n \n // Default fallback\n return 'en';\n}\n\n/**\n * Normalize locale code\n * Converts various formats to standard format (e.g., 'en-US' -> 'en')\n * \n * @param {string} locale - Locale code\n * @param {boolean} [keepRegion=false] - Keep region code\n * @returns {string} Normalized locale\n */\nexport function normalizeLocale(locale, keepRegion = false) {\n if (!locale) return 'en';\n \n // Convert to lowercase and replace underscores\n let normalized = locale.toLowerCase().replace('_', '-');\n \n // Extract language code\n if (!keepRegion && normalized.includes('-')) {\n normalized = normalized.split('-')[0];\n }\n \n return normalized;\n}\n\n/**\n * Parse locale into components\n * \n * @param {string} locale - Locale code\n * @returns {Object} Parsed locale components\n */\nexport function parseLocale(locale) {\n const normalized = locale.replace('_', '-');\n const parts = normalized.split('-');\n \n return {\n language: parts[0]?.toLowerCase() || 'en',\n region: parts[1]?.toUpperCase() || null,\n script: parts.length > 2 ? parts[1] : null,\n full: normalized\n };\n}\n\n/**\n * Get locale direction (LTR or RTL)\n * \n * @param {string} locale - Locale code\n * @returns {string} 'ltr' or 'rtl'\n */\nexport function getLocaleDirection(locale) {\n const rtlLocales = ['ar', 'he', 'fa', 'ur', 'yi'];\n const language = parseLocale(locale).language;\n \n return rtlLocales.includes(language) ? 'rtl' : 'ltr';\n}\n\n/**\n * Check if locale is RTL\n * \n * @param {string} locale - Locale code\n * @returns {boolean} True if RTL\n */\nexport function isRTL(locale) {\n return getLocaleDirection(locale) === 'rtl';\n}\n\n/**\n * Get locale display name\n * \n * @param {string} locale - Locale code\n * @param {string} [displayLocale] - Locale to display name in\n * @returns {string} Display name\n */\nexport function getLocaleDisplayName(locale, displayLocale = 'en') {\n if (typeof Intl !== 'undefined' && Intl.DisplayNames) {\n try {\n const displayNames = new Intl.DisplayNames([displayLocale], { type: 'language' });\n return displayNames.of(locale);\n } catch {\n // Fallback\n }\n }\n \n // Fallback to locale code\n return locale;\n}\n\n/**\n * Match locale from available locales\n * Finds best matching locale from available options\n * \n * @param {string} requestedLocale - Requested locale\n * @param {Array<string>} availableLocales - Available locales\n * @param {string} [defaultLocale='en'] - Default fallback\n * @returns {string} Best matching locale\n */\nexport function matchLocale(requestedLocale, availableLocales, defaultLocale = 'en') {\n const normalized = normalizeLocale(requestedLocale);\n \n // Exact match\n if (availableLocales.includes(normalized)) {\n return normalized;\n }\n \n // Try with region\n const withRegion = normalizeLocale(requestedLocale, true);\n if (availableLocales.includes(withRegion)) {\n return withRegion;\n }\n \n // Try language match (ignore region)\n const language = parseLocale(requestedLocale).language;\n const languageMatch = availableLocales.find(locale => \n parseLocale(locale).language === language\n );\n \n if (languageMatch) {\n return languageMatch;\n }\n \n // Fallback to default\n return availableLocales.includes(defaultLocale) ? defaultLocale : availableLocales[0];\n}\n\n/**\n * Get supported locales from browser\n * \n * @returns {Array<string>} Array of supported locales\n */\nexport function getSupportedLocales() {\n if (typeof navigator !== 'undefined' && navigator.languages) {\n return navigator.languages.map(locale => normalizeLocale(locale));\n }\n \n return [detectLocale()];\n}\n\n/**\n * Locale Manager\n * Manages locale state and persistence\n */\nexport class LocaleManager {\n constructor(options = {}) {\n this.options = {\n defaultLocale: 'en',\n availableLocales: ['en'],\n storageKey: 'coherent-locale',\n autoDetect: true,\n ...options\n };\n \n this.currentLocale = this.options.defaultLocale;\n this.listeners = [];\n \n // Auto-detect or load from storage\n if (this.options.autoDetect) {\n this.currentLocale = this.detectAndMatch();\n }\n \n this.loadFromStorage();\n }\n\n /**\n * Detect and match best locale\n */\n detectAndMatch() {\n const detected = detectLocale();\n return matchLocale(\n detected,\n this.options.availableLocales,\n this.options.defaultLocale\n );\n }\n\n /**\n * Get current locale\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Set locale\n */\n setLocale(locale) {\n const matched = matchLocale(\n locale,\n this.options.availableLocales,\n this.options.defaultLocale\n );\n \n if (matched !== this.currentLocale) {\n const oldLocale = this.currentLocale;\n this.currentLocale = matched;\n \n this.saveToStorage();\n this.notifyListeners(oldLocale, matched);\n }\n }\n\n /**\n * Add locale change listener\n */\n onChange(listener) {\n this.listeners.push(listener);\n \n // Return unsubscribe function\n return () => {\n const index = this.listeners.indexOf(listener);\n if (index > -1) {\n this.listeners.splice(index, 1);\n }\n };\n }\n\n /**\n * Notify listeners of locale change\n */\n notifyListeners(oldLocale, newLocale) {\n this.listeners.forEach(listener => {\n try {\n listener(newLocale, oldLocale);\n } catch (error) {\n console.error('Error in locale change listener:', error);\n }\n });\n }\n\n /**\n * Save locale to storage\n */\n saveToStorage() {\n if (typeof localStorage !== 'undefined') {\n try {\n localStorage.setItem(this.options.storageKey, this.currentLocale);\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n /**\n * Load locale from storage\n */\n loadFromStorage() {\n if (typeof localStorage !== 'undefined') {\n try {\n const stored = localStorage.getItem(this.options.storageKey);\n if (stored) {\n this.setLocale(stored);\n }\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n /**\n * Get available locales\n */\n getAvailableLocales() {\n return [...this.options.availableLocales];\n }\n\n /**\n * Check if locale is available\n */\n isAvailable(locale) {\n return this.options.availableLocales.includes(locale);\n }\n}\n\n/**\n * Create a locale manager\n */\nexport function createLocaleManager(options = {}) {\n return new LocaleManager(options);\n}\n\nexport default {\n detectLocale,\n normalizeLocale,\n parseLocale,\n getLocaleDirection,\n isRTL,\n getLocaleDisplayName,\n matchLocale,\n getSupportedLocales,\n LocaleManager,\n createLocaleManager\n};\n"],
|
|
5
|
+
"mappings": ";AAYO,IAAM,aAAN,MAAiB;AAAA,EACtB,YAAY,UAAU,CAAC,GAAG;AACxB,SAAK,UAAU;AAAA,MACb,eAAe;AAAA,MACf,gBAAgB;AAAA,MAChB,mBAAmB;AAAA,MACnB,eAAe;AAAA,QACb,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV;AAAA,MACA,GAAG;AAAA,IACL;AAEA,SAAK,eAAe,oBAAI,IAAI;AAC5B,SAAK,gBAAgB,KAAK,QAAQ;AAClC,SAAK,gBAAgB,oBAAI,IAAI;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,gBAAgB,QAAQ,cAAc;AACpC,QAAI,CAAC,KAAK,aAAa,IAAI,MAAM,GAAG;AAClC,WAAK,aAAa,IAAI,QAAQ,CAAC,CAAC;AAAA,IAClC;AAEA,UAAM,WAAW,KAAK,aAAa,IAAI,MAAM;AAC7C,SAAK,aAAa,IAAI,QAAQ,KAAK,UAAU,UAAU,YAAY,CAAC;AACpE,SAAK,cAAc,IAAI,MAAM;AAAA,EAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAQ,QAAQ;AACxB,UAAM,SAAS,EAAE,GAAG,OAAO;AAE3B,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/D,eAAO,GAAG,IAAI,KAAK,UAAU,OAAO,GAAG,KAAK,CAAC,GAAG,KAAK;AAAA,MACvD,OAAO;AACL,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAU,QAAQ;AAChB,QAAI,CAAC,KAAK,cAAc,IAAI,MAAM,GAAG;AACnC,cAAQ,KAAK,UAAU,MAAM,6BAA6B;AAC1D,WAAK,gBAAgB,KAAK,QAAQ;AAAA,IACpC,OAAO;AACL,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAY;AACV,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,EAAE,KAAK,SAAS,CAAC,GAAG,SAAS,MAAM;AACjC,UAAM,eAAe,UAAU,KAAK;AAGpC,QAAI,cAAc,KAAK,eAAe,KAAK,YAAY;AAGvD,QAAI,gBAAgB,QAAQ,iBAAiB,KAAK,QAAQ,gBAAgB;AACxE,oBAAc,KAAK,eAAe,KAAK,KAAK,QAAQ,cAAc;AAAA,IACpE;AAGA,QAAI,gBAAgB,MAAM;AACxB,UAAI,KAAK,QAAQ,mBAAmB;AAClC,eAAO,KAAK,QAAQ,kBAAkB,KAAK,YAAY;AAAA,MACzD;AACA,aAAO;AAAA,IACT;AAGA,QAAI,OAAO,gBAAgB,YAAY,OAAO,UAAU,QAAW;AACjE,oBAAc,KAAK,aAAa,aAAa,OAAO,OAAO,YAAY;AAAA,IACzE;AAGA,QAAI,OAAO,gBAAgB,UAAU;AACnC,aAAO,KAAK,YAAY,aAAa,MAAM;AAAA,IAC7C;AAEA,WAAO,OAAO,WAAW;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,KAAK,QAAQ;AAC1B,UAAM,eAAe,KAAK,aAAa,IAAI,MAAM;AACjD,QAAI,CAAC,aAAc,QAAO;AAE1B,UAAM,OAAO,IAAI,MAAM,GAAG;AAC1B,QAAI,QAAQ;AAEZ,eAAW,KAAK,MAAM;AACpB,UAAI,SAAS,OAAO,UAAU,YAAY,KAAK,OAAO;AACpD,gBAAQ,MAAM,CAAC;AAAA,MACjB,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,cAAc,OAAO,QAAQ;AAExC,QAAI,UAAU,KAAK,aAAa,MAAM;AACpC,aAAO,aAAa;AAAA,IACtB;AAGA,QAAI,OAAO,SAAS,eAAe,KAAK,aAAa;AACnD,YAAM,QAAQ,IAAI,KAAK,YAAY,MAAM;AACzC,YAAM,OAAO,MAAM,OAAO,KAAK;AAE/B,UAAI,aAAa,IAAI,GAAG;AACtB,eAAO,aAAa,IAAI;AAAA,MAC1B;AAAA,IACF;AAGA,QAAI,UAAU,KAAK,aAAa,KAAK;AACnC,aAAO,aAAa;AAAA,IACtB,WAAW,aAAa,OAAO;AAC7B,aAAO,aAAa;AAAA,IACtB;AAEA,WAAO,aAAa,OAAO,aAAa,SAAS;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,KAAK,QAAQ;AACvB,UAAM,EAAE,QAAQ,OAAO,IAAI,KAAK,QAAQ;AACxC,QAAI,SAAS;AAEb,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,YAAM,cAAc,GAAG,MAAM,GAAG,GAAG,GAAG,MAAM;AAC5C,eAAS,OAAO,QAAQ,IAAI,OAAO,aAAa,GAAG,GAAG,OAAO,KAAK,CAAC;AAAA,IACrE;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,IAAI,KAAK,SAAS,MAAM;AACtB,UAAM,eAAe,UAAU,KAAK;AACpC,WAAO,KAAK,eAAe,KAAK,YAAY,MAAM;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,gBAAgB,SAAS,MAAM;AAC7B,UAAM,eAAe,UAAU,KAAK;AACpC,WAAO,KAAK,aAAa,IAAI,YAAY,KAAK,CAAC;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,mBAAmB;AACjB,WAAO,MAAM,KAAK,KAAK,aAAa;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,aAAa,QAAQ;AACnB,SAAK,aAAa,OAAO,MAAM;AAC/B,SAAK,cAAc,OAAO,MAAM;AAEhC,QAAI,KAAK,kBAAkB,QAAQ;AACjC,WAAK,gBAAgB,KAAK,QAAQ;AAAA,IACpC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ;AACN,SAAK,aAAa,MAAM;AACxB,SAAK,cAAc,MAAM;AACzB,SAAK,gBAAgB,KAAK,QAAQ;AAAA,EACpC;AACF;AAQO,SAAS,iBAAiB,UAAU,CAAC,GAAG;AAC7C,SAAO,IAAI,WAAW,OAAO;AAC/B;AAUO,SAAS,uBAAuB,YAAY,WAAW;AAC5D,SAAO;AAAA,IACL,GAAG,CAAC,KAAK,QAAQ,WAAW;AAC1B,aAAO,WAAW,EAAE,GAAG,SAAS,IAAI,GAAG,IAAI,QAAQ,MAAM;AAAA,IAC3D;AAAA,IACA,KAAK,CAAC,KAAK,WAAW;AACpB,aAAO,WAAW,IAAI,GAAG,SAAS,IAAI,GAAG,IAAI,MAAM;AAAA,IACrD;AAAA,IACA,WAAW,MAAM,WAAW,UAAU;AAAA,IACtC,WAAW,CAAC,WAAW,WAAW,UAAU,MAAM;AAAA,EACpD;AACF;;;ACvQO,IAAM,gBAAN,MAAoB;AAAA,EACzB,YAAY,SAAS,MAAM;AACzB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,MAAM,UAAU,CAAC,GAAG;AACzB,UAAM,UAAU,gBAAgB,OAAO,OAAO,IAAI,KAAK,IAAI;AAE3D,QAAI,OAAO,SAAS,eAAe,KAAK,gBAAgB;AACtD,YAAM,YAAY,IAAI,KAAK,eAAe,KAAK,QAAQ,OAAO;AAC9D,aAAO,UAAU,OAAO,OAAO;AAAA,IACjC;AAGA,WAAO,QAAQ,mBAAmB;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,MAAM;AACV,WAAO,KAAK,OAAO,MAAM;AAAA,MACvB,MAAM;AAAA,MACN,OAAO;AAAA,MACP,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,MAAM;AACX,WAAO,KAAK,OAAO,MAAM;AAAA,MACvB,MAAM;AAAA,MACN,OAAO;AAAA,MACP,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,MAAM;AACT,WAAO,KAAK,OAAO,MAAM;AAAA,MACvB,MAAM;AAAA,MACN,OAAO;AAAA,MACP,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,MAAM;AACT,WAAO,KAAK,OAAO,MAAM;AAAA,MACvB,SAAS;AAAA,MACT,MAAM;AAAA,MACN,OAAO;AAAA,MACP,KAAK;AAAA,IACP,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,MAAM,UAAU,CAAC,GAAG;AACvB,WAAO,KAAK,OAAO,MAAM;AAAA,MACvB,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAM,UAAU,CAAC,GAAG;AAC3B,WAAO,KAAK,OAAO,MAAM;AAAA,MACvB,MAAM;AAAA,MACN,OAAO;AAAA,MACP,KAAK;AAAA,MACL,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,GAAG;AAAA,IACL,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAM;AACb,UAAM,UAAU,gBAAgB,OAAO,OAAO,IAAI,KAAK,IAAI;AAC3D,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,SAAS,MAAM;AACrB,UAAM,UAAU,KAAK,MAAM,SAAS,GAAI;AACxC,UAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,UAAM,WAAW,KAAK,MAAM,UAAU,EAAE;AACxC,UAAM,UAAU,KAAK,MAAM,WAAW,EAAE;AAExC,QAAI,OAAO,SAAS,eAAe,KAAK,oBAAoB;AAC1D,YAAM,MAAM,IAAI,KAAK,mBAAmB,KAAK,QAAQ,EAAE,SAAS,OAAO,CAAC;AAExE,UAAI,UAAU,GAAG;AACf,eAAO,IAAI,OAAO,CAAC,SAAS,KAAK;AAAA,MACnC,WAAW,WAAW,GAAG;AACvB,eAAO,IAAI,OAAO,CAAC,UAAU,MAAM;AAAA,MACrC,WAAW,UAAU,GAAG;AACtB,eAAO,IAAI,OAAO,CAAC,SAAS,QAAQ;AAAA,MACtC,OAAO;AACL,eAAO,IAAI,OAAO,CAAC,SAAS,QAAQ;AAAA,MACtC;AAAA,IACF;AAGA,QAAI,UAAU,GAAG;AACf,aAAO,GAAG,OAAO,OAAO,UAAU,IAAI,MAAM,EAAE;AAAA,IAChD,WAAW,WAAW,GAAG;AACvB,aAAO,GAAG,QAAQ,QAAQ,WAAW,IAAI,MAAM,EAAE;AAAA,IACnD,WAAW,UAAU,GAAG;AACtB,aAAO,GAAG,OAAO,UAAU,UAAU,IAAI,MAAM,EAAE;AAAA,IACnD,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAMO,IAAM,kBAAN,MAAsB;AAAA,EAC3B,YAAY,SAAS,MAAM;AACzB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,OAAO,UAAU,CAAC,GAAG;AAC1B,QAAI,OAAO,SAAS,eAAe,KAAK,cAAc;AACpD,YAAM,YAAY,IAAI,KAAK,aAAa,KAAK,QAAQ,OAAO;AAC5D,aAAO,UAAU,OAAO,KAAK;AAAA,IAC/B;AAGA,WAAO,MAAM,eAAe;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,OAAO,WAAW,GAAG;AAC3B,WAAO,KAAK,OAAO,OAAO;AAAA,MACxB,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,OAAO,WAAW,GAAG;AAC3B,WAAO,KAAK,OAAO,OAAO;AAAA,MACxB,OAAO;AAAA,MACP,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,OAAO;AACb,QAAI,OAAO,SAAS,eAAe,KAAK,cAAc;AACpD,UAAI;AACF,cAAM,YAAY,IAAI,KAAK,aAAa,KAAK,QAAQ;AAAA,UACnD,UAAU;AAAA,UACV,gBAAgB;AAAA,QAClB,CAAC;AACD,eAAO,UAAU,OAAO,KAAK;AAAA,MAC/B,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,QAAI,SAAS,KAAK;AAChB,aAAO,IAAI,QAAQ,KAAK,QAAQ,CAAC,CAAG;AAAA,IACtC,WAAW,SAAS,KAAK;AACvB,aAAO,IAAI,QAAQ,KAAK,QAAQ,CAAC,CAAG;AAAA,IACtC,WAAW,SAAS,KAAK;AACvB,aAAO,IAAI,QAAQ,KAAK,QAAQ,CAAC,CAAG;AAAA,IACtC;AAEA,WAAO,OAAO,KAAK;AAAA,EACrB;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,OAAO,MAAM,UAAU,CAAC,GAAG;AAC9B,QAAI,OAAO,SAAS,eAAe,KAAK,cAAc;AACpD,UAAI;AACF,cAAM,YAAY,IAAI,KAAK,aAAa,KAAK,QAAQ;AAAA,UACnD,OAAO;AAAA,UACP;AAAA,UACA,GAAG;AAAA,QACL,CAAC;AACD,eAAO,UAAU,OAAO,KAAK;AAAA,MAC/B,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,WAAO,GAAG,KAAK,IAAI,IAAI;AAAA,EACzB;AACF;AAMO,IAAM,oBAAN,MAAwB;AAAA,EAC7B,YAAY,SAAS,MAAM,kBAAkB,OAAO;AAClD,SAAK,SAAS;AACd,SAAK,kBAAkB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,OAAO,OAAO,WAAW,MAAM,UAAU,CAAC,GAAG;AAC3C,UAAM,eAAe,YAAY,KAAK;AAEtC,QAAI,OAAO,SAAS,eAAe,KAAK,cAAc;AACpD,YAAM,YAAY,IAAI,KAAK,aAAa,KAAK,QAAQ;AAAA,QACnD,OAAO;AAAA,QACP,UAAU;AAAA,QACV,GAAG;AAAA,MACL,CAAC;AACD,aAAO,UAAU,OAAO,KAAK;AAAA,IAC/B;AAGA,WAAO,GAAG,YAAY,IAAI,MAAM,QAAQ,CAAC,CAAC;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,OAAO,WAAW,MAAM;AAC5B,WAAO,KAAK,OAAO,OAAO,UAAU;AAAA,MAClC,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,OAAO,WAAW,MAAM;AAC7B,WAAO,KAAK,OAAO,OAAO,UAAU;AAAA,MAClC,iBAAiB;AAAA,IACnB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,aAAa,OAAO,WAAW,MAAM;AACnC,WAAO,KAAK,OAAO,OAAO,UAAU;AAAA,MAClC,iBAAiB;AAAA,IACnB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,OAAO,WAAW,MAAM;AAC3B,WAAO,KAAK,OAAO,OAAO,UAAU;AAAA,MAClC,iBAAiB;AAAA,IACnB,CAAC;AAAA,EACH;AACF;AAMO,IAAM,gBAAN,MAAoB;AAAA,EACzB,YAAY,SAAS,MAAM;AACzB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,OAAO,UAAU,CAAC,GAAG;AAC1B,QAAI,OAAO,SAAS,eAAe,KAAK,YAAY;AAClD,YAAM,YAAY,IAAI,KAAK,WAAW,KAAK,QAAQ,OAAO;AAC1D,aAAO,UAAU,OAAO,KAAK;AAAA,IAC/B;AAGA,QAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAI,MAAM,WAAW,EAAG,QAAO,MAAM,CAAC;AACtC,QAAI,MAAM,WAAW,EAAG,QAAO,GAAG,MAAM,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC;AAE1D,UAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,UAAM,OAAO,MAAM,MAAM,GAAG,EAAE;AAC9B,WAAO,GAAG,KAAK,KAAK,IAAI,CAAC,SAAS,IAAI;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAO;AACT,WAAO,KAAK,OAAO,OAAO,EAAE,MAAM,cAAc,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,GAAG,OAAO;AACR,WAAO,KAAK,OAAO,OAAO,EAAE,MAAM,cAAc,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,KAAK,OAAO;AACV,WAAO,KAAK,OAAO,OAAO,EAAE,MAAM,OAAO,CAAC;AAAA,EAC5C;AACF;AASO,SAAS,iBAAiB,SAAS,MAAM,UAAU,CAAC,GAAG;AAC5D,SAAO;AAAA,IACL,MAAM,IAAI,cAAc,MAAM;AAAA,IAC9B,QAAQ,IAAI,gBAAgB,MAAM;AAAA,IAClC,UAAU,IAAI,kBAAkB,QAAQ,QAAQ,eAAe;AAAA,IAC/D,MAAM,IAAI,cAAc,MAAM;AAAA,EAChC;AACF;;;AChXO,SAAS,eAAe;AAC7B,MAAI,OAAO,cAAc,aAAa;AAEpC,QAAI,UAAU,UAAU;AACtB,aAAO,gBAAgB,UAAU,QAAQ;AAAA,IAC3C;AAGA,QAAI,UAAU,aAAa,UAAU,UAAU,SAAS,GAAG;AACzD,aAAO,gBAAgB,UAAU,UAAU,CAAC,CAAC;AAAA,IAC/C;AAGA,QAAI,UAAU,cAAc;AAC1B,aAAO,gBAAgB,UAAU,YAAY;AAAA,IAC/C;AAAA,EACF;AAGA,SAAO;AACT;AAUO,SAAS,gBAAgB,QAAQ,aAAa,OAAO;AAC1D,MAAI,CAAC,OAAQ,QAAO;AAGpB,MAAI,aAAa,OAAO,YAAY,EAAE,QAAQ,KAAK,GAAG;AAGtD,MAAI,CAAC,cAAc,WAAW,SAAS,GAAG,GAAG;AAC3C,iBAAa,WAAW,MAAM,GAAG,EAAE,CAAC;AAAA,EACtC;AAEA,SAAO;AACT;AAQO,SAAS,YAAY,QAAQ;AAClC,QAAM,aAAa,OAAO,QAAQ,KAAK,GAAG;AAC1C,QAAM,QAAQ,WAAW,MAAM,GAAG;AAElC,SAAO;AAAA,IACL,UAAU,MAAM,CAAC,GAAG,YAAY,KAAK;AAAA,IACrC,QAAQ,MAAM,CAAC,GAAG,YAAY,KAAK;AAAA,IACnC,QAAQ,MAAM,SAAS,IAAI,MAAM,CAAC,IAAI;AAAA,IACtC,MAAM;AAAA,EACR;AACF;AAQO,SAAS,mBAAmB,QAAQ;AACzC,QAAM,aAAa,CAAC,MAAM,MAAM,MAAM,MAAM,IAAI;AAChD,QAAM,WAAW,YAAY,MAAM,EAAE;AAErC,SAAO,WAAW,SAAS,QAAQ,IAAI,QAAQ;AACjD;AAQO,SAAS,MAAM,QAAQ;AAC5B,SAAO,mBAAmB,MAAM,MAAM;AACxC;AASO,SAAS,qBAAqB,QAAQ,gBAAgB,MAAM;AACjE,MAAI,OAAO,SAAS,eAAe,KAAK,cAAc;AACpD,QAAI;AACF,YAAM,eAAe,IAAI,KAAK,aAAa,CAAC,aAAa,GAAG,EAAE,MAAM,WAAW,CAAC;AAChF,aAAO,aAAa,GAAG,MAAM;AAAA,IAC/B,QAAQ;AAAA,IAER;AAAA,EACF;AAGA,SAAO;AACT;AAWO,SAAS,YAAY,iBAAiB,kBAAkB,gBAAgB,MAAM;AACnF,QAAM,aAAa,gBAAgB,eAAe;AAGlD,MAAI,iBAAiB,SAAS,UAAU,GAAG;AACzC,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,gBAAgB,iBAAiB,IAAI;AACxD,MAAI,iBAAiB,SAAS,UAAU,GAAG;AACzC,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,YAAY,eAAe,EAAE;AAC9C,QAAM,gBAAgB,iBAAiB;AAAA,IAAK,YAC1C,YAAY,MAAM,EAAE,aAAa;AAAA,EACnC;AAEA,MAAI,eAAe;AACjB,WAAO;AAAA,EACT;AAGA,SAAO,iBAAiB,SAAS,aAAa,IAAI,gBAAgB,iBAAiB,CAAC;AACtF;AAOO,SAAS,sBAAsB;AACpC,MAAI,OAAO,cAAc,eAAe,UAAU,WAAW;AAC3D,WAAO,UAAU,UAAU,IAAI,YAAU,gBAAgB,MAAM,CAAC;AAAA,EAClE;AAEA,SAAO,CAAC,aAAa,CAAC;AACxB;AAMO,IAAM,gBAAN,MAAoB;AAAA,EACzB,YAAY,UAAU,CAAC,GAAG;AACxB,SAAK,UAAU;AAAA,MACb,eAAe;AAAA,MACf,kBAAkB,CAAC,IAAI;AAAA,MACvB,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,GAAG;AAAA,IACL;AAEA,SAAK,gBAAgB,KAAK,QAAQ;AAClC,SAAK,YAAY,CAAC;AAGlB,QAAI,KAAK,QAAQ,YAAY;AAC3B,WAAK,gBAAgB,KAAK,eAAe;AAAA,IAC3C;AAEA,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAiB;AACf,UAAM,WAAW,aAAa;AAC9B,WAAO;AAAA,MACL;AAAA,MACA,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY;AACV,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,QAAQ;AAChB,UAAM,UAAU;AAAA,MACd;AAAA,MACA,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAEA,QAAI,YAAY,KAAK,eAAe;AAClC,YAAM,YAAY,KAAK;AACvB,WAAK,gBAAgB;AAErB,WAAK,cAAc;AACnB,WAAK,gBAAgB,WAAW,OAAO;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,UAAU;AACjB,SAAK,UAAU,KAAK,QAAQ;AAG5B,WAAO,MAAM;AACX,YAAM,QAAQ,KAAK,UAAU,QAAQ,QAAQ;AAC7C,UAAI,QAAQ,IAAI;AACd,aAAK,UAAU,OAAO,OAAO,CAAC;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,WAAW,WAAW;AACpC,SAAK,UAAU,QAAQ,cAAY;AACjC,UAAI;AACF,iBAAS,WAAW,SAAS;AAAA,MAC/B,SAAS,OAAO;AACd,gBAAQ,MAAM,oCAAoC,KAAK;AAAA,MACzD;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB;AACd,QAAI,OAAO,iBAAiB,aAAa;AACvC,UAAI;AACF,qBAAa,QAAQ,KAAK,QAAQ,YAAY,KAAK,aAAa;AAAA,MAClE,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkB;AAChB,QAAI,OAAO,iBAAiB,aAAa;AACvC,UAAI;AACF,cAAM,SAAS,aAAa,QAAQ,KAAK,QAAQ,UAAU;AAC3D,YAAI,QAAQ;AACV,eAAK,UAAU,MAAM;AAAA,QACvB;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,sBAAsB;AACpB,WAAO,CAAC,GAAG,KAAK,QAAQ,gBAAgB;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,QAAQ;AAClB,WAAO,KAAK,QAAQ,iBAAiB,SAAS,MAAM;AAAA,EACtD;AACF;AAKO,SAAS,oBAAoB,UAAU,CAAC,GAAG;AAChD,SAAO,IAAI,cAAc,OAAO;AAClC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/locale.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
// src/locale.js
|
|
2
|
+
function detectLocale() {
|
|
3
|
+
if (typeof navigator !== "undefined") {
|
|
4
|
+
if (navigator.language) {
|
|
5
|
+
return normalizeLocale(navigator.language);
|
|
6
|
+
}
|
|
7
|
+
if (navigator.languages && navigator.languages.length > 0) {
|
|
8
|
+
return normalizeLocale(navigator.languages[0]);
|
|
9
|
+
}
|
|
10
|
+
if (navigator.userLanguage) {
|
|
11
|
+
return normalizeLocale(navigator.userLanguage);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return "en";
|
|
15
|
+
}
|
|
16
|
+
function normalizeLocale(locale, keepRegion = false) {
|
|
17
|
+
if (!locale) return "en";
|
|
18
|
+
let normalized = locale.toLowerCase().replace("_", "-");
|
|
19
|
+
if (!keepRegion && normalized.includes("-")) {
|
|
20
|
+
normalized = normalized.split("-")[0];
|
|
21
|
+
}
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
function parseLocale(locale) {
|
|
25
|
+
const normalized = locale.replace("_", "-");
|
|
26
|
+
const parts = normalized.split("-");
|
|
27
|
+
return {
|
|
28
|
+
language: parts[0]?.toLowerCase() || "en",
|
|
29
|
+
region: parts[1]?.toUpperCase() || null,
|
|
30
|
+
script: parts.length > 2 ? parts[1] : null,
|
|
31
|
+
full: normalized
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function getLocaleDirection(locale) {
|
|
35
|
+
const rtlLocales = ["ar", "he", "fa", "ur", "yi"];
|
|
36
|
+
const language = parseLocale(locale).language;
|
|
37
|
+
return rtlLocales.includes(language) ? "rtl" : "ltr";
|
|
38
|
+
}
|
|
39
|
+
function isRTL(locale) {
|
|
40
|
+
return getLocaleDirection(locale) === "rtl";
|
|
41
|
+
}
|
|
42
|
+
function getLocaleDisplayName(locale, displayLocale = "en") {
|
|
43
|
+
if (typeof Intl !== "undefined" && Intl.DisplayNames) {
|
|
44
|
+
try {
|
|
45
|
+
const displayNames = new Intl.DisplayNames([displayLocale], { type: "language" });
|
|
46
|
+
return displayNames.of(locale);
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return locale;
|
|
51
|
+
}
|
|
52
|
+
function matchLocale(requestedLocale, availableLocales, defaultLocale = "en") {
|
|
53
|
+
const normalized = normalizeLocale(requestedLocale);
|
|
54
|
+
if (availableLocales.includes(normalized)) {
|
|
55
|
+
return normalized;
|
|
56
|
+
}
|
|
57
|
+
const withRegion = normalizeLocale(requestedLocale, true);
|
|
58
|
+
if (availableLocales.includes(withRegion)) {
|
|
59
|
+
return withRegion;
|
|
60
|
+
}
|
|
61
|
+
const language = parseLocale(requestedLocale).language;
|
|
62
|
+
const languageMatch = availableLocales.find(
|
|
63
|
+
(locale) => parseLocale(locale).language === language
|
|
64
|
+
);
|
|
65
|
+
if (languageMatch) {
|
|
66
|
+
return languageMatch;
|
|
67
|
+
}
|
|
68
|
+
return availableLocales.includes(defaultLocale) ? defaultLocale : availableLocales[0];
|
|
69
|
+
}
|
|
70
|
+
function getSupportedLocales() {
|
|
71
|
+
if (typeof navigator !== "undefined" && navigator.languages) {
|
|
72
|
+
return navigator.languages.map((locale) => normalizeLocale(locale));
|
|
73
|
+
}
|
|
74
|
+
return [detectLocale()];
|
|
75
|
+
}
|
|
76
|
+
var LocaleManager = class {
|
|
77
|
+
constructor(options = {}) {
|
|
78
|
+
this.options = {
|
|
79
|
+
defaultLocale: "en",
|
|
80
|
+
availableLocales: ["en"],
|
|
81
|
+
storageKey: "coherent-locale",
|
|
82
|
+
autoDetect: true,
|
|
83
|
+
...options
|
|
84
|
+
};
|
|
85
|
+
this.currentLocale = this.options.defaultLocale;
|
|
86
|
+
this.listeners = [];
|
|
87
|
+
if (this.options.autoDetect) {
|
|
88
|
+
this.currentLocale = this.detectAndMatch();
|
|
89
|
+
}
|
|
90
|
+
this.loadFromStorage();
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Detect and match best locale
|
|
94
|
+
*/
|
|
95
|
+
detectAndMatch() {
|
|
96
|
+
const detected = detectLocale();
|
|
97
|
+
return matchLocale(
|
|
98
|
+
detected,
|
|
99
|
+
this.options.availableLocales,
|
|
100
|
+
this.options.defaultLocale
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Get current locale
|
|
105
|
+
*/
|
|
106
|
+
getLocale() {
|
|
107
|
+
return this.currentLocale;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Set locale
|
|
111
|
+
*/
|
|
112
|
+
setLocale(locale) {
|
|
113
|
+
const matched = matchLocale(
|
|
114
|
+
locale,
|
|
115
|
+
this.options.availableLocales,
|
|
116
|
+
this.options.defaultLocale
|
|
117
|
+
);
|
|
118
|
+
if (matched !== this.currentLocale) {
|
|
119
|
+
const oldLocale = this.currentLocale;
|
|
120
|
+
this.currentLocale = matched;
|
|
121
|
+
this.saveToStorage();
|
|
122
|
+
this.notifyListeners(oldLocale, matched);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Add locale change listener
|
|
127
|
+
*/
|
|
128
|
+
onChange(listener) {
|
|
129
|
+
this.listeners.push(listener);
|
|
130
|
+
return () => {
|
|
131
|
+
const index = this.listeners.indexOf(listener);
|
|
132
|
+
if (index > -1) {
|
|
133
|
+
this.listeners.splice(index, 1);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Notify listeners of locale change
|
|
139
|
+
*/
|
|
140
|
+
notifyListeners(oldLocale, newLocale) {
|
|
141
|
+
this.listeners.forEach((listener) => {
|
|
142
|
+
try {
|
|
143
|
+
listener(newLocale, oldLocale);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error("Error in locale change listener:", error);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Save locale to storage
|
|
151
|
+
*/
|
|
152
|
+
saveToStorage() {
|
|
153
|
+
if (typeof localStorage !== "undefined") {
|
|
154
|
+
try {
|
|
155
|
+
localStorage.setItem(this.options.storageKey, this.currentLocale);
|
|
156
|
+
} catch {
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Load locale from storage
|
|
162
|
+
*/
|
|
163
|
+
loadFromStorage() {
|
|
164
|
+
if (typeof localStorage !== "undefined") {
|
|
165
|
+
try {
|
|
166
|
+
const stored = localStorage.getItem(this.options.storageKey);
|
|
167
|
+
if (stored) {
|
|
168
|
+
this.setLocale(stored);
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Get available locales
|
|
176
|
+
*/
|
|
177
|
+
getAvailableLocales() {
|
|
178
|
+
return [...this.options.availableLocales];
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Check if locale is available
|
|
182
|
+
*/
|
|
183
|
+
isAvailable(locale) {
|
|
184
|
+
return this.options.availableLocales.includes(locale);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
function createLocaleManager(options = {}) {
|
|
188
|
+
return new LocaleManager(options);
|
|
189
|
+
}
|
|
190
|
+
var locale_default = {
|
|
191
|
+
detectLocale,
|
|
192
|
+
normalizeLocale,
|
|
193
|
+
parseLocale,
|
|
194
|
+
getLocaleDirection,
|
|
195
|
+
isRTL,
|
|
196
|
+
getLocaleDisplayName,
|
|
197
|
+
matchLocale,
|
|
198
|
+
getSupportedLocales,
|
|
199
|
+
LocaleManager,
|
|
200
|
+
createLocaleManager
|
|
201
|
+
};
|
|
202
|
+
export {
|
|
203
|
+
LocaleManager,
|
|
204
|
+
createLocaleManager,
|
|
205
|
+
locale_default as default,
|
|
206
|
+
detectLocale,
|
|
207
|
+
getLocaleDirection,
|
|
208
|
+
getLocaleDisplayName,
|
|
209
|
+
getSupportedLocales,
|
|
210
|
+
isRTL,
|
|
211
|
+
matchLocale,
|
|
212
|
+
normalizeLocale,
|
|
213
|
+
parseLocale
|
|
214
|
+
};
|
|
215
|
+
//# sourceMappingURL=locale.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/locale.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Coherent.js Locale Utilities\n * \n * Utilities for locale detection and management\n * \n * @module i18n/locale\n */\n\n/**\n * Detect browser locale\n * \n * @returns {string} Detected locale code\n */\nexport function detectLocale() {\n if (typeof navigator !== 'undefined') {\n // Try navigator.language first\n if (navigator.language) {\n return normalizeLocale(navigator.language);\n }\n \n // Try navigator.languages array\n if (navigator.languages && navigator.languages.length > 0) {\n return normalizeLocale(navigator.languages[0]);\n }\n \n // Fallback to userLanguage (IE)\n if (navigator.userLanguage) {\n return normalizeLocale(navigator.userLanguage);\n }\n }\n \n // Default fallback\n return 'en';\n}\n\n/**\n * Normalize locale code\n * Converts various formats to standard format (e.g., 'en-US' -> 'en')\n * \n * @param {string} locale - Locale code\n * @param {boolean} [keepRegion=false] - Keep region code\n * @returns {string} Normalized locale\n */\nexport function normalizeLocale(locale, keepRegion = false) {\n if (!locale) return 'en';\n \n // Convert to lowercase and replace underscores\n let normalized = locale.toLowerCase().replace('_', '-');\n \n // Extract language code\n if (!keepRegion && normalized.includes('-')) {\n normalized = normalized.split('-')[0];\n }\n \n return normalized;\n}\n\n/**\n * Parse locale into components\n * \n * @param {string} locale - Locale code\n * @returns {Object} Parsed locale components\n */\nexport function parseLocale(locale) {\n const normalized = locale.replace('_', '-');\n const parts = normalized.split('-');\n \n return {\n language: parts[0]?.toLowerCase() || 'en',\n region: parts[1]?.toUpperCase() || null,\n script: parts.length > 2 ? parts[1] : null,\n full: normalized\n };\n}\n\n/**\n * Get locale direction (LTR or RTL)\n * \n * @param {string} locale - Locale code\n * @returns {string} 'ltr' or 'rtl'\n */\nexport function getLocaleDirection(locale) {\n const rtlLocales = ['ar', 'he', 'fa', 'ur', 'yi'];\n const language = parseLocale(locale).language;\n \n return rtlLocales.includes(language) ? 'rtl' : 'ltr';\n}\n\n/**\n * Check if locale is RTL\n * \n * @param {string} locale - Locale code\n * @returns {boolean} True if RTL\n */\nexport function isRTL(locale) {\n return getLocaleDirection(locale) === 'rtl';\n}\n\n/**\n * Get locale display name\n * \n * @param {string} locale - Locale code\n * @param {string} [displayLocale] - Locale to display name in\n * @returns {string} Display name\n */\nexport function getLocaleDisplayName(locale, displayLocale = 'en') {\n if (typeof Intl !== 'undefined' && Intl.DisplayNames) {\n try {\n const displayNames = new Intl.DisplayNames([displayLocale], { type: 'language' });\n return displayNames.of(locale);\n } catch {\n // Fallback\n }\n }\n \n // Fallback to locale code\n return locale;\n}\n\n/**\n * Match locale from available locales\n * Finds best matching locale from available options\n * \n * @param {string} requestedLocale - Requested locale\n * @param {Array<string>} availableLocales - Available locales\n * @param {string} [defaultLocale='en'] - Default fallback\n * @returns {string} Best matching locale\n */\nexport function matchLocale(requestedLocale, availableLocales, defaultLocale = 'en') {\n const normalized = normalizeLocale(requestedLocale);\n \n // Exact match\n if (availableLocales.includes(normalized)) {\n return normalized;\n }\n \n // Try with region\n const withRegion = normalizeLocale(requestedLocale, true);\n if (availableLocales.includes(withRegion)) {\n return withRegion;\n }\n \n // Try language match (ignore region)\n const language = parseLocale(requestedLocale).language;\n const languageMatch = availableLocales.find(locale => \n parseLocale(locale).language === language\n );\n \n if (languageMatch) {\n return languageMatch;\n }\n \n // Fallback to default\n return availableLocales.includes(defaultLocale) ? defaultLocale : availableLocales[0];\n}\n\n/**\n * Get supported locales from browser\n * \n * @returns {Array<string>} Array of supported locales\n */\nexport function getSupportedLocales() {\n if (typeof navigator !== 'undefined' && navigator.languages) {\n return navigator.languages.map(locale => normalizeLocale(locale));\n }\n \n return [detectLocale()];\n}\n\n/**\n * Locale Manager\n * Manages locale state and persistence\n */\nexport class LocaleManager {\n constructor(options = {}) {\n this.options = {\n defaultLocale: 'en',\n availableLocales: ['en'],\n storageKey: 'coherent-locale',\n autoDetect: true,\n ...options\n };\n \n this.currentLocale = this.options.defaultLocale;\n this.listeners = [];\n \n // Auto-detect or load from storage\n if (this.options.autoDetect) {\n this.currentLocale = this.detectAndMatch();\n }\n \n this.loadFromStorage();\n }\n\n /**\n * Detect and match best locale\n */\n detectAndMatch() {\n const detected = detectLocale();\n return matchLocale(\n detected,\n this.options.availableLocales,\n this.options.defaultLocale\n );\n }\n\n /**\n * Get current locale\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Set locale\n */\n setLocale(locale) {\n const matched = matchLocale(\n locale,\n this.options.availableLocales,\n this.options.defaultLocale\n );\n \n if (matched !== this.currentLocale) {\n const oldLocale = this.currentLocale;\n this.currentLocale = matched;\n \n this.saveToStorage();\n this.notifyListeners(oldLocale, matched);\n }\n }\n\n /**\n * Add locale change listener\n */\n onChange(listener) {\n this.listeners.push(listener);\n \n // Return unsubscribe function\n return () => {\n const index = this.listeners.indexOf(listener);\n if (index > -1) {\n this.listeners.splice(index, 1);\n }\n };\n }\n\n /**\n * Notify listeners of locale change\n */\n notifyListeners(oldLocale, newLocale) {\n this.listeners.forEach(listener => {\n try {\n listener(newLocale, oldLocale);\n } catch (error) {\n console.error('Error in locale change listener:', error);\n }\n });\n }\n\n /**\n * Save locale to storage\n */\n saveToStorage() {\n if (typeof localStorage !== 'undefined') {\n try {\n localStorage.setItem(this.options.storageKey, this.currentLocale);\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n /**\n * Load locale from storage\n */\n loadFromStorage() {\n if (typeof localStorage !== 'undefined') {\n try {\n const stored = localStorage.getItem(this.options.storageKey);\n if (stored) {\n this.setLocale(stored);\n }\n } catch {\n // Ignore storage errors\n }\n }\n }\n\n /**\n * Get available locales\n */\n getAvailableLocales() {\n return [...this.options.availableLocales];\n }\n\n /**\n * Check if locale is available\n */\n isAvailable(locale) {\n return this.options.availableLocales.includes(locale);\n }\n}\n\n/**\n * Create a locale manager\n */\nexport function createLocaleManager(options = {}) {\n return new LocaleManager(options);\n}\n\nexport default {\n detectLocale,\n normalizeLocale,\n parseLocale,\n getLocaleDirection,\n isRTL,\n getLocaleDisplayName,\n matchLocale,\n getSupportedLocales,\n LocaleManager,\n createLocaleManager\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// src/translator.js
|
|
2
|
+
var Translator = class {
|
|
3
|
+
constructor(options = {}) {
|
|
4
|
+
this.options = {
|
|
5
|
+
defaultLocale: "en",
|
|
6
|
+
fallbackLocale: "en",
|
|
7
|
+
missingKeyHandler: null,
|
|
8
|
+
interpolation: {
|
|
9
|
+
prefix: "{{",
|
|
10
|
+
suffix: "}}"
|
|
11
|
+
},
|
|
12
|
+
...options
|
|
13
|
+
};
|
|
14
|
+
this.translations = /* @__PURE__ */ new Map();
|
|
15
|
+
this.currentLocale = this.options.defaultLocale;
|
|
16
|
+
this.loadedLocales = /* @__PURE__ */ new Set();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Add translations for a locale
|
|
20
|
+
*
|
|
21
|
+
* @param {string} locale - Locale code (e.g., 'en', 'fr', 'es')
|
|
22
|
+
* @param {Object} translations - Translation object
|
|
23
|
+
*/
|
|
24
|
+
addTranslations(locale, translations) {
|
|
25
|
+
if (!this.translations.has(locale)) {
|
|
26
|
+
this.translations.set(locale, {});
|
|
27
|
+
}
|
|
28
|
+
const existing = this.translations.get(locale);
|
|
29
|
+
this.translations.set(locale, this.deepMerge(existing, translations));
|
|
30
|
+
this.loadedLocales.add(locale);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Deep merge objects
|
|
34
|
+
*/
|
|
35
|
+
deepMerge(target, source) {
|
|
36
|
+
const result = { ...target };
|
|
37
|
+
for (const [key, value] of Object.entries(source)) {
|
|
38
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
39
|
+
result[key] = this.deepMerge(result[key] || {}, value);
|
|
40
|
+
} else {
|
|
41
|
+
result[key] = value;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Set current locale
|
|
48
|
+
*
|
|
49
|
+
* @param {string} locale - Locale code
|
|
50
|
+
*/
|
|
51
|
+
setLocale(locale) {
|
|
52
|
+
if (!this.loadedLocales.has(locale)) {
|
|
53
|
+
console.warn(`Locale ${locale} not loaded, using fallback`);
|
|
54
|
+
this.currentLocale = this.options.fallbackLocale;
|
|
55
|
+
} else {
|
|
56
|
+
this.currentLocale = locale;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get current locale
|
|
61
|
+
*
|
|
62
|
+
* @returns {string} Current locale code
|
|
63
|
+
*/
|
|
64
|
+
getLocale() {
|
|
65
|
+
return this.currentLocale;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Translate a key
|
|
69
|
+
*
|
|
70
|
+
* @param {string} key - Translation key (supports dot notation)
|
|
71
|
+
* @param {Object} [params] - Interpolation parameters
|
|
72
|
+
* @param {string} [locale] - Override locale
|
|
73
|
+
* @returns {string} Translated string
|
|
74
|
+
*/
|
|
75
|
+
t(key, params = {}, locale = null) {
|
|
76
|
+
const targetLocale = locale || this.currentLocale;
|
|
77
|
+
let translation = this.getTranslation(key, targetLocale);
|
|
78
|
+
if (translation === null && targetLocale !== this.options.fallbackLocale) {
|
|
79
|
+
translation = this.getTranslation(key, this.options.fallbackLocale);
|
|
80
|
+
}
|
|
81
|
+
if (translation === null) {
|
|
82
|
+
if (this.options.missingKeyHandler) {
|
|
83
|
+
return this.options.missingKeyHandler(key, targetLocale);
|
|
84
|
+
}
|
|
85
|
+
return key;
|
|
86
|
+
}
|
|
87
|
+
if (typeof translation === "object" && params.count !== void 0) {
|
|
88
|
+
translation = this.selectPlural(translation, params.count, targetLocale);
|
|
89
|
+
}
|
|
90
|
+
if (typeof translation === "string") {
|
|
91
|
+
return this.interpolate(translation, params);
|
|
92
|
+
}
|
|
93
|
+
return String(translation);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Get translation from nested object
|
|
97
|
+
*/
|
|
98
|
+
getTranslation(key, locale) {
|
|
99
|
+
const translations = this.translations.get(locale);
|
|
100
|
+
if (!translations) return null;
|
|
101
|
+
const keys = key.split(".");
|
|
102
|
+
let value = translations;
|
|
103
|
+
for (const k of keys) {
|
|
104
|
+
if (value && typeof value === "object" && k in value) {
|
|
105
|
+
value = value[k];
|
|
106
|
+
} else {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Select plural form
|
|
114
|
+
*/
|
|
115
|
+
selectPlural(pluralObject, count, locale) {
|
|
116
|
+
if (count === 0 && pluralObject.zero) {
|
|
117
|
+
return pluralObject.zero;
|
|
118
|
+
}
|
|
119
|
+
if (typeof Intl !== "undefined" && Intl.PluralRules) {
|
|
120
|
+
const rules = new Intl.PluralRules(locale);
|
|
121
|
+
const rule = rules.select(count);
|
|
122
|
+
if (pluralObject[rule]) {
|
|
123
|
+
return pluralObject[rule];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (count === 1 && pluralObject.one) {
|
|
127
|
+
return pluralObject.one;
|
|
128
|
+
} else if (pluralObject.other) {
|
|
129
|
+
return pluralObject.other;
|
|
130
|
+
}
|
|
131
|
+
return pluralObject.one || pluralObject.other || "";
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Interpolate parameters into string
|
|
135
|
+
*/
|
|
136
|
+
interpolate(str, params) {
|
|
137
|
+
const { prefix, suffix } = this.options.interpolation;
|
|
138
|
+
let result = str;
|
|
139
|
+
for (const [key, value] of Object.entries(params)) {
|
|
140
|
+
const placeholder = `${prefix}${key}${suffix}`;
|
|
141
|
+
result = result.replace(new RegExp(placeholder, "g"), String(value));
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Check if translation exists
|
|
147
|
+
*
|
|
148
|
+
* @param {string} key - Translation key
|
|
149
|
+
* @param {string} [locale] - Locale to check
|
|
150
|
+
* @returns {boolean} True if translation exists
|
|
151
|
+
*/
|
|
152
|
+
has(key, locale = null) {
|
|
153
|
+
const targetLocale = locale || this.currentLocale;
|
|
154
|
+
return this.getTranslation(key, targetLocale) !== null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get all translations for current locale
|
|
158
|
+
*
|
|
159
|
+
* @returns {Object} All translations
|
|
160
|
+
*/
|
|
161
|
+
getTranslations(locale = null) {
|
|
162
|
+
const targetLocale = locale || this.currentLocale;
|
|
163
|
+
return this.translations.get(targetLocale) || {};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get all loaded locales
|
|
167
|
+
*
|
|
168
|
+
* @returns {Array<string>} Array of locale codes
|
|
169
|
+
*/
|
|
170
|
+
getLoadedLocales() {
|
|
171
|
+
return Array.from(this.loadedLocales);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Remove translations for a locale
|
|
175
|
+
*
|
|
176
|
+
* @param {string} locale - Locale code
|
|
177
|
+
*/
|
|
178
|
+
removeLocale(locale) {
|
|
179
|
+
this.translations.delete(locale);
|
|
180
|
+
this.loadedLocales.delete(locale);
|
|
181
|
+
if (this.currentLocale === locale) {
|
|
182
|
+
this.currentLocale = this.options.defaultLocale;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Clear all translations
|
|
187
|
+
*/
|
|
188
|
+
clear() {
|
|
189
|
+
this.translations.clear();
|
|
190
|
+
this.loadedLocales.clear();
|
|
191
|
+
this.currentLocale = this.options.defaultLocale;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
function createTranslator(options = {}) {
|
|
195
|
+
return new Translator(options);
|
|
196
|
+
}
|
|
197
|
+
function createScopedTranslator(translator, namespace) {
|
|
198
|
+
return {
|
|
199
|
+
t: (key, params, locale) => {
|
|
200
|
+
return translator.t(`${namespace}.${key}`, params, locale);
|
|
201
|
+
},
|
|
202
|
+
has: (key, locale) => {
|
|
203
|
+
return translator.has(`${namespace}.${key}`, locale);
|
|
204
|
+
},
|
|
205
|
+
getLocale: () => translator.getLocale(),
|
|
206
|
+
setLocale: (locale) => translator.setLocale(locale)
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
var translator_default = {
|
|
210
|
+
Translator,
|
|
211
|
+
createTranslator,
|
|
212
|
+
createScopedTranslator
|
|
213
|
+
};
|
|
214
|
+
export {
|
|
215
|
+
Translator,
|
|
216
|
+
createScopedTranslator,
|
|
217
|
+
createTranslator,
|
|
218
|
+
translator_default as default
|
|
219
|
+
};
|
|
220
|
+
//# sourceMappingURL=translator.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/translator.js"],
|
|
4
|
+
"sourcesContent": ["/**\n * Coherent.js Translator\n * \n * Handles translation of strings with interpolation and pluralization\n * \n * @module i18n/translator\n */\n\n/**\n * Translator\n * Manages translations and locale switching\n */\nexport class Translator {\n constructor(options = {}) {\n this.options = {\n defaultLocale: 'en',\n fallbackLocale: 'en',\n missingKeyHandler: null,\n interpolation: {\n prefix: '{{',\n suffix: '}}'\n },\n ...options\n };\n \n this.translations = new Map();\n this.currentLocale = this.options.defaultLocale;\n this.loadedLocales = new Set();\n }\n\n /**\n * Add translations for a locale\n * \n * @param {string} locale - Locale code (e.g., 'en', 'fr', 'es')\n * @param {Object} translations - Translation object\n */\n addTranslations(locale, translations) {\n if (!this.translations.has(locale)) {\n this.translations.set(locale, {});\n }\n \n const existing = this.translations.get(locale);\n this.translations.set(locale, this.deepMerge(existing, translations));\n this.loadedLocales.add(locale);\n }\n\n /**\n * Deep merge objects\n */\n deepMerge(target, source) {\n const result = { ...target };\n \n for (const [key, value] of Object.entries(source)) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n result[key] = this.deepMerge(result[key] || {}, value);\n } else {\n result[key] = value;\n }\n }\n \n return result;\n }\n\n /**\n * Set current locale\n * \n * @param {string} locale - Locale code\n */\n setLocale(locale) {\n if (!this.loadedLocales.has(locale)) {\n console.warn(`Locale ${locale} not loaded, using fallback`);\n this.currentLocale = this.options.fallbackLocale;\n } else {\n this.currentLocale = locale;\n }\n }\n\n /**\n * Get current locale\n * \n * @returns {string} Current locale code\n */\n getLocale() {\n return this.currentLocale;\n }\n\n /**\n * Translate a key\n * \n * @param {string} key - Translation key (supports dot notation)\n * @param {Object} [params] - Interpolation parameters\n * @param {string} [locale] - Override locale\n * @returns {string} Translated string\n */\n t(key, params = {}, locale = null) {\n const targetLocale = locale || this.currentLocale;\n \n // Get translation\n let translation = this.getTranslation(key, targetLocale);\n \n // Fallback to default locale\n if (translation === null && targetLocale !== this.options.fallbackLocale) {\n translation = this.getTranslation(key, this.options.fallbackLocale);\n }\n \n // Handle missing translation\n if (translation === null) {\n if (this.options.missingKeyHandler) {\n return this.options.missingKeyHandler(key, targetLocale);\n }\n return key;\n }\n \n // Handle pluralization\n if (typeof translation === 'object' && params.count !== undefined) {\n translation = this.selectPlural(translation, params.count, targetLocale);\n }\n \n // Interpolate parameters\n if (typeof translation === 'string') {\n return this.interpolate(translation, params);\n }\n \n return String(translation);\n }\n\n /**\n * Get translation from nested object\n */\n getTranslation(key, locale) {\n const translations = this.translations.get(locale);\n if (!translations) return null;\n \n const keys = key.split('.');\n let value = translations;\n \n for (const k of keys) {\n if (value && typeof value === 'object' && k in value) {\n value = value[k];\n } else {\n return null;\n }\n }\n \n return value;\n }\n\n /**\n * Select plural form\n */\n selectPlural(pluralObject, count, locale) {\n // Check for explicit zero first (takes precedence over Intl rules)\n if (count === 0 && pluralObject.zero) {\n return pluralObject.zero;\n }\n \n // Use Intl.PluralRules for locale-specific pluralization\n if (typeof Intl !== 'undefined' && Intl.PluralRules) {\n const rules = new Intl.PluralRules(locale);\n const rule = rules.select(count);\n \n if (pluralObject[rule]) {\n return pluralObject[rule];\n }\n }\n \n // Fallback to simple rules\n if (count === 1 && pluralObject.one) {\n return pluralObject.one;\n } else if (pluralObject.other) {\n return pluralObject.other;\n }\n \n return pluralObject.one || pluralObject.other || '';\n }\n\n /**\n * Interpolate parameters into string\n */\n interpolate(str, params) {\n const { prefix, suffix } = this.options.interpolation;\n let result = str;\n \n for (const [key, value] of Object.entries(params)) {\n const placeholder = `${prefix}${key}${suffix}`;\n result = result.replace(new RegExp(placeholder, 'g'), String(value));\n }\n \n return result;\n }\n\n /**\n * Check if translation exists\n * \n * @param {string} key - Translation key\n * @param {string} [locale] - Locale to check\n * @returns {boolean} True if translation exists\n */\n has(key, locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.getTranslation(key, targetLocale) !== null;\n }\n\n /**\n * Get all translations for current locale\n * \n * @returns {Object} All translations\n */\n getTranslations(locale = null) {\n const targetLocale = locale || this.currentLocale;\n return this.translations.get(targetLocale) || {};\n }\n\n /**\n * Get all loaded locales\n * \n * @returns {Array<string>} Array of locale codes\n */\n getLoadedLocales() {\n return Array.from(this.loadedLocales);\n }\n\n /**\n * Remove translations for a locale\n * \n * @param {string} locale - Locale code\n */\n removeLocale(locale) {\n this.translations.delete(locale);\n this.loadedLocales.delete(locale);\n \n if (this.currentLocale === locale) {\n this.currentLocale = this.options.defaultLocale;\n }\n }\n\n /**\n * Clear all translations\n */\n clear() {\n this.translations.clear();\n this.loadedLocales.clear();\n this.currentLocale = this.options.defaultLocale;\n }\n}\n\n/**\n * Create a translator instance\n * \n * @param {Object} [options] - Translator options\n * @returns {Translator} Translator instance\n */\nexport function createTranslator(options = {}) {\n return new Translator(options);\n}\n\n/**\n * Create a scoped translator\n * Automatically prefixes all keys with a namespace\n * \n * @param {Translator} translator - Base translator\n * @param {string} namespace - Namespace prefix\n * @returns {Object} Scoped translator\n */\nexport function createScopedTranslator(translator, namespace) {\n return {\n t: (key, params, locale) => {\n return translator.t(`${namespace}.${key}`, params, locale);\n },\n has: (key, locale) => {\n return translator.has(`${namespace}.${key}`, locale);\n },\n getLocale: () => translator.getLocale(),\n setLocale: (locale) => translator.setLocale(locale)\n };\n}\n\nexport default {\n Translator,\n createTranslator,\n createScopedTranslator\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coherent.js/i18n",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.8",
|
|
4
4
|
"description": "Internationalization support for Coherent.js applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -20,17 +20,25 @@
|
|
|
20
20
|
"author": "Coherent.js Team",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@coherent.js/core": "1.0.0-beta.
|
|
23
|
+
"@coherent.js/core": "1.0.0-beta.8"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
27
27
|
"url": "git+https://github.com/Tomdrouv1/coherent.js.git"
|
|
28
28
|
},
|
|
29
|
+
"homepage": "https://github.com/Tomdrouv1/coherent.js",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/Tomdrouv1/coherent.js/issues"
|
|
32
|
+
},
|
|
29
33
|
"publishConfig": {
|
|
30
34
|
"access": "public"
|
|
31
35
|
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20.0.0"
|
|
38
|
+
},
|
|
32
39
|
"types": "./types/index.d.ts",
|
|
33
40
|
"files": [
|
|
41
|
+
"dist/",
|
|
34
42
|
"LICENSE",
|
|
35
43
|
"README.md",
|
|
36
44
|
"types/"
|
|
@@ -38,6 +46,8 @@
|
|
|
38
46
|
"sideEffects": false,
|
|
39
47
|
"scripts": {
|
|
40
48
|
"build": "node build.mjs",
|
|
41
|
-
"clean": "rm -rf dist"
|
|
49
|
+
"clean": "rm -rf dist",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:watch": "vitest"
|
|
42
52
|
}
|
|
43
53
|
}
|