@lingo.dev/react 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/dist/index.cjs +0 -343
- package/dist/index.d.cts +0 -395
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.ts +0 -395
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -313
- package/dist/index.js.map +0 -1
package/dist/index.js
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
import { buildIcuPlural, buildIcuSelect, computeKey } from "@lingo.dev/spec";
|
|
2
|
-
import { Fragment, createContext, createElement, useContext, useMemo } from "react";
|
|
3
|
-
import IntlMessageFormat from "intl-messageformat";
|
|
4
|
-
import { jsx } from "react/jsx-runtime";
|
|
5
|
-
//#region src/format.ts
|
|
6
|
-
/**
|
|
7
|
-
* Locale-aware formatting methods wrapping native Intl APIs.
|
|
8
|
-
* Near-zero bundle cost - each method delegates to built-in browser/Node Intl formatters.
|
|
9
|
-
* All methods are pure functions that take a locale string and return formatted strings.
|
|
10
|
-
*/
|
|
11
|
-
const FILE_SIZE_UNITS = [
|
|
12
|
-
"B",
|
|
13
|
-
"KB",
|
|
14
|
-
"MB",
|
|
15
|
-
"GB",
|
|
16
|
-
"TB",
|
|
17
|
-
"PB"
|
|
18
|
-
];
|
|
19
|
-
/**
|
|
20
|
-
* Creates all formatting methods bound to a specific locale.
|
|
21
|
-
*/
|
|
22
|
-
function createFormatMethods(locale) {
|
|
23
|
-
return {
|
|
24
|
-
num: (value, options) => new Intl.NumberFormat(locale, options).format(value),
|
|
25
|
-
currency: (value, currency, options) => new Intl.NumberFormat(locale, {
|
|
26
|
-
...options,
|
|
27
|
-
style: "currency",
|
|
28
|
-
currency
|
|
29
|
-
}).format(value),
|
|
30
|
-
percent: (value, options) => new Intl.NumberFormat(locale, {
|
|
31
|
-
...options,
|
|
32
|
-
style: "percent"
|
|
33
|
-
}).format(value),
|
|
34
|
-
unit: (value, unit, options) => new Intl.NumberFormat(locale, {
|
|
35
|
-
...options,
|
|
36
|
-
style: "unit",
|
|
37
|
-
unit
|
|
38
|
-
}).format(value),
|
|
39
|
-
compact: (value, options) => new Intl.NumberFormat(locale, {
|
|
40
|
-
...options,
|
|
41
|
-
notation: "compact"
|
|
42
|
-
}).format(value),
|
|
43
|
-
date: (value, options) => new Intl.DateTimeFormat(locale, {
|
|
44
|
-
dateStyle: "medium",
|
|
45
|
-
...options
|
|
46
|
-
}).format(value),
|
|
47
|
-
time: (value, options) => new Intl.DateTimeFormat(locale, {
|
|
48
|
-
timeStyle: "short",
|
|
49
|
-
...options
|
|
50
|
-
}).format(value),
|
|
51
|
-
datetime: (value, options) => new Intl.DateTimeFormat(locale, {
|
|
52
|
-
dateStyle: "medium",
|
|
53
|
-
timeStyle: "short",
|
|
54
|
-
...options
|
|
55
|
-
}).format(value),
|
|
56
|
-
relative: (value, unit, options) => new Intl.RelativeTimeFormat(locale, options).format(value, unit),
|
|
57
|
-
list: (items, options) => new Intl.ListFormat(locale, {
|
|
58
|
-
type: "conjunction",
|
|
59
|
-
...options
|
|
60
|
-
}).format(items),
|
|
61
|
-
displayName: (code, type) => {
|
|
62
|
-
try {
|
|
63
|
-
return new Intl.DisplayNames(locale, { type }).of(code);
|
|
64
|
-
} catch {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
sort: (items, options) => {
|
|
69
|
-
const collator = new Intl.Collator(locale, options);
|
|
70
|
-
return [...items].sort(collator.compare);
|
|
71
|
-
},
|
|
72
|
-
segment: (text, granularity = "grapheme") => [...new Intl.Segmenter(locale, { granularity }).segment(text)].map((s) => s.segment),
|
|
73
|
-
fileSize: (bytes) => {
|
|
74
|
-
if (bytes === 0) return `0 ${FILE_SIZE_UNITS[0]}`;
|
|
75
|
-
const exp = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), FILE_SIZE_UNITS.length - 1);
|
|
76
|
-
const value = bytes / 1024 ** exp;
|
|
77
|
-
return `${new Intl.NumberFormat(locale, { maximumFractionDigits: exp === 0 ? 0 : 1 }).format(value)} ${FILE_SIZE_UNITS[exp]}`;
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
//#endregion
|
|
82
|
-
//#region src/text.ts
|
|
83
|
-
const ICU_COMPLEX = /\{[^}]+,\s*(?:plural|select|selectordinal|number|date|time)\s*[,}]/;
|
|
84
|
-
const formatCache = /* @__PURE__ */ new Map();
|
|
85
|
-
const warnedKeys = /* @__PURE__ */ new Set();
|
|
86
|
-
function warnOnce(id, message, level = "warn") {
|
|
87
|
-
if (process.env.NODE_ENV === "production") return;
|
|
88
|
-
if (warnedKeys.has(id)) return;
|
|
89
|
-
warnedKeys.add(id);
|
|
90
|
-
console[level](`[lingo] ${message}`);
|
|
91
|
-
}
|
|
92
|
-
function simpleInterpolate(template, values) {
|
|
93
|
-
return template.replace(/\{(\w+)\}/g, (_, key) => {
|
|
94
|
-
const val = values[key];
|
|
95
|
-
return val !== void 0 ? String(val) : `{${key}}`;
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
function getFormatter(template, locale) {
|
|
99
|
-
const cacheKey = `${locale}\0${template}`;
|
|
100
|
-
let formatter = formatCache.get(cacheKey);
|
|
101
|
-
if (!formatter) {
|
|
102
|
-
formatter = new IntlMessageFormat(template, locale);
|
|
103
|
-
formatCache.set(cacheKey, formatter);
|
|
104
|
-
}
|
|
105
|
-
return formatter;
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Looks up a translation by hash key with dev-mode diagnostics.
|
|
109
|
-
* Falls back to source text when translation is missing or empty.
|
|
110
|
-
*/
|
|
111
|
-
function lookupTemplate(source, messages, locale, context) {
|
|
112
|
-
const key = computeKey(source, context);
|
|
113
|
-
const raw = messages[key];
|
|
114
|
-
if (process.env.NODE_ENV !== "production") {
|
|
115
|
-
if (raw === void 0) warnOnce(`missing:${key}:${locale}`, `Missing translation for "${source}"\n Key: ${key}\n Locale: ${locale}\n → Run \`lingo extract\` to add this string to your locale files`);
|
|
116
|
-
else if (raw === "") warnOnce(`empty:${key}:${locale}`, `Empty translation for "${source}"\n Key: ${key}\n Locale: ${locale}\n → This key exists in your locale file but has no translation`);
|
|
117
|
-
}
|
|
118
|
-
return raw || source;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Resolves a translated string from messages by hash, falling back to source text.
|
|
122
|
-
* Uses simple regex for {placeholder} substitution; loads ICU parser only for complex syntax.
|
|
123
|
-
*/
|
|
124
|
-
function resolveText(source, messages, locale, options) {
|
|
125
|
-
const template = lookupTemplate(source, messages, locale, options?.context);
|
|
126
|
-
if (!options?.values) return template;
|
|
127
|
-
if (!ICU_COMPLEX.test(template)) return simpleInterpolate(template, options.values);
|
|
128
|
-
try {
|
|
129
|
-
return getFormatter(template, locale).format(options.values);
|
|
130
|
-
} catch (error) {
|
|
131
|
-
if (process.env.NODE_ENV !== "production") {
|
|
132
|
-
const key = computeKey(source, options?.context);
|
|
133
|
-
warnOnce(`icu:${key}:${locale}`, `Failed to format "${template}"\n Key: ${key}\n Locale: ${locale}\n Error: ${error instanceof Error ? error.message : String(error)}\n → Check the ICU MessageFormat syntax in your locale file`, "error");
|
|
134
|
-
}
|
|
135
|
-
return template;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
//#endregion
|
|
139
|
-
//#region src/jsx.ts
|
|
140
|
-
const TAG_RE = /<(\w+)>([\s\S]*?)<\/\1>|<(\w+)\s*\/>/g;
|
|
141
|
-
function renderTemplate(template, tags, values) {
|
|
142
|
-
const parts = [];
|
|
143
|
-
let lastIndex = 0;
|
|
144
|
-
let keyCounter = 0;
|
|
145
|
-
for (const match of template.matchAll(TAG_RE)) {
|
|
146
|
-
if (match.index > lastIndex) parts.push(simpleInterpolate(template.slice(lastIndex, match.index), values));
|
|
147
|
-
const tagName = match[1] ?? match[3];
|
|
148
|
-
const content = match[2];
|
|
149
|
-
const tagFn = tags[tagName];
|
|
150
|
-
if (tagFn) {
|
|
151
|
-
const children = content != null ? renderTemplate(content, tags, values) : null;
|
|
152
|
-
parts.push(createElement(Fragment, { key: keyCounter++ }, tagFn(children)));
|
|
153
|
-
} else parts.push(match[0]);
|
|
154
|
-
lastIndex = match.index + match[0].length;
|
|
155
|
-
}
|
|
156
|
-
if (lastIndex < template.length) parts.push(simpleInterpolate(template.slice(lastIndex), values));
|
|
157
|
-
if (parts.length === 0) return "";
|
|
158
|
-
if (parts.length === 1) return parts[0];
|
|
159
|
-
return createElement(Fragment, null, ...parts);
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Resolves a translated string with JSX tag interpolation.
|
|
163
|
-
* Parses <tag>children</tag> and <tag/> patterns, mapping them to React components.
|
|
164
|
-
*/
|
|
165
|
-
function resolveJsx(source, messages, locale, options) {
|
|
166
|
-
return renderTemplate(lookupTemplate(source, messages, locale, options?.context), options?.tags ?? {}, options?.values ?? {});
|
|
167
|
-
}
|
|
168
|
-
//#endregion
|
|
169
|
-
//#region src/lingo.ts
|
|
170
|
-
const RTL_LANGS = new Set([
|
|
171
|
-
"ar",
|
|
172
|
-
"arc",
|
|
173
|
-
"dv",
|
|
174
|
-
"fa",
|
|
175
|
-
"ha",
|
|
176
|
-
"he",
|
|
177
|
-
"khw",
|
|
178
|
-
"ks",
|
|
179
|
-
"ku",
|
|
180
|
-
"ps",
|
|
181
|
-
"ur",
|
|
182
|
-
"yi"
|
|
183
|
-
]);
|
|
184
|
-
function fallbackDirection(locale) {
|
|
185
|
-
const lang = locale.split("-")[0].toLowerCase();
|
|
186
|
-
return RTL_LANGS.has(lang) ? "rtl" : "ltr";
|
|
187
|
-
}
|
|
188
|
-
function resolveDirection(locale) {
|
|
189
|
-
try {
|
|
190
|
-
const direction = new Intl.Locale(locale).textInfo?.direction;
|
|
191
|
-
if (direction === "rtl" || direction === "ltr") return direction;
|
|
192
|
-
} catch {}
|
|
193
|
-
return fallbackDirection(locale);
|
|
194
|
-
}
|
|
195
|
-
function resolveLocaleInfo(locale) {
|
|
196
|
-
try {
|
|
197
|
-
const loc = new Intl.Locale(locale).maximize();
|
|
198
|
-
return {
|
|
199
|
-
script: loc.script,
|
|
200
|
-
region: loc.region
|
|
201
|
-
};
|
|
202
|
-
} catch {
|
|
203
|
-
return {
|
|
204
|
-
script: void 0,
|
|
205
|
-
region: void 0
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Creates a `Lingo` object for translating text outside of React context.
|
|
211
|
-
* Used by `@lingo.dev/react-next` for Server Components and internally by `LingoProvider`.
|
|
212
|
-
*
|
|
213
|
-
* @param locale - BCP-47 locale string (e.g., "en", "es", "ar-SA")
|
|
214
|
-
* @param messages - Hash-keyed translations from JSONC locale files
|
|
215
|
-
*
|
|
216
|
-
* @example
|
|
217
|
-
* ```ts
|
|
218
|
-
* const l = createLingo("es", messages);
|
|
219
|
-
* l.text("Hello", { context: "Hero greeting" });
|
|
220
|
-
* l.direction; // "ltr"
|
|
221
|
-
* l.script; // "Latn"
|
|
222
|
-
* ```
|
|
223
|
-
*/
|
|
224
|
-
function createLingo(locale, messages = {}) {
|
|
225
|
-
const direction = resolveDirection(locale);
|
|
226
|
-
const { script, region } = resolveLocaleInfo(locale);
|
|
227
|
-
return {
|
|
228
|
-
locale,
|
|
229
|
-
direction,
|
|
230
|
-
script,
|
|
231
|
-
region,
|
|
232
|
-
text: (source, options) => resolveText(source, messages, locale, options),
|
|
233
|
-
jsx: (source, options) => resolveJsx(source, messages, locale, options),
|
|
234
|
-
plural: (count, forms, options) => {
|
|
235
|
-
return resolveText(buildIcuPlural(forms), messages, locale, {
|
|
236
|
-
context: options?.context ?? "",
|
|
237
|
-
values: { count }
|
|
238
|
-
});
|
|
239
|
-
},
|
|
240
|
-
select: (value, forms, options) => {
|
|
241
|
-
return resolveText(buildIcuSelect(forms), messages, locale, {
|
|
242
|
-
context: options?.context ?? "",
|
|
243
|
-
values: { value }
|
|
244
|
-
});
|
|
245
|
-
},
|
|
246
|
-
...createFormatMethods(locale)
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
//#endregion
|
|
250
|
-
//#region src/provider.tsx
|
|
251
|
-
const LingoContext = createContext(null);
|
|
252
|
-
/**
|
|
253
|
-
* Provides locale and translations to all descendant components via `useLingo()`.
|
|
254
|
-
*
|
|
255
|
-
* Nested providers with the **same locale** merge messages (child overrides parent,
|
|
256
|
-
* missing keys fall through). Nested providers with **different locales** are standalone.
|
|
257
|
-
*
|
|
258
|
-
* @example
|
|
259
|
-
* ```tsx
|
|
260
|
-
* // Root layout: shared messages
|
|
261
|
-
* <LingoProvider locale="es" messages={sharedMessages}>
|
|
262
|
-
* {/* Dashboard: adds route-specific messages */}
|
|
263
|
-
* <LingoProvider locale="es" messages={dashboardMessages}>
|
|
264
|
-
* <App />
|
|
265
|
-
* </LingoProvider>
|
|
266
|
-
* </LingoProvider>
|
|
267
|
-
* ```
|
|
268
|
-
*/
|
|
269
|
-
function LingoProvider({ locale, messages = {}, children }) {
|
|
270
|
-
const parent = useContext(LingoContext);
|
|
271
|
-
const parentMessages = parent?.messages;
|
|
272
|
-
const parentLocale = parent?.lingo.locale;
|
|
273
|
-
return /* @__PURE__ */ jsx(LingoContext, {
|
|
274
|
-
value: useMemo(() => {
|
|
275
|
-
const merged = parentMessages && parentLocale === locale ? {
|
|
276
|
-
...parentMessages,
|
|
277
|
-
...messages
|
|
278
|
-
} : messages;
|
|
279
|
-
return {
|
|
280
|
-
lingo: createLingo(locale, merged),
|
|
281
|
-
messages: merged
|
|
282
|
-
};
|
|
283
|
-
}, [
|
|
284
|
-
parentMessages,
|
|
285
|
-
parentLocale,
|
|
286
|
-
locale,
|
|
287
|
-
messages
|
|
288
|
-
]),
|
|
289
|
-
children
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Access the `Lingo` translation object from the nearest `LingoProvider`.
|
|
294
|
-
*
|
|
295
|
-
* @throws If called outside a `<LingoProvider>`.
|
|
296
|
-
*
|
|
297
|
-
* @example
|
|
298
|
-
* ```tsx
|
|
299
|
-
* function Greeting() {
|
|
300
|
-
* const l = useLingo();
|
|
301
|
-
* return <h1>{l.text("Hello")}</h1>;
|
|
302
|
-
* }
|
|
303
|
-
* ```
|
|
304
|
-
*/
|
|
305
|
-
function useLingo() {
|
|
306
|
-
const ctx = useContext(LingoContext);
|
|
307
|
-
if (ctx === null) throw new Error("useLingo() called outside <LingoProvider>. Wrap your component tree with <LingoProvider locale=\"en\"> to fix this.");
|
|
308
|
-
return ctx.lingo;
|
|
309
|
-
}
|
|
310
|
-
//#endregion
|
|
311
|
-
export { LingoProvider, computeKey, createLingo, useLingo };
|
|
312
|
-
|
|
313
|
-
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/format.ts","../src/text.ts","../src/jsx.ts","../src/lingo.ts","../src/provider.tsx"],"sourcesContent":["/**\n * Locale-aware formatting methods wrapping native Intl APIs.\n * Near-zero bundle cost - each method delegates to built-in browser/Node Intl formatters.\n * All methods are pure functions that take a locale string and return formatted strings.\n */\n\nconst FILE_SIZE_UNITS = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\", \"PB\"] as const;\n\nexport type FormatMethods = {\n /**\n * Format a number with locale-aware grouping and decimals.\n * @example l.num(1234567) // \"1,234,567\" (en) / \"1.234.567\" (de)\n */\n readonly num: (value: number, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a currency value with locale-aware symbol placement and decimals.\n * @example l.currency(29.99, \"USD\") // \"$29.99\" (en) / \"29,99 $US\" (fr)\n */\n readonly currency: (value: number, currency: string, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a decimal as a percentage.\n * @example l.percent(0.156) // \"16%\" (en) / \"16 %\" (fr)\n */\n readonly percent: (value: number, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a value with a measurement unit.\n * @example l.unit(32, \"celsius\") // \"32°C\" (en) / \"32 °C\" (de)\n */\n readonly unit: (value: number, unit: string, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a number in compact notation.\n * @example l.compact(1234567) // \"1.2M\" (en) / \"123万\" (ja)\n */\n readonly compact: (value: number, options?: Intl.NumberFormatOptions) => string;\n\n /**\n * Format a date.\n * @example l.date(new Date()) // \"3/16/2026\" (en) / \"16.03.2026\" (de)\n */\n readonly date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n\n /**\n * Format a time.\n * @example l.time(new Date()) // \"3:45 PM\" (en) / \"15:45\" (de)\n */\n readonly time: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n\n /**\n * Format a date and time together.\n * @example l.datetime(new Date()) // \"3/16/2026, 3:45 PM\" (en)\n */\n readonly datetime: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n\n /**\n * Format a relative time span.\n * @example l.relative(-3, \"day\") // \"3 days ago\" (en) / \"vor 3 Tagen\" (de)\n */\n readonly relative: (\n value: number,\n unit: Intl.RelativeTimeFormatUnit,\n options?: Intl.RelativeTimeFormatOptions,\n ) => string;\n\n /**\n * Format a list of items with locale-aware conjunction.\n * @example l.list([\"A\", \"B\", \"C\"]) // \"A, B, and C\" (en) / \"A, B y C\" (es)\n */\n readonly list: (items: string[], options?: Intl.ListFormatOptions) => string;\n\n /**\n * Get the localized display name for a language, region, script, or currency code.\n * @example l.displayName(\"en\", \"language\") // \"English\" (en) / \"Englisch\" (de)\n */\n readonly displayName: (code: string, type: \"language\" | \"region\" | \"script\" | \"currency\") => string | undefined;\n\n /**\n * Sort an array of strings using locale-aware collation rules.\n * Returns a new sorted array. Does not mutate the input.\n * @example l.sort([\"ä\", \"z\", \"a\"]) // [\"a\", \"ä\", \"z\"] (de) vs [\"a\", \"z\", \"ä\"] (sv)\n */\n readonly sort: (items: string[], options?: Intl.CollatorOptions) => string[];\n\n /**\n * Segment text into graphemes, words, or sentences using locale-aware rules.\n * Essential for CJK text where spaces don't separate words.\n * @example l.segment(\"Hello world\", \"word\") // [\"Hello\", \" \", \"world\"]\n */\n readonly segment: (text: string, granularity?: \"grapheme\" | \"word\" | \"sentence\") => string[];\n\n /**\n * Format a byte count as a human-readable file size with locale-aware number formatting.\n * @example l.fileSize(1073741824) // \"1 GB\" (en) / \"1 Go\" (fr)\n */\n readonly fileSize: (bytes: number) => string;\n};\n\n/**\n * Creates all formatting methods bound to a specific locale.\n */\nexport function createFormatMethods(locale: string): FormatMethods {\n return {\n num: (value, options?) => new Intl.NumberFormat(locale, options).format(value),\n\n currency: (value, currency, options?) =>\n new Intl.NumberFormat(locale, { ...options, style: \"currency\", currency }).format(value),\n\n percent: (value, options?) => new Intl.NumberFormat(locale, { ...options, style: \"percent\" }).format(value),\n\n unit: (value, unit, options?) => new Intl.NumberFormat(locale, { ...options, style: \"unit\", unit }).format(value),\n\n compact: (value, options?) => new Intl.NumberFormat(locale, { ...options, notation: \"compact\" }).format(value),\n\n date: (value, options?) => new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", ...options }).format(value),\n\n time: (value, options?) => new Intl.DateTimeFormat(locale, { timeStyle: \"short\", ...options }).format(value),\n\n datetime: (value, options?) =>\n new Intl.DateTimeFormat(locale, { dateStyle: \"medium\", timeStyle: \"short\", ...options }).format(value),\n\n relative: (value, unit, options?) => new Intl.RelativeTimeFormat(locale, options).format(value, unit),\n\n list: (items, options?) => new Intl.ListFormat(locale, { type: \"conjunction\", ...options }).format(items),\n\n displayName: (code, type) => {\n try {\n return new Intl.DisplayNames(locale, { type }).of(code);\n } catch {\n return undefined;\n }\n },\n\n sort: (items, options?) => {\n const collator = new Intl.Collator(locale, options);\n return [...items].sort(collator.compare);\n },\n\n segment: (text, granularity = \"grapheme\") =>\n [...new Intl.Segmenter(locale, { granularity }).segment(text)].map((s) => s.segment),\n\n fileSize: (bytes) => {\n if (bytes === 0) return `0 ${FILE_SIZE_UNITS[0]}`;\n const exp = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), FILE_SIZE_UNITS.length - 1);\n const value = bytes / 1024 ** exp;\n const formatted = new Intl.NumberFormat(locale, { maximumFractionDigits: exp === 0 ? 0 : 1 }).format(value);\n return `${formatted} ${FILE_SIZE_UNITS[exp]}`;\n },\n };\n}\n","import IntlMessageFormat from \"intl-messageformat\";\nimport { computeKey } from \"./hash\";\nimport type { Messages } from \"./types\";\n\n/**\n * Options for `l.text()` translation calls.\n *\n * @example\n * ```ts\n * l.text(\"Welcome, {name}!\", { values: { name: \"Max\" }, context: \"Dashboard\" })\n * ```\n */\nexport type TextOptions = {\n /** Interpolation values for `{placeholder}` substitution. */\n values?: Record<string, string | number>;\n /** Required context describing where/how this text is used. Improves translation quality and enables disambiguation. */\n context: string;\n};\n\nconst ICU_COMPLEX = /\\{[^}]+,\\s*(?:plural|select|selectordinal|number|date|time)\\s*[,}]/;\n\nconst formatCache = new Map<string, IntlMessageFormat>();\nconst warnedKeys = new Set<string>();\n\nfunction warnOnce(id: string, message: string, level: \"warn\" | \"error\" = \"warn\"): void {\n if (process.env.NODE_ENV === \"production\") return;\n if (warnedKeys.has(id)) return;\n warnedKeys.add(id);\n console[level](`[lingo] ${message}`);\n}\n\nexport function simpleInterpolate(template: string, values: Record<string, string | number>): string {\n return template.replace(/\\{(\\w+)\\}/g, (_, key) => {\n const val = values[key];\n return val !== undefined ? String(val) : `{${key}}`;\n });\n}\n\nfunction getFormatter(template: string, locale: string): IntlMessageFormat {\n const cacheKey = `${locale}\\0${template}`;\n let formatter = formatCache.get(cacheKey);\n if (!formatter) {\n formatter = new IntlMessageFormat(template, locale);\n formatCache.set(cacheKey, formatter);\n }\n return formatter;\n}\n\n/**\n * Looks up a translation by hash key with dev-mode diagnostics.\n * Falls back to source text when translation is missing or empty.\n */\nexport function lookupTemplate(source: string, messages: Messages, locale: string, context?: string): string {\n const key = computeKey(source, context);\n const raw = messages[key];\n\n if (process.env.NODE_ENV !== \"production\") {\n if (raw === undefined) {\n warnOnce(\n `missing:${key}:${locale}`,\n `Missing translation for \"${source}\"\\n Key: ${key}\\n Locale: ${locale}\\n → Run \\`lingo extract\\` to add this string to your locale files`,\n );\n } else if (raw === \"\") {\n warnOnce(\n `empty:${key}:${locale}`,\n `Empty translation for \"${source}\"\\n Key: ${key}\\n Locale: ${locale}\\n → This key exists in your locale file but has no translation`,\n );\n }\n }\n\n return raw || source;\n}\n\n/**\n * Resolves a translated string from messages by hash, falling back to source text.\n * Uses simple regex for {placeholder} substitution; loads ICU parser only for complex syntax.\n */\nexport function resolveText(source: string, messages: Messages, locale: string, options?: TextOptions): string {\n const template = lookupTemplate(source, messages, locale, options?.context);\n\n if (!options?.values) return template;\n\n // Fast path: simple {placeholder} interpolation\n if (!ICU_COMPLEX.test(template)) {\n return simpleInterpolate(template, options.values);\n }\n\n // Full ICU path (plural, select, number, date, time)\n try {\n const formatter = getFormatter(template, locale);\n return formatter.format(options.values) as string;\n } catch (error) {\n if (process.env.NODE_ENV !== \"production\") {\n const key = computeKey(source, options?.context);\n warnOnce(\n `icu:${key}:${locale}`,\n `Failed to format \"${template}\"\\n Key: ${key}\\n Locale: ${locale}\\n Error: ${error instanceof Error ? error.message : String(error)}\\n → Check the ICU MessageFormat syntax in your locale file`,\n \"error\",\n );\n }\n return template;\n }\n}\n","import { createElement, Fragment, type ReactNode } from \"react\";\nimport { lookupTemplate, simpleInterpolate } from \"./text\";\nimport type { Messages } from \"./types\";\n\n/**\n * Options for `l.jsx()` rich text translation calls.\n *\n * @example\n * ```tsx\n * l.jsx(\"Click <link>here</link> for {topic}\", {\n * tags: { link: (children) => <a href=\"/help\">{children}</a> },\n * values: { topic: \"details\" },\n * context: \"Footer\",\n * })\n * ```\n */\nexport type JsxOptions = {\n /** Map tag names to React component renderers. `<tag>children</tag>` and `<tag/>` supported. */\n tags?: Record<string, (children: ReactNode) => ReactNode>;\n /** Interpolation values for `{placeholder}` substitution in text segments. */\n values?: Record<string, string | number>;\n /** Required context describing where/how this text is used. Improves translation quality and enables disambiguation. */\n context: string;\n};\n\nconst TAG_RE = /<(\\w+)>([\\s\\S]*?)<\\/\\1>|<(\\w+)\\s*\\/>/g;\n\nfunction renderTemplate(\n template: string,\n tags: Record<string, (children: ReactNode) => ReactNode>,\n values: Record<string, string | number>,\n): ReactNode {\n const parts: ReactNode[] = [];\n let lastIndex = 0;\n let keyCounter = 0;\n\n for (const match of template.matchAll(TAG_RE)) {\n if (match.index > lastIndex) {\n parts.push(simpleInterpolate(template.slice(lastIndex, match.index), values));\n }\n\n const tagName = match[1] ?? match[3];\n const content = match[2];\n const tagFn = tags[tagName];\n\n if (tagFn) {\n const children = content != null ? renderTemplate(content, tags, values) : null;\n parts.push(createElement(Fragment, { key: keyCounter++ }, tagFn(children)));\n } else {\n // No handler for this tag - render raw text\n parts.push(match[0]);\n }\n\n lastIndex = match.index + match[0].length;\n }\n\n if (lastIndex < template.length) {\n parts.push(simpleInterpolate(template.slice(lastIndex), values));\n }\n\n if (parts.length === 0) return \"\";\n if (parts.length === 1) return parts[0];\n return createElement(Fragment, null, ...parts);\n}\n\n/**\n * Resolves a translated string with JSX tag interpolation.\n * Parses <tag>children</tag> and <tag/> patterns, mapping them to React components.\n */\nexport function resolveJsx(source: string, messages: Messages, locale: string, options?: JsxOptions): ReactNode {\n const template = lookupTemplate(source, messages, locale, options?.context);\n return renderTemplate(template, options?.tags ?? {}, options?.values ?? {});\n}\n","import { buildIcuPlural, buildIcuSelect } from \"@lingo.dev/spec\";\nimport { createFormatMethods } from \"./format\";\nimport { resolveJsx } from \"./jsx\";\nimport { resolveText, type TextOptions } from \"./text\";\nimport type { Lingo, Messages, PluralForms, SelectForms } from \"./types\";\n\nconst RTL_LANGS = new Set([\"ar\", \"arc\", \"dv\", \"fa\", \"ha\", \"he\", \"khw\", \"ks\", \"ku\", \"ps\", \"ur\", \"yi\"]);\n\nfunction fallbackDirection(locale: string): \"ltr\" | \"rtl\" {\n const lang = locale.split(\"-\")[0].toLowerCase();\n return RTL_LANGS.has(lang) ? \"rtl\" : \"ltr\";\n}\n\nexport function resolveDirection(locale: string): \"ltr\" | \"rtl\" {\n try {\n const loc = new Intl.Locale(locale);\n // Intl.Locale.textInfo available in Node 21+, Chrome 99+, Safari 17.4+\n const direction = (loc as any).textInfo?.direction;\n if (direction === \"rtl\" || direction === \"ltr\") return direction;\n } catch {\n // Invalid locale or Intl.Locale not available\n }\n return fallbackDirection(locale);\n}\n\nfunction resolveLocaleInfo(locale: string): { script: string | undefined; region: string | undefined } {\n try {\n const loc = new Intl.Locale(locale).maximize();\n return { script: loc.script, region: loc.region };\n } catch {\n return { script: undefined, region: undefined };\n }\n}\n\n/**\n * Creates a `Lingo` object for translating text outside of React context.\n * Used by `@lingo.dev/react-next` for Server Components and internally by `LingoProvider`.\n *\n * @param locale - BCP-47 locale string (e.g., \"en\", \"es\", \"ar-SA\")\n * @param messages - Hash-keyed translations from JSONC locale files\n *\n * @example\n * ```ts\n * const l = createLingo(\"es\", messages);\n * l.text(\"Hello\", { context: \"Hero greeting\" });\n * l.direction; // \"ltr\"\n * l.script; // \"Latn\"\n * ```\n */\nexport function createLingo(locale: string, messages: Messages = {}): Lingo {\n const direction = resolveDirection(locale);\n const { script, region } = resolveLocaleInfo(locale);\n\n // Cast needed: implementation uses (source, options?) but Lingo type uses\n // conditional rest params (...args) to enforce required context + values per message key.\n // Runtime behavior is identical - rest params with one element === single param.\n return {\n locale,\n direction,\n script,\n region,\n text: (source: string, options?: TextOptions) => resolveText(source, messages, locale, options),\n jsx: (source: string, options?: any) => resolveJsx(source, messages, locale, options),\n plural: (count: number, forms: PluralForms, options?: { context: string }) => {\n const source = buildIcuPlural(forms);\n return resolveText(source, messages, locale, {\n context: options?.context ?? \"\",\n values: { count },\n });\n },\n select: (value: string, forms: SelectForms, options?: { context: string }) => {\n const source = buildIcuSelect(forms);\n return resolveText(source, messages, locale, {\n context: options?.context ?? \"\",\n values: { value },\n });\n },\n ...createFormatMethods(locale),\n } as Lingo;\n}\n","import { createContext, useContext, useMemo, type ReactNode } from \"react\";\nimport { createLingo } from \"./lingo\";\nimport type { Lingo, Messages } from \"./types\";\n\ntype LingoState = {\n lingo: Lingo;\n messages: Messages;\n};\n\nconst LingoContext = createContext<LingoState | null>(null);\n\nexport type LingoProviderProps = {\n /** BCP-47 locale string (e.g., \"en\", \"es\", \"ar-SA\") */\n locale: string;\n /** Hash-keyed translations loaded from JSONC/JSON locale files */\n messages?: Messages;\n children: ReactNode;\n};\n\n/**\n * Provides locale and translations to all descendant components via `useLingo()`.\n *\n * Nested providers with the **same locale** merge messages (child overrides parent,\n * missing keys fall through). Nested providers with **different locales** are standalone.\n *\n * @example\n * ```tsx\n * // Root layout: shared messages\n * <LingoProvider locale=\"es\" messages={sharedMessages}>\n * {/* Dashboard: adds route-specific messages */}\n * <LingoProvider locale=\"es\" messages={dashboardMessages}>\n * <App />\n * </LingoProvider>\n * </LingoProvider>\n * ```\n */\nexport function LingoProvider({ locale, messages = {}, children }: LingoProviderProps) {\n const parent = useContext(LingoContext);\n const parentMessages = parent?.messages;\n const parentLocale = parent?.lingo.locale;\n\n const state = useMemo<LingoState>(() => {\n // Merge with parent when same locale; standalone when locale differs or no parent\n const merged = parentMessages && parentLocale === locale ? { ...parentMessages, ...messages } : messages;\n return { lingo: createLingo(locale, merged), messages: merged };\n }, [parentMessages, parentLocale, locale, messages]);\n\n return <LingoContext value={state}>{children}</LingoContext>;\n}\n\n/**\n * Access the `Lingo` translation object from the nearest `LingoProvider`.\n *\n * @throws If called outside a `<LingoProvider>`.\n *\n * @example\n * ```tsx\n * function Greeting() {\n * const l = useLingo();\n * return <h1>{l.text(\"Hello\")}</h1>;\n * }\n * ```\n */\nexport function useLingo(): Lingo {\n const ctx = useContext(LingoContext);\n if (ctx === null) {\n throw new Error(\n \"useLingo() called outside <LingoProvider>. \" +\n 'Wrap your component tree with <LingoProvider locale=\"en\"> to fix this.',\n );\n }\n return ctx.lingo;\n}\n"],"mappings":"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"}
|