@c9up/rosetta 0.1.3 → 0.1.4

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.
@@ -0,0 +1,100 @@
1
+ export type TranslationParams = Record<string, string | number | boolean | Date | null | undefined>;
2
+ export type MessageValue = string | number | boolean | null;
3
+ export type MessageTree = {
4
+ [key: string]: MessageValue | MessageTree;
5
+ };
6
+ export type MessageCatalog = Record<string, string>;
7
+ export interface TranslateOptions {
8
+ locale?: string;
9
+ defaultValue?: string;
10
+ }
11
+ export interface LocaleResolverInput {
12
+ header?: string | null;
13
+ accepted?: string[];
14
+ }
15
+ export interface RosettaLoader {
16
+ load(locale: string): Promise<MessageTree | MessageCatalog | null | undefined>;
17
+ }
18
+ export interface RosettaOptions {
19
+ defaultLocale?: string;
20
+ supportedLocales?: string[];
21
+ fallbackLocale?: string;
22
+ fallbackLocales?: Record<string, string>;
23
+ messages?: Record<string, MessageTree | MessageCatalog>;
24
+ loaders?: RosettaLoader[];
25
+ }
26
+ /**
27
+ * Numeric separators for a resolved locale. Returned by
28
+ * `Rosetta.getNumberFormatData()` so consumers (e.g. `@c9up/atom`'s
29
+ * `Decimal.parseLocale`) can honor Rosetta's `fallbackLocales`
30
+ * chain when extracting group / decimal separators — without
31
+ * importing `@c9up/rosetta` directly (structural typing).
32
+ */
33
+ export interface NumberFormatData {
34
+ decimal: string;
35
+ group: string;
36
+ minus: string;
37
+ plusSign: string;
38
+ }
39
+ export declare class RosettaLocale {
40
+ #private;
41
+ constructor(manager: Rosetta, locale: string);
42
+ getLocale(): string;
43
+ has(key: string): boolean;
44
+ t(key: string, params?: TranslationParams, options?: Omit<TranslateOptions, "locale">): string;
45
+ formatNumber(value: number, options?: Intl.NumberFormatOptions): string;
46
+ formatCurrency(value: number, currency: string, options?: Intl.NumberFormatOptions): string;
47
+ formatDate(value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
48
+ formatRelativeTime(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
49
+ getNumberFormatData(): NumberFormatData;
50
+ formatNumberString(value: string, options?: Intl.NumberFormatOptions): string;
51
+ }
52
+ /**
53
+ * Rosetta i18n manager.
54
+ *
55
+ * Framework-agnostic with request-scoped locale instances.
56
+ */
57
+ export declare class Rosetta {
58
+ #private;
59
+ constructor(options?: RosettaOptions);
60
+ boot(): Promise<void>;
61
+ locale(locale: string): RosettaLocale;
62
+ loadMessages(locale: string, messages: MessageTree | MessageCatalog): this;
63
+ loadLocale(locale: string): Promise<this>;
64
+ setLocale(locale: string): this;
65
+ getLocale(): string;
66
+ setDefaultLocale(locale: string): this;
67
+ getDefaultLocale(): string;
68
+ setSupportedLocales(locales: string[]): this;
69
+ getSupportedLocales(): string[] | undefined;
70
+ setFallbackLocale(locale: string): this;
71
+ getFallbackLocale(): string;
72
+ setFallbackLocales(locales: Record<string, string>): this;
73
+ getFallbackLocales(): Record<string, string>;
74
+ resolveLocale(input: string | LocaleResolverInput): string;
75
+ has(key: string, locale?: string): boolean;
76
+ t(key: string, params?: TranslationParams, options?: TranslateOptions): string;
77
+ formatNumber(value: number, options?: Intl.NumberFormatOptions, locale?: string): string;
78
+ formatCurrency(value: number, currency: string, options?: Intl.NumberFormatOptions, locale?: string): string;
79
+ formatDate(value: Date | number | string, options?: Intl.DateTimeFormatOptions, locale?: string): string;
80
+ formatRelativeTime(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions, locale?: string): string;
81
+ /**
82
+ * Resolve the numeric separators for `locale`, honoring the
83
+ * configured `fallbackLocales` chain. Returns the first locale
84
+ * in the chain that `Intl.NumberFormat` actually supports.
85
+ *
86
+ * Cached per resolved locale so the `formatToParts` work runs
87
+ * once per chain destination.
88
+ */
89
+ getNumberFormatData(locale?: string): NumberFormatData;
90
+ /**
91
+ * Format a string-valued number through `Intl.NumberFormat`'s
92
+ * string-accepting overload — preserves precision on 18+-digit
93
+ * values that would otherwise truncate via `Number()`.
94
+ *
95
+ * Locale resolution uses the same fallback chain as
96
+ * `getNumberFormatData`.
97
+ */
98
+ formatNumberString(value: string, options?: Intl.NumberFormatOptions, locale?: string): string;
99
+ }
100
+ //# sourceMappingURL=Rosetta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Rosetta.d.ts","sourceRoot":"","sources":["../src/Rosetta.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,375 @@
1
+ import { createNativeEngine } from "./native.js";
2
+ const DEFAULT_LOCALE = "en";
3
+ export class RosettaLocale {
4
+ #manager;
5
+ #locale;
6
+ constructor(manager, locale) {
7
+ this.#manager = manager;
8
+ this.#locale = locale;
9
+ }
10
+ getLocale() {
11
+ return this.#locale;
12
+ }
13
+ has(key) {
14
+ return this.#manager.has(key, this.#locale);
15
+ }
16
+ t(key, params, options) {
17
+ return this.#manager.t(key, params, { ...options, locale: this.#locale });
18
+ }
19
+ formatNumber(value, options) {
20
+ return this.#manager.formatNumber(value, options, this.#locale);
21
+ }
22
+ formatCurrency(value, currency, options) {
23
+ return this.#manager.formatCurrency(value, currency, options, this.#locale);
24
+ }
25
+ formatDate(value, options) {
26
+ return this.#manager.formatDate(value, options, this.#locale);
27
+ }
28
+ formatRelativeTime(value, unit, options) {
29
+ return this.#manager.formatRelativeTime(value, unit, options, this.#locale);
30
+ }
31
+ getNumberFormatData() {
32
+ return this.#manager.getNumberFormatData(this.#locale);
33
+ }
34
+ formatNumberString(value, options) {
35
+ return this.#manager.formatNumberString(value, options, this.#locale);
36
+ }
37
+ }
38
+ /**
39
+ * Rosetta i18n manager.
40
+ *
41
+ * Framework-agnostic with request-scoped locale instances.
42
+ */
43
+ export class Rosetta {
44
+ #messages = new Map();
45
+ #catalogsCache;
46
+ #catalogsCacheDirty = true;
47
+ /** Stateful Rust engine (Story 37.9) — holds parsed catalogs in Rust memory. */
48
+ #nativeEngine = createNativeEngine();
49
+ #locale;
50
+ #defaultLocale;
51
+ #fallbackLocale;
52
+ #fallbackLocales;
53
+ #supportedLocales;
54
+ #loaders;
55
+ #numberFormatDataCache = new Map();
56
+ constructor(options = {}) {
57
+ this.#defaultLocale = normalizeLocale(options.defaultLocale ?? DEFAULT_LOCALE);
58
+ this.#locale = this.#defaultLocale;
59
+ this.#fallbackLocale = normalizeLocale(options.fallbackLocale ?? this.#defaultLocale);
60
+ this.#fallbackLocales = normalizeFallbackMap(options.fallbackLocales ?? {});
61
+ this.#supportedLocales = options.supportedLocales
62
+ ? new Set(options.supportedLocales.map(normalizeLocale))
63
+ : undefined;
64
+ this.#loaders = options.loaders ?? [];
65
+ if (options.messages) {
66
+ for (const [locale, catalog] of Object.entries(options.messages)) {
67
+ this.loadMessages(locale, catalog);
68
+ }
69
+ }
70
+ }
71
+ async boot() {
72
+ if (!this.#supportedLocales || this.#loaders.length === 0) {
73
+ return;
74
+ }
75
+ for (const locale of this.#supportedLocales) {
76
+ await this.#loadFromLoaders(locale);
77
+ }
78
+ }
79
+ locale(locale) {
80
+ return new RosettaLocale(this, normalizeLocale(locale));
81
+ }
82
+ loadMessages(locale, messages) {
83
+ const normalizedLocale = normalizeLocale(locale);
84
+ const existing = this.#messages.get(normalizedLocale) ?? {};
85
+ const flattened = flattenMessages(messages);
86
+ this.#messages.set(normalizedLocale, { ...existing, ...flattened });
87
+ this.#catalogsCacheDirty = true;
88
+ return this;
89
+ }
90
+ async loadLocale(locale) {
91
+ await this.#loadFromLoaders(normalizeLocale(locale));
92
+ return this;
93
+ }
94
+ setLocale(locale) {
95
+ this.#locale = normalizeLocale(locale);
96
+ return this;
97
+ }
98
+ getLocale() {
99
+ return this.#locale;
100
+ }
101
+ setDefaultLocale(locale) {
102
+ this.#defaultLocale = normalizeLocale(locale);
103
+ // Invalidate the number-format-data cache: even though the
104
+ // cache is keyed by the *resolved* locale (so most entries
105
+ // stay correct across config changes), an entry produced by
106
+ // the previous "ultimate fallback" path may now mismatch.
107
+ this.#numberFormatDataCache.clear();
108
+ return this;
109
+ }
110
+ getDefaultLocale() {
111
+ return this.#defaultLocale;
112
+ }
113
+ setSupportedLocales(locales) {
114
+ this.#supportedLocales = new Set(locales.map(normalizeLocale));
115
+ return this;
116
+ }
117
+ getSupportedLocales() {
118
+ return this.#supportedLocales
119
+ ? Array.from(this.#supportedLocales)
120
+ : undefined;
121
+ }
122
+ setFallbackLocale(locale) {
123
+ this.#fallbackLocale = normalizeLocale(locale);
124
+ this.#numberFormatDataCache.clear();
125
+ return this;
126
+ }
127
+ getFallbackLocale() {
128
+ return this.#fallbackLocale;
129
+ }
130
+ setFallbackLocales(locales) {
131
+ this.#fallbackLocales = normalizeFallbackMap(locales);
132
+ this.#numberFormatDataCache.clear();
133
+ return this;
134
+ }
135
+ getFallbackLocales() {
136
+ return { ...this.#fallbackLocales };
137
+ }
138
+ resolveLocale(input) {
139
+ const requested = typeof input === "string"
140
+ ? parseAcceptLanguage(input)
141
+ : (input.accepted ?? parseAcceptLanguage(input.header ?? ""));
142
+ for (const candidate of requested) {
143
+ const normalized = normalizeLocale(candidate);
144
+ const supported = this.#pickFirstSupported(this.#localeChainFor(normalized));
145
+ if (supported) {
146
+ return supported;
147
+ }
148
+ }
149
+ const fallback = this.#pickFirstSupported(this.#localeChainFor(this.#defaultLocale));
150
+ return fallback ?? this.#defaultLocale;
151
+ }
152
+ has(key, locale = this.#locale) {
153
+ const normalizedLocale = normalizeLocale(locale);
154
+ const chain = this.#localeChainFor(normalizedLocale);
155
+ if (!this.#nativeEngine) {
156
+ throw new Error("[ROSETTA_NAPI_REQUIRED] The Rust ICU engine is required. Build it with `cd packages/rosetta && pnpm build:napi`.");
157
+ }
158
+ this.#syncNativeEngine();
159
+ return this.#nativeEngine.has(key, JSON.stringify(chain));
160
+ }
161
+ t(key, params, options) {
162
+ const requestedLocale = normalizeLocale(options?.locale ?? this.#locale);
163
+ const chain = this.#localeChainFor(requestedLocale);
164
+ if (!this.#nativeEngine) {
165
+ throw new Error("[ROSETTA_NAPI_REQUIRED] The Rust ICU engine is required. Build it with `cd packages/rosetta && pnpm build:napi`.");
166
+ }
167
+ this.#syncNativeEngine();
168
+ const paramsJson = params ? JSON.stringify(params) : undefined;
169
+ return this.#nativeEngine.translate(key, paramsJson, JSON.stringify(chain), options?.defaultValue);
170
+ }
171
+ formatNumber(value, options, locale = this.#locale) {
172
+ return new Intl.NumberFormat(normalizeLocale(locale), options).format(value);
173
+ }
174
+ formatCurrency(value, currency, options, locale = this.#locale) {
175
+ // Spread `options` FIRST so explicit `style: "currency"` and
176
+ // `currency` always win — a caller's stray `{ style: "decimal" }`
177
+ // must not silently disable currency formatting.
178
+ return this.formatNumber(value, { ...options, style: "currency", currency }, locale);
179
+ }
180
+ formatDate(value, options, locale = this.#locale) {
181
+ const date = value instanceof Date ? value : new Date(value);
182
+ return new Intl.DateTimeFormat(normalizeLocale(locale), options).format(date);
183
+ }
184
+ formatRelativeTime(value, unit, options, locale = this.#locale) {
185
+ return new Intl.RelativeTimeFormat(normalizeLocale(locale), options).format(value, unit);
186
+ }
187
+ /**
188
+ * Resolve the numeric separators for `locale`, honoring the
189
+ * configured `fallbackLocales` chain. Returns the first locale
190
+ * in the chain that `Intl.NumberFormat` actually supports.
191
+ *
192
+ * Cached per resolved locale so the `formatToParts` work runs
193
+ * once per chain destination.
194
+ */
195
+ getNumberFormatData(locale = this.#locale) {
196
+ const resolved = this.#resolveNumberLocale(locale);
197
+ const cached = this.#numberFormatDataCache.get(resolved);
198
+ if (cached)
199
+ return cached;
200
+ const formatter = new Intl.NumberFormat(resolved);
201
+ const negativeParts = formatter.formatToParts(-12345.6);
202
+ const positiveParts = new Intl.NumberFormat(resolved, {
203
+ signDisplay: "always",
204
+ }).formatToParts(1);
205
+ const data = {
206
+ decimal: negativeParts.find((p) => p.type === "decimal")?.value ?? ".",
207
+ group: negativeParts.find((p) => p.type === "group")?.value ?? ",",
208
+ minus: negativeParts.find((p) => p.type === "minusSign")?.value ?? "-",
209
+ plusSign: positiveParts.find((p) => p.type === "plusSign")?.value ?? "+",
210
+ };
211
+ this.#numberFormatDataCache.set(resolved, data);
212
+ return data;
213
+ }
214
+ /**
215
+ * Format a string-valued number through `Intl.NumberFormat`'s
216
+ * string-accepting overload — preserves precision on 18+-digit
217
+ * values that would otherwise truncate via `Number()`.
218
+ *
219
+ * Locale resolution uses the same fallback chain as
220
+ * `getNumberFormatData`.
221
+ */
222
+ formatNumberString(value, options, locale = this.#locale) {
223
+ const resolved = this.#resolveNumberLocale(locale);
224
+ const formatter = new Intl.NumberFormat(resolved, options);
225
+ return formatter.format(value);
226
+ }
227
+ #resolveNumberLocale(locale) {
228
+ const chain = this.#localeChainFor(normalizeLocale(locale));
229
+ for (const candidate of chain) {
230
+ const supported = Intl.NumberFormat.supportedLocalesOf([candidate], {
231
+ localeMatcher: "lookup",
232
+ });
233
+ if (supported.length > 0)
234
+ return supported[0];
235
+ }
236
+ // Last-resort: the configured #defaultLocale itself may not be
237
+ // Intl-supported (e.g. an invented private-use tag). Validate
238
+ // it once so the cache key is always a real Intl locale.
239
+ const defaultSupported = Intl.NumberFormat.supportedLocalesOf([this.#defaultLocale], { localeMatcher: "lookup" });
240
+ if (defaultSupported.length > 0)
241
+ return defaultSupported[0];
242
+ // Truly unresolvable — fall back to "en", which every Intl
243
+ // implementation must support per ECMA-402.
244
+ return "en";
245
+ }
246
+ async #loadFromLoaders(locale) {
247
+ for (const loader of this.#loaders) {
248
+ const messages = await loader.load(locale);
249
+ if (messages) {
250
+ this.loadMessages(locale, messages);
251
+ }
252
+ }
253
+ }
254
+ #pickFirstSupported(candidates) {
255
+ if (!this.#supportedLocales) {
256
+ return candidates[0];
257
+ }
258
+ return candidates.find((candidate) => this.#supportedLocales?.has(candidate));
259
+ }
260
+ #localeChainFor(locale) {
261
+ const chain = [];
262
+ const visited = new Set();
263
+ const push = (value) => {
264
+ const normalized = normalizeLocale(value);
265
+ if (!normalized || visited.has(normalized))
266
+ return;
267
+ visited.add(normalized);
268
+ chain.push(normalized);
269
+ };
270
+ push(locale);
271
+ const localeParts = locale.split("-");
272
+ if (localeParts.length > 1) {
273
+ push(localeParts[0]);
274
+ }
275
+ const explicitFallback = this.#fallbackLocales[locale];
276
+ if (explicitFallback) {
277
+ push(explicitFallback);
278
+ const explicitParts = explicitFallback.split("-");
279
+ if (explicitParts.length > 1) {
280
+ push(explicitParts[0]);
281
+ }
282
+ }
283
+ push(this.#fallbackLocale);
284
+ const fallbackParts = this.#fallbackLocale.split("-");
285
+ if (fallbackParts.length > 1) {
286
+ push(fallbackParts[0]);
287
+ }
288
+ push(this.#defaultLocale);
289
+ const defaultParts = this.#defaultLocale.split("-");
290
+ if (defaultParts.length > 1) {
291
+ push(defaultParts[0]);
292
+ }
293
+ return chain;
294
+ }
295
+ /**
296
+ * Sync the Rust-resident catalog with the TS-side `#messages` map. Only
297
+ * performs JSON.stringify when the dirty flag is set (a locale was loaded or
298
+ * edited). On subsequent `t()` / `has()` calls with no changes, this is a
299
+ * no-op — the Rust engine already holds the parsed catalog in memory.
300
+ *
301
+ * Story 37.9: this replaces the old `#catalogsJson()` which serialized the
302
+ * entire catalog on EVERY `t()` call.
303
+ */
304
+ #syncNativeEngine() {
305
+ if (!this.#nativeEngine)
306
+ return;
307
+ if (!this.#catalogsCacheDirty && this.#catalogsCache)
308
+ return;
309
+ const cache = {};
310
+ for (const [locale, catalog] of this.#messages.entries()) {
311
+ cache[locale] = catalog;
312
+ }
313
+ // loadCatalogs may throw (malformed JSON, lock poisoned). The dirty flag
314
+ // is only cleared AFTER a successful load so a retry on the next t()
315
+ // call re-attempts instead of silently using a stale/empty catalog.
316
+ this.#nativeEngine.loadCatalogs(JSON.stringify(cache));
317
+ this.#catalogsCache = cache;
318
+ this.#catalogsCacheDirty = false;
319
+ }
320
+ }
321
+ function normalizeLocale(locale) {
322
+ return locale.trim().replace(/_/g, "-").toLowerCase();
323
+ }
324
+ function normalizeFallbackMap(map) {
325
+ const normalized = {};
326
+ for (const [from, to] of Object.entries(map)) {
327
+ normalized[normalizeLocale(from)] = normalizeLocale(to);
328
+ }
329
+ return normalized;
330
+ }
331
+ function parseAcceptLanguage(header) {
332
+ return header
333
+ .split(",")
334
+ .map((item) => item.trim())
335
+ .filter(Boolean)
336
+ .map((item) => {
337
+ const [localePart, ...rest] = item.split(";");
338
+ let quality = 1;
339
+ for (const part of rest) {
340
+ const trimmed = part.trim();
341
+ if (trimmed.startsWith("q=")) {
342
+ const n = Number(trimmed.slice(2));
343
+ if (Number.isFinite(n))
344
+ quality = n;
345
+ }
346
+ }
347
+ return { locale: normalizeLocale(localePart), quality };
348
+ })
349
+ .filter((entry) => entry.locale.length > 0)
350
+ .sort((a, b) => b.quality - a.quality)
351
+ .map((entry) => entry.locale);
352
+ }
353
+ function flattenMessages(messages) {
354
+ const out = {};
355
+ walkFlatten("", messages, out);
356
+ return out;
357
+ }
358
+ function walkFlatten(prefix, value, out) {
359
+ if (typeof value === "string") {
360
+ out[prefix] = value;
361
+ return;
362
+ }
363
+ if (typeof value === "number" || typeof value === "boolean") {
364
+ out[prefix] = String(value);
365
+ return;
366
+ }
367
+ if (value === null || value === undefined) {
368
+ return;
369
+ }
370
+ for (const [key, child] of Object.entries(value)) {
371
+ const next = prefix ? `${prefix}.${key}` : key;
372
+ walkFlatten(next, child, out);
373
+ }
374
+ }
375
+ //# sourceMappingURL=Rosetta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Rosetta.js","sourceRoot":"","sources":["../src/Rosetta.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { type RosettaOptions } from "./Rosetta.js";
2
+ /**
3
+ * Duck-typed host context — rosetta stays publishable without
4
+ * importing `@c9up/ream`. Any framework that exposes a Container + a
5
+ * config store satisfies the contract.
6
+ */
7
+ interface RosettaContainer {
8
+ singleton(token: unknown, factory: () => unknown): void;
9
+ resolve<T = unknown>(token: unknown): T;
10
+ }
11
+ interface RosettaConfigStore {
12
+ get<T = unknown>(key: string): T | undefined;
13
+ }
14
+ export interface RosettaAppContext {
15
+ container: RosettaContainer;
16
+ config: RosettaConfigStore;
17
+ }
18
+ export interface RosettaProviderConfig extends RosettaOptions {
19
+ /**
20
+ * If set, a `FileSystemLoader` reading from this directory is
21
+ * appended to `options.loaders`. Most apps only need this and the
22
+ * `supportedLocales` list — the provider's `boot()` calls
23
+ * `rosetta.boot()` so catalogs are warm before the first request.
24
+ */
25
+ rootDir?: string;
26
+ }
27
+ /**
28
+ * RosettaProvider — registers a shared `Rosetta` instance under
29
+ * `Rosetta` + `"i18n"` tokens, then awaits its boot so the configured
30
+ * locale catalogs are loaded by the time the first request lands.
31
+ *
32
+ * // reamrc.ts
33
+ * providers: [() => import('@c9up/rosetta/provider')]
34
+ *
35
+ * // config/i18n.ts
36
+ * import { resolve } from 'node:path'
37
+ * export default {
38
+ * defaultLocale: 'en',
39
+ * supportedLocales: ['en', 'fr'],
40
+ * rootDir: resolve('./resources/lang'),
41
+ * }
42
+ *
43
+ * // anywhere
44
+ * import i18n from '@c9up/rosetta/services/main'
45
+ * const t = i18n.locale('fr').t('greeting', { name: 'Alice' })
46
+ */
47
+ export default class RosettaProvider {
48
+ protected app: RosettaAppContext;
49
+ constructor(app: RosettaAppContext);
50
+ register(): void;
51
+ boot(): Promise<void>;
52
+ shutdown(): Promise<void>;
53
+ }
54
+ export {};
55
+ //# sourceMappingURL=RosettaProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RosettaProvider.d.ts","sourceRoot":"","sources":["../src/RosettaProvider.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAG5D;;;;GAIG;AACH,UAAU,gBAAgB;IACzB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC;CACxC;AACD,UAAU,kBAAkB;IAC3B,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAC7C;AACD,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,gBAAgB,CAAC;IAC5B,MAAM,EAAE,kBAAkB,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC5D;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,OAAO,OAAO,eAAe;IACvB,SAAS,CAAC,GAAG,EAAE,iBAAiB;gBAAtB,GAAG,EAAE,iBAAiB;IAE5C,QAAQ,IAAI,IAAI;IAeV,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAMrB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAC/B"}
@@ -0,0 +1,48 @@
1
+ import { FileSystemLoader } from "./loaders/FileSystemLoader.js";
2
+ import { Rosetta } from "./Rosetta.js";
3
+ import { setI18n } from "./services/main.js";
4
+ /**
5
+ * RosettaProvider — registers a shared `Rosetta` instance under
6
+ * `Rosetta` + `"i18n"` tokens, then awaits its boot so the configured
7
+ * locale catalogs are loaded by the time the first request lands.
8
+ *
9
+ * // reamrc.ts
10
+ * providers: [() => import('@c9up/rosetta/provider')]
11
+ *
12
+ * // config/i18n.ts
13
+ * import { resolve } from 'node:path'
14
+ * export default {
15
+ * defaultLocale: 'en',
16
+ * supportedLocales: ['en', 'fr'],
17
+ * rootDir: resolve('./resources/lang'),
18
+ * }
19
+ *
20
+ * // anywhere
21
+ * import i18n from '@c9up/rosetta/services/main'
22
+ * const t = i18n.locale('fr').t('greeting', { name: 'Alice' })
23
+ */
24
+ export default class RosettaProvider {
25
+ app;
26
+ constructor(app) {
27
+ this.app = app;
28
+ }
29
+ register() {
30
+ this.app.container.singleton(Rosetta, () => {
31
+ const config = this.app.config.get("i18n");
32
+ const options = { ...(config ?? {}) };
33
+ if (config?.rootDir) {
34
+ const fsLoader = new FileSystemLoader({ rootDir: config.rootDir });
35
+ options.loaders = [...(options.loaders ?? []), fsLoader];
36
+ }
37
+ return new Rosetta(options);
38
+ });
39
+ this.app.container.singleton("i18n", () => this.app.container.resolve(Rosetta));
40
+ }
41
+ async boot() {
42
+ const rosetta = this.app.container.resolve(Rosetta);
43
+ await rosetta.boot();
44
+ setI18n(rosetta);
45
+ }
46
+ async shutdown() { }
47
+ }
48
+ //# sourceMappingURL=RosettaProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RosettaProvider.js","sourceRoot":"","sources":["../src/RosettaProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,OAAO,EAAuB,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AA6B7C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,OAAO,OAAO,eAAe;IACb;IAAtB,YAAsB,GAAsB;QAAtB,QAAG,GAAH,GAAG,CAAmB;IAAG,CAAC;IAEhD,QAAQ;QACP,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,EAAE;YAC1C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAwB,MAAM,CAAC,CAAC;YAClE,MAAM,OAAO,GAAmB,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;YACtD,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACrB,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;gBACnE,OAAO,CAAC,OAAO,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,CACzC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAU,OAAO,CAAC,CAC5C,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI;QACT,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAU,OAAO,CAAC,CAAC;QAC7D,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;QACrB,OAAO,CAAC,OAAO,CAAC,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,QAAQ,KAAmB,CAAC;CAClC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @c9up/rosetta — framework-agnostic internationalization module.
3
+ */
4
+ export type { FileSystemLoaderOptions } from "./loaders/FileSystemLoader.js";
5
+ export { FileSystemLoader } from "./loaders/FileSystemLoader.js";
6
+ export { isNativeAvailable } from "./native.js";
7
+ export type { LocaleResolverInput, MessageCatalog, MessageTree, NumberFormatData, RosettaLoader, RosettaLocale, RosettaOptions, TranslateOptions, TranslationParams, } from "./Rosetta.js";
8
+ export { Rosetta } from "./Rosetta.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EAAE,uBAAuB,EAAE,MAAM,+BAA+B,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,YAAY,EACX,mBAAmB,EACnB,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,iBAAiB,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @c9up/rosetta — framework-agnostic internationalization module.
3
+ */
4
+ export { FileSystemLoader } from "./loaders/FileSystemLoader.js";
5
+ export { isNativeAvailable } from "./native.js";
6
+ export { Rosetta } from "./Rosetta.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAYhD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { MessageCatalog, MessageTree, RosettaLoader } from "../Rosetta.js";
2
+ export interface FileSystemLoaderOptions {
3
+ rootDir: string;
4
+ }
5
+ export declare class FileSystemLoader implements RosettaLoader {
6
+ #private;
7
+ constructor(options: FileSystemLoaderOptions);
8
+ load(locale: string): Promise<MessageTree | MessageCatalog | null>;
9
+ }
10
+ //# sourceMappingURL=FileSystemLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileSystemLoader.d.ts","sourceRoot":"","sources":["../../src/loaders/FileSystemLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAKhF,MAAM,WAAW,uBAAuB;IACvC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,gBAAiB,YAAW,aAAa;;gBAGzC,OAAO,EAAE,uBAAuB;IAItC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,cAAc,GAAG,IAAI,CAAC;CA6BxE"}
@@ -0,0 +1,175 @@
1
+ import * as fsp from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ /** IANA BCP 47 locale ID pattern — allows `en`, `en-US`, `zh-Hant-TW`, etc. */
4
+ const SAFE_LOCALE_PATTERN = /^[a-zA-Z]{2,8}(-[a-zA-Z0-9]{2,8})*$/;
5
+ export class FileSystemLoader {
6
+ #rootDir;
7
+ constructor(options) {
8
+ this.#rootDir = options.rootDir;
9
+ }
10
+ async load(locale) {
11
+ // Validate locale to prevent path traversal. A locale like
12
+ // `../../etc/passwd` would escape the rootDir without this check.
13
+ if (!SAFE_LOCALE_PATTERN.test(locale))
14
+ return null;
15
+ for (const ext of ["json", "yaml", "yml"]) {
16
+ const fullPath = path.join(this.#rootDir, `${locale}.${ext}`);
17
+ // Defense-in-depth: verify the resolved path is still inside rootDir
18
+ // even after the regex check (belt + suspenders).
19
+ if (!path.resolve(fullPath).startsWith(path.resolve(this.#rootDir)))
20
+ return null;
21
+ try {
22
+ const raw = await fsp.readFile(fullPath, "utf8");
23
+ return ext === "json"
24
+ ? JSON.parse(raw)
25
+ : parseYaml(raw);
26
+ }
27
+ catch (err) {
28
+ // ENOENT = file doesn't exist → try next extension. Any other error
29
+ // (permission denied, invalid JSON, disk failure) is a real problem
30
+ // that must surface — silently swallowing it would turn a config bug
31
+ // into a "missing translation" ghost that's impossible to debug in prod.
32
+ const code = err?.code;
33
+ if (code === "ENOENT")
34
+ continue;
35
+ throw err;
36
+ }
37
+ }
38
+ return null;
39
+ }
40
+ }
41
+ /**
42
+ * Minimal YAML parser for locale files. Supports:
43
+ * - Nested keys via indentation
44
+ * - Quoted and unquoted scalar values
45
+ * - Colons in values (URLs like `https://example.com`)
46
+ * - Multiline block scalars: `|` (literal) and `>` (folded)
47
+ * - Comments (`#`)
48
+ *
49
+ * Does NOT support: anchors/aliases (`&`/`*`), flow sequences (`[a, b]`),
50
+ * flow mappings (`{a: 1}`), complex keys, merge keys (`<<`), or tags (`!!`).
51
+ * For production apps with complex locale files, replace with `js-yaml`.
52
+ */
53
+ function parseYaml(input) {
54
+ const root = {};
55
+ const stack = [
56
+ { indent: -1, node: root },
57
+ ];
58
+ const lines = input.split(/\r?\n/);
59
+ let i = 0;
60
+ while (i < lines.length) {
61
+ const rawLine = lines[i];
62
+ i++;
63
+ if (!rawLine.trim() || rawLine.trim().startsWith("#"))
64
+ continue;
65
+ // Match `key:` with an optional value. The key stops at the first unquoted
66
+ // colon that is NOT inside a URL scheme (`://`). We use a simpler heuristic:
67
+ // split on the first `:` followed by a space or end-of-line. This correctly
68
+ // handles `url: https://example.com` because the `: ` after `url` is the
69
+ // first split point, and `://` inside the value has no preceding space.
70
+ const match = rawLine.match(/^(\s*)([^:#\s][^:]*?)\s*:\s*(.*)$/);
71
+ if (!match)
72
+ continue;
73
+ const indent = match[1].length;
74
+ const key = match[2].trim();
75
+ let valueRaw = match[3].trim();
76
+ while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
77
+ stack.pop();
78
+ }
79
+ const parent = stack[stack.length - 1].node;
80
+ // Block scalar: `|` (literal) or `>` (folded).
81
+ if (valueRaw === "|" || valueRaw === ">") {
82
+ const block = parseBlockScalar(lines, i, indent, valueRaw === ">");
83
+ parent[key] = block.value;
84
+ i = block.nextIndex;
85
+ continue;
86
+ }
87
+ if (!valueRaw) {
88
+ const child = {};
89
+ parent[key] = child;
90
+ stack.push({ indent, node: child });
91
+ continue;
92
+ }
93
+ // Strip inline comments: `value # comment` → `value`. But only if the `#`
94
+ // is preceded by whitespace (to avoid stripping inside URLs like `#fragment`).
95
+ const commentIdx = valueRaw.search(/\s+#/);
96
+ if (commentIdx > 0)
97
+ valueRaw = valueRaw.slice(0, commentIdx).trim();
98
+ parent[key] = parseScalar(valueRaw);
99
+ }
100
+ return root;
101
+ }
102
+ /**
103
+ * Consume a `|` (literal) or `>` (folded) block scalar starting at `startIndex`.
104
+ * Returns the joined value and the index of the first line past the block.
105
+ */
106
+ function parseBlockScalar(lines, startIndex, indent, fold) {
107
+ const blockLines = [];
108
+ const blockIndent = indent + 2;
109
+ let i = startIndex;
110
+ while (i < lines.length) {
111
+ const bl = lines[i];
112
+ // A line with less indent (or empty after content) ends the block.
113
+ if (bl.trim() && bl.search(/\S/) < blockIndent)
114
+ break;
115
+ blockLines.push(bl.slice(blockIndent) || "");
116
+ i++;
117
+ }
118
+ // Trim trailing empty lines.
119
+ while (blockLines.length > 0 && blockLines[blockLines.length - 1] === "") {
120
+ blockLines.pop();
121
+ }
122
+ return {
123
+ value: fold ? foldParagraphs(blockLines) : blockLines.join("\n"),
124
+ nextIndex: i,
125
+ };
126
+ }
127
+ /**
128
+ * Fold a `>` block scalar: consecutive non-blank lines join with a space, blank
129
+ * lines become hard newlines (paragraph breaks per the YAML spec).
130
+ */
131
+ function foldParagraphs(blockLines) {
132
+ const paragraphs = [];
133
+ let current = [];
134
+ for (const bl of blockLines) {
135
+ if (bl === "") {
136
+ if (current.length > 0)
137
+ paragraphs.push(current.join(" "));
138
+ current = [];
139
+ paragraphs.push("");
140
+ }
141
+ else {
142
+ current.push(bl);
143
+ }
144
+ }
145
+ if (current.length > 0)
146
+ paragraphs.push(current.join(" "));
147
+ return paragraphs.join("\n");
148
+ }
149
+ function parseScalar(value) {
150
+ if (value.startsWith('"') && value.endsWith('"')) {
151
+ // Double-quoted: process YAML escape sequences (\n, \t, \\, \', \")
152
+ return value
153
+ .slice(1, -1)
154
+ .replace(/\\n/g, "\n")
155
+ .replace(/\\t/g, "\t")
156
+ .replace(/\\\\/g, "\\")
157
+ .replace(/\\'/g, "'")
158
+ .replace(/\\"/g, '"');
159
+ }
160
+ if (value.startsWith("'") && value.endsWith("'")) {
161
+ // Single-quoted: only '' escape (doubled apostrophe → single)
162
+ return value.slice(1, -1).replace(/''/g, "'");
163
+ }
164
+ if (value === "true")
165
+ return true;
166
+ if (value === "false")
167
+ return false;
168
+ if (value === "null")
169
+ return null;
170
+ const n = Number(value);
171
+ if (Number.isFinite(n) && value !== "")
172
+ return n;
173
+ return value;
174
+ }
175
+ //# sourceMappingURL=FileSystemLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileSystemLoader.js","sourceRoot":"","sources":["../../src/loaders/FileSystemLoader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Universal engine loader — auto-detects Node (NAPI) vs Browser (WASM).
3
+ *
4
+ * Node gets the stateful `RosettaEngine` class (resident catalog in Rust memory).
5
+ * Browser gets the stateless WASM functions (re-parse catalog per call — acceptable
6
+ * because browser apps typically have smaller catalogs than server-rendered i18n).
7
+ */
8
+ export interface NativeRosettaEngine {
9
+ loadCatalogs(catalogsJson: string): void;
10
+ translate(key: string, paramsJson: string | undefined, chainJson: string, defaultValue: string | undefined): string;
11
+ has(key: string, chainJson: string): boolean;
12
+ }
13
+ export declare function isNativeAvailable(): boolean;
14
+ /** Create a stateful engine (Node NAPI only). Returns null in browser (WASM is stateless). */
15
+ export declare function createNativeEngine(): NativeRosettaEngine | null;
16
+ export declare function nativeTranslate(catalogsJson: string, key: string, paramsJson: string | undefined, chainJson: string, defaultValue: string | undefined): string;
17
+ export declare function nativeHas(catalogsJson: string, key: string, chainJson: string): boolean;
18
+ //# sourceMappingURL=native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native.d.ts","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,mBAAmB;IACnC,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,SAAS,CACR,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GAAG,SAAS,GAC9B,MAAM,CAAC;IACV,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;CAC7C;AAuED,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED,8FAA8F;AAC9F,wBAAgB,kBAAkB,IAAI,mBAAmB,GAAG,IAAI,CAwB/D;AAGD,wBAAgB,eAAe,CAC9B,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GAAG,SAAS,GAC9B,MAAM,CAqBR;AAED,wBAAgB,SAAS,CACxB,YAAY,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GACf,OAAO,CAQT"}
package/dist/native.js ADDED
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Universal engine loader — auto-detects Node (NAPI) vs Browser (WASM).
3
+ *
4
+ * Node gets the stateful `RosettaEngine` class (resident catalog in Rust memory).
5
+ * Browser gets the stateless WASM functions (re-parse catalog per call — acceptable
6
+ * because browser apps typically have smaller catalogs than server-rendered i18n).
7
+ */
8
+ let native;
9
+ let wasmModule;
10
+ let loadError;
11
+ const isNode = typeof globalThis.process !== "undefined" &&
12
+ typeof globalThis.process.versions?.node === "string";
13
+ if (isNode) {
14
+ try {
15
+ const { createRequire } = await import("node:module");
16
+ const { dirname, join } = await import("node:path");
17
+ const { fileURLToPath } = await import("node:url");
18
+ const { arch, platform } = await import("node:process");
19
+ const nodeRequire = createRequire(import.meta.url);
20
+ const currentDir = dirname(fileURLToPath(import.meta.url));
21
+ const platformMap = {
22
+ "linux-x64": "linux-x64-gnu",
23
+ "linux-arm64": "linux-arm64-gnu",
24
+ "darwin-x64": "darwin-x64",
25
+ "darwin-arm64": "darwin-arm64",
26
+ "win32-x64": "win32-x64-msvc",
27
+ };
28
+ const suffix = platformMap[`${platform}-${arch}`];
29
+ if (suffix) {
30
+ native = nodeRequire(join(currentDir, `../index.${suffix}.node`));
31
+ }
32
+ }
33
+ catch (e) {
34
+ loadError = e;
35
+ }
36
+ }
37
+ else {
38
+ try {
39
+ // The WASM glue generated by wasm-pack exports `translate` and `has` as
40
+ // top-level functions with the exact same signatures as `WasmStatelessApi`.
41
+ // We declare the import shape explicitly so no cast is needed.
42
+ const wasm = await import("../wasm/rosetta_engine_wasm.js");
43
+ await wasm.default();
44
+ wasmModule = wasm;
45
+ }
46
+ catch (e) {
47
+ loadError = e;
48
+ }
49
+ }
50
+ export function isNativeAvailable() {
51
+ return native !== undefined || wasmModule !== undefined;
52
+ }
53
+ /** Create a stateful engine (Node NAPI only). Returns null in browser (WASM is stateless). */
54
+ export function createNativeEngine() {
55
+ if (!native) {
56
+ if (!wasmModule)
57
+ return null;
58
+ const wasm = wasmModule;
59
+ let catalogsJson = "{}";
60
+ return {
61
+ loadCatalogs(json) {
62
+ catalogsJson = json;
63
+ },
64
+ translate(key, paramsJson, chainJson, defaultValue) {
65
+ return wasm.translate(catalogsJson, key, paramsJson ?? undefined, chainJson, defaultValue ?? undefined);
66
+ },
67
+ has(key, chainJson) {
68
+ return wasm.has(catalogsJson, key, chainJson);
69
+ },
70
+ };
71
+ }
72
+ return new native.RosettaEngine();
73
+ }
74
+ // Legacy stateless exports (used by tests)
75
+ export function nativeTranslate(catalogsJson, key, paramsJson, chainJson, defaultValue) {
76
+ if (native)
77
+ return native.translate(catalogsJson, key, paramsJson, chainJson, defaultValue);
78
+ if (wasmModule)
79
+ return wasmModule.translate(catalogsJson, key, paramsJson, chainJson, defaultValue);
80
+ throw new Error(`[ROSETTA_ENGINE_NOT_FOUND] ${loadError ?? "binary not found"}`, { cause: loadError });
81
+ }
82
+ export function nativeHas(catalogsJson, key, chainJson) {
83
+ if (native)
84
+ return native.has(catalogsJson, key, chainJson);
85
+ if (wasmModule)
86
+ return wasmModule.has(catalogsJson, key, chainJson);
87
+ throw new Error(`[ROSETTA_ENGINE_NOT_FOUND] ${loadError ?? "binary not found"}`, { cause: loadError });
88
+ }
89
+ //# sourceMappingURL=native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native.js","sourceRoot":"","sources":["../src/native.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Default `Rosetta` singleton — mirror of Adonis's
3
+ * `import i18n from '@adonisjs/i18n/services/main'` shape.
4
+ *
5
+ * import i18n from '@c9up/rosetta/services/main'
6
+ *
7
+ * const locale = i18n.locale(i18n.resolveLocale({ header: req.headers['accept-language'] }))
8
+ * locale.t('greeting', { name: user.name })
9
+ *
10
+ * Populated by `RosettaProvider.boot()` or by the app directly through
11
+ * `setI18n(myRosetta)` when the i18n config has to be built outside
12
+ * the provider flow.
13
+ */
14
+ import type { Rosetta } from "../Rosetta.js";
15
+ /** @internal Bind the singleton (called by RosettaProvider or the app). */
16
+ export declare function setI18n(value: Rosetta): void;
17
+ /** @internal Read the singleton (or `undefined` pre-boot). */
18
+ export declare function getI18n(): Rosetta | undefined;
19
+ declare const i18n: Rosetta;
20
+ export default i18n;
21
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/services/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAI7C,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAE5C;AAED,8DAA8D;AAC9D,wBAAgB,OAAO,IAAI,OAAO,GAAG,SAAS,CAE7C;AAED,QAAA,MAAM,IAAI,EAAE,OAWV,CAAC;AAEH,eAAe,IAAI,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Default `Rosetta` singleton — mirror of Adonis's
3
+ * `import i18n from '@adonisjs/i18n/services/main'` shape.
4
+ *
5
+ * import i18n from '@c9up/rosetta/services/main'
6
+ *
7
+ * const locale = i18n.locale(i18n.resolveLocale({ header: req.headers['accept-language'] }))
8
+ * locale.t('greeting', { name: user.name })
9
+ *
10
+ * Populated by `RosettaProvider.boot()` or by the app directly through
11
+ * `setI18n(myRosetta)` when the i18n config has to be built outside
12
+ * the provider flow.
13
+ */
14
+ let instance;
15
+ /** @internal Bind the singleton (called by RosettaProvider or the app). */
16
+ export function setI18n(value) {
17
+ instance = value;
18
+ }
19
+ /** @internal Read the singleton (or `undefined` pre-boot). */
20
+ export function getI18n() {
21
+ return instance;
22
+ }
23
+ const i18n = new Proxy({}, {
24
+ get(_target, prop) {
25
+ if (!instance) {
26
+ throw new Error("[rosetta] Rosetta singleton accessed before RosettaProvider.boot() ran " +
27
+ "or `setI18n(myRosetta)` was called. Wire one of them first.");
28
+ }
29
+ const value = Reflect.get(instance, prop, instance);
30
+ return typeof value === "function" ? value.bind(instance) : value;
31
+ },
32
+ });
33
+ export default i18n;
34
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/services/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,IAAI,QAA6B,CAAC;AAElC,2EAA2E;AAC3E,MAAM,UAAU,OAAO,CAAC,KAAc;IACrC,QAAQ,GAAG,KAAK,CAAC;AAClB,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,OAAO;IACtB,OAAO,QAAQ,CAAC;AACjB,CAAC;AAED,MAAM,IAAI,GAAY,IAAI,KAAK,CAAC,EAAa,EAAE;IAC9C,GAAG,CAAC,OAAO,EAAE,IAAI;QAChB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACd,yEAAyE;gBACxE,6DAA6D,CAC9D,CAAC;QACH,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QACpD,OAAO,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACnE,CAAC;CACD,CAAC,CAAC;AAEH,eAAe,IAAI,CAAC"}
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/rosetta",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Rosetta — dedicated i18n module for the Ream ecosystem",
5
5
  "license": "MIT",
6
6
  "type": "module",