locon 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.mjs ADDED
@@ -0,0 +1,322 @@
1
+ // src/locon/utils/pluralCategory.ts
2
+ var between = (x, from, to) => x >= from && x <= to;
3
+ var oneIfIntegerOne = ({ i, v }) => i === 1 && v === 0 ? "one" : "other";
4
+ var oneIfOne = ({ n }) => n === 1 ? "one" : "other";
5
+ var oneIfZeroOrOne = ({ i }) => i === 0 || i === 1 ? "one" : "other";
6
+ var onlyOther = () => "other";
7
+ var eastSlavic = ({ i, v }) => {
8
+ if (v !== 0) return "other";
9
+ if (i % 10 === 1 && i % 100 !== 11) return "one";
10
+ if (between(i % 10, 2, 4) && !between(i % 100, 12, 14)) return "few";
11
+ return "many";
12
+ };
13
+ var southSlavic = ({ i, v }) => {
14
+ if (v === 0 && i % 10 === 1 && i % 100 !== 11) return "one";
15
+ if (v === 0 && between(i % 10, 2, 4) && !between(i % 100, 12, 14)) return "few";
16
+ return "other";
17
+ };
18
+ var westSlavic = ({ i, v }) => {
19
+ if (i === 1 && v === 0) return "one";
20
+ if (between(i, 2, 4) && v === 0) return "few";
21
+ return v !== 0 ? "many" : "other";
22
+ };
23
+ var RULES = {
24
+ ru: eastSlavic,
25
+ uk: eastSlavic,
26
+ be: eastSlavic,
27
+ pl: ({ i, v }) => {
28
+ if (v !== 0) return "other";
29
+ if (i === 1) return "one";
30
+ if (between(i % 10, 2, 4) && !between(i % 100, 12, 14)) return "few";
31
+ return "many";
32
+ },
33
+ cs: westSlavic,
34
+ sk: westSlavic,
35
+ hr: southSlavic,
36
+ sr: southSlavic,
37
+ bs: southSlavic,
38
+ sl: ({ i, v }) => {
39
+ if (v !== 0) return "few";
40
+ if (i % 100 === 1) return "one";
41
+ if (i % 100 === 2) return "two";
42
+ if (between(i % 100, 3, 4)) return "few";
43
+ return "other";
44
+ },
45
+ lt: ({ n, v }) => {
46
+ if (v !== 0) return "many";
47
+ if (n % 10 === 1 && !between(n % 100, 11, 19)) return "one";
48
+ if (between(n % 10, 2, 9) && !between(n % 100, 11, 19)) return "few";
49
+ return "other";
50
+ },
51
+ lv: ({ n, v }) => {
52
+ if (v === 0 && (n % 10 === 0 || between(n % 100, 11, 19))) return "zero";
53
+ if (v === 0 && n % 10 === 1 && n % 100 !== 11) return "one";
54
+ return "other";
55
+ },
56
+ ro: ({ n, i, v }) => {
57
+ if (i === 1 && v === 0) return "one";
58
+ if (v !== 0 || n === 0 || n !== 1 && between(n % 100, 1, 19)) return "few";
59
+ return "other";
60
+ },
61
+ ar: ({ n, v }) => {
62
+ if (v !== 0) return "other";
63
+ if (n === 0) return "zero";
64
+ if (n === 1) return "one";
65
+ if (n === 2) return "two";
66
+ if (between(n % 100, 3, 10)) return "few";
67
+ if (between(n % 100, 11, 99)) return "many";
68
+ return "other";
69
+ },
70
+ he: ({ i, v }) => {
71
+ if (i === 1 && v === 0 || i === 0 && v !== 0) return "one";
72
+ if (i === 2 && v === 0) return "two";
73
+ return "other";
74
+ },
75
+ ga: ({ n, v }) => {
76
+ if (v !== 0) return "other";
77
+ if (n === 1) return "one";
78
+ if (n === 2) return "two";
79
+ if (between(n, 3, 6)) return "few";
80
+ if (between(n, 7, 10)) return "many";
81
+ return "other";
82
+ },
83
+ fr: oneIfZeroOrOne,
84
+ pt: oneIfZeroOrOne,
85
+ hi: ({ n, i }) => i === 0 || n === 1 ? "one" : "other",
86
+ bn: ({ n, i }) => i === 0 || n === 1 ? "one" : "other",
87
+ fa: ({ n, i }) => i === 0 || n === 1 ? "one" : "other",
88
+ es: oneIfOne,
89
+ tr: oneIfOne,
90
+ el: oneIfOne,
91
+ hu: oneIfOne,
92
+ ka: oneIfOne,
93
+ kk: oneIfOne,
94
+ az: oneIfOne,
95
+ uz: oneIfOne,
96
+ bg: oneIfOne,
97
+ sq: oneIfOne,
98
+ ja: onlyOther,
99
+ zh: onlyOther,
100
+ ko: onlyOther,
101
+ vi: onlyOther,
102
+ th: onlyOther,
103
+ id: onlyOther,
104
+ ms: onlyOther,
105
+ my: onlyOther,
106
+ km: onlyOther,
107
+ lo: onlyOther
108
+ };
109
+ function operands(count) {
110
+ const n = Math.abs(count);
111
+ const fraction = String(n).split(".")[1] ?? "";
112
+ return { n, i: Math.floor(n), v: fraction.length };
113
+ }
114
+ function fallbackCategory(count, tag) {
115
+ const language = tag.split("-")[0]?.toLowerCase() ?? "";
116
+ const rule = RULES[language] ?? oneIfIntegerOne;
117
+ return rule(operands(count));
118
+ }
119
+ function pluralCategory(count, locale) {
120
+ const tag = locale.replace(/_/g, "-");
121
+ if (typeof Intl !== "undefined" && typeof Intl.PluralRules === "function") {
122
+ try {
123
+ return new Intl.PluralRules(tag).select(count);
124
+ } catch {
125
+ }
126
+ }
127
+ return fallbackCategory(count, tag);
128
+ }
129
+
130
+ // src/locon/utils/createTranslator.ts
131
+ var reverseIndexCache = /* @__PURE__ */ new WeakMap();
132
+ function getReverseIndex(assets, locale) {
133
+ let perLocale = reverseIndexCache.get(assets);
134
+ if (!perLocale) {
135
+ perLocale = /* @__PURE__ */ new Map();
136
+ reverseIndexCache.set(assets, perLocale);
137
+ }
138
+ const cached = perLocale.get(locale);
139
+ if (cached) {
140
+ return cached;
141
+ }
142
+ const index = /* @__PURE__ */ new Map();
143
+ const localeAssets = assets[locale];
144
+ if (localeAssets) {
145
+ for (const key of Object.keys(localeAssets)) {
146
+ const value = localeAssets[key];
147
+ if (value !== void 0 && !index.has(value)) {
148
+ index.set(value, key);
149
+ }
150
+ }
151
+ }
152
+ perLocale.set(locale, index);
153
+ return index;
154
+ }
155
+ function interpolate(value, params) {
156
+ if (!params) {
157
+ return value;
158
+ }
159
+ return value.replace(
160
+ /\{(\w+)\}/g,
161
+ (match, token) => params[token] === void 0 ? match : String(params[token])
162
+ );
163
+ }
164
+ var PLURAL_SUFFIX = /_(zero|one|two|few|many|other)$/;
165
+ function createTranslator({ assets, locale, defaultLocale = "en", projectLocale }) {
166
+ const resolvedProjectLocale = projectLocale || defaultLocale;
167
+ const read = (fromLocale, key) => {
168
+ const value = assets[fromLocale]?.[key];
169
+ return value === void 0 || value === "" ? void 0 : value;
170
+ };
171
+ const keyFor = (input, targetLocale) => {
172
+ if (assets[targetLocale]?.[input] !== void 0 || assets[defaultLocale]?.[input] !== void 0 || assets[resolvedProjectLocale]?.[input] !== void 0) {
173
+ return input;
174
+ }
175
+ return getReverseIndex(assets, resolvedProjectLocale).get(input) ?? getReverseIndex(assets, defaultLocale).get(input) ?? input;
176
+ };
177
+ return function l(assetKey, options) {
178
+ if (!assetKey) {
179
+ return "";
180
+ }
181
+ const targetLocale = options?.locale || locale;
182
+ const resolvedKey = keyFor(assetKey, targetLocale);
183
+ const key = options?.count === void 0 ? resolvedKey : resolvedKey.replace(PLURAL_SUFFIX, "");
184
+ const params = options?.count === void 0 ? options?.params : { ...options.params, count: options.count };
185
+ for (const candidateLocale of [targetLocale, defaultLocale, resolvedProjectLocale]) {
186
+ const candidateKeys = options?.count === void 0 ? [key] : [`${key}_${pluralCategory(options.count, candidateLocale)}`, `${key}_other`, key];
187
+ for (const candidateKey of candidateKeys) {
188
+ const value = read(candidateLocale, candidateKey);
189
+ if (value !== void 0) {
190
+ return interpolate(value, params);
191
+ }
192
+ }
193
+ }
194
+ return interpolate(options?.fallback ?? assetKey, params);
195
+ };
196
+ }
197
+ var createTranslator_default = createTranslator;
198
+
199
+ // src/locon/utils/intlLocale.ts
200
+ function intlLocale(locale, extensions = {}) {
201
+ const normalizedLocale = locale.replace(/_/g, "-");
202
+ const requested = [];
203
+ if (extensions.calendar) {
204
+ requested.push(["ca", extensions.calendar]);
205
+ }
206
+ if (extensions.numbering) {
207
+ requested.push(["nu", extensions.numbering]);
208
+ }
209
+ if (!requested.length) {
210
+ return normalizedLocale;
211
+ }
212
+ const subtags = normalizedLocale.split("-");
213
+ const isSingleton = (subtag) => subtag.length === 1;
214
+ const privateUseIndex = subtags.findIndex((subtag, index) => index > 0 && subtag.toLowerCase() === "x");
215
+ const unicodeIndex = subtags.findIndex(
216
+ (subtag, index) => index > 0 && (privateUseIndex < 0 || index < privateUseIndex) && subtag.toLowerCase() === "u"
217
+ );
218
+ if (unicodeIndex >= 0) {
219
+ const nextSingleton = subtags.findIndex((subtag, index) => index > unicodeIndex && isSingleton(subtag));
220
+ const unicodeEnd = nextSingleton < 0 ? subtags.length : nextSingleton;
221
+ const existingKeys = new Set(
222
+ subtags.slice(unicodeIndex + 1, unicodeEnd).filter((subtag) => subtag.length === 2).map((subtag) => subtag.toLowerCase())
223
+ );
224
+ const additions = requested.flatMap(([key, value]) => existingKeys.has(key) ? [] : [key, value]);
225
+ if (!additions.length) {
226
+ return normalizedLocale;
227
+ }
228
+ subtags.splice(unicodeEnd, 0, ...additions);
229
+ return subtags.join("-");
230
+ }
231
+ const insertionIndex = privateUseIndex < 0 ? subtags.length : privateUseIndex;
232
+ subtags.splice(insertionIndex, 0, "u", ...requested.flat());
233
+ return subtags.join("-");
234
+ }
235
+ var intlLocale_default = intlLocale;
236
+
237
+ // src/core/resources.ts
238
+ var normalized = (value) => value.replace(/_/g, "-").toLowerCase();
239
+ function matchLocale(languages, supported, fallback) {
240
+ for (const language of languages) {
241
+ let candidate = normalized(language);
242
+ while (candidate) {
243
+ const exact = supported.find((locale) => normalized(locale) === candidate);
244
+ if (exact) return exact;
245
+ const next = candidate.lastIndexOf("-");
246
+ if (next < 0) break;
247
+ candidate = candidate.slice(0, next);
248
+ }
249
+ const base = normalized(language).split("-")[0];
250
+ const variant = supported.find((locale) => normalized(locale).split("-")[0] === base);
251
+ if (variant) return variant;
252
+ }
253
+ return fallback;
254
+ }
255
+ function createLocon(options) {
256
+ const locales = Object.keys(options.loaders);
257
+ if (!locales.includes(options.defaultLocale)) throw new Error("[locon] defaultLocale needs a loader");
258
+ const initial = matchLocale(options.locale ? [options.locale] : options.languages ?? [], locales, options.defaultLocale);
259
+ let snapshot = { currentLocale: initial, loading: false, ready: false, error: null };
260
+ let assets = {};
261
+ const pending = /* @__PURE__ */ new Map();
262
+ const listeners = /* @__PURE__ */ new Set();
263
+ let selection = 0;
264
+ const notify = (next) => {
265
+ snapshot = next;
266
+ listeners.forEach((listener) => listener());
267
+ };
268
+ async function loadLocale(locale) {
269
+ if (!locales.includes(locale)) throw new Error(`[locon] Unsupported locale: ${locale}`);
270
+ if (assets[locale]) return assets[locale];
271
+ let request = pending.get(locale);
272
+ if (!request) {
273
+ request = Promise.resolve().then(options.loaders[locale]).then((module) => {
274
+ const resource = typeof module.default === "object" ? module.default : module;
275
+ if (!resource || typeof resource !== "object" || Array.isArray(resource) || Object.values(resource).some((value) => typeof value !== "string")) {
276
+ throw new Error(`[locon] Invalid resources: ${locale}`);
277
+ }
278
+ assets = { ...assets, [locale]: { ...resource } };
279
+ return assets[locale];
280
+ }).finally(() => pending.delete(locale));
281
+ pending.set(locale, request);
282
+ }
283
+ return request;
284
+ }
285
+ async function setLocale(locale) {
286
+ const target = locale === null ? matchLocale(options.languages ?? [], locales, options.defaultLocale) : locale;
287
+ if (!locales.includes(target)) throw new Error(`[locon] Unsupported locale: ${target}`);
288
+ const version = ++selection;
289
+ notify({ ...snapshot, loading: true, error: null });
290
+ try {
291
+ await loadLocale(target);
292
+ if (version !== selection) return false;
293
+ notify({ currentLocale: target, loading: false, ready: true, error: null });
294
+ return true;
295
+ } catch (cause) {
296
+ if (version === selection) notify({ ...snapshot, loading: false, error: cause instanceof Error ? cause : new Error("[locon] Loading failed") });
297
+ throw cause;
298
+ }
299
+ }
300
+ function l(key, translateOptions) {
301
+ return createTranslator_default({ assets, locale: snapshot.currentLocale, defaultLocale: options.defaultLocale, projectLocale: options.projectLocale })(key, translateOptions);
302
+ }
303
+ return {
304
+ locales,
305
+ l,
306
+ loadLocale,
307
+ setLocale,
308
+ getSnapshot: () => snapshot,
309
+ subscribe(listener) {
310
+ listeners.add(listener);
311
+ return () => {
312
+ listeners.delete(listener);
313
+ };
314
+ }
315
+ };
316
+ }
317
+ export {
318
+ createLocon,
319
+ createTranslator_default as createTranslator,
320
+ intlLocale_default as intlLocale,
321
+ matchLocale
322
+ };
package/dist/index.js CHANGED
@@ -36,6 +36,135 @@ __export(index_exports, {
36
36
  module.exports = __toCommonJS(index_exports);
37
37
  var import_react2 = require("react");
38
38
 
39
+ // src/locon/utils/pluralCategory.ts
40
+ var between = (x, from, to) => x >= from && x <= to;
41
+ var oneIfIntegerOne = ({ i, v }) => i === 1 && v === 0 ? "one" : "other";
42
+ var oneIfOne = ({ n }) => n === 1 ? "one" : "other";
43
+ var oneIfZeroOrOne = ({ i }) => i === 0 || i === 1 ? "one" : "other";
44
+ var onlyOther = () => "other";
45
+ var eastSlavic = ({ i, v }) => {
46
+ if (v !== 0) return "other";
47
+ if (i % 10 === 1 && i % 100 !== 11) return "one";
48
+ if (between(i % 10, 2, 4) && !between(i % 100, 12, 14)) return "few";
49
+ return "many";
50
+ };
51
+ var southSlavic = ({ i, v }) => {
52
+ if (v === 0 && i % 10 === 1 && i % 100 !== 11) return "one";
53
+ if (v === 0 && between(i % 10, 2, 4) && !between(i % 100, 12, 14)) return "few";
54
+ return "other";
55
+ };
56
+ var westSlavic = ({ i, v }) => {
57
+ if (i === 1 && v === 0) return "one";
58
+ if (between(i, 2, 4) && v === 0) return "few";
59
+ return v !== 0 ? "many" : "other";
60
+ };
61
+ var RULES = {
62
+ ru: eastSlavic,
63
+ uk: eastSlavic,
64
+ be: eastSlavic,
65
+ pl: ({ i, v }) => {
66
+ if (v !== 0) return "other";
67
+ if (i === 1) return "one";
68
+ if (between(i % 10, 2, 4) && !between(i % 100, 12, 14)) return "few";
69
+ return "many";
70
+ },
71
+ cs: westSlavic,
72
+ sk: westSlavic,
73
+ hr: southSlavic,
74
+ sr: southSlavic,
75
+ bs: southSlavic,
76
+ sl: ({ i, v }) => {
77
+ if (v !== 0) return "few";
78
+ if (i % 100 === 1) return "one";
79
+ if (i % 100 === 2) return "two";
80
+ if (between(i % 100, 3, 4)) return "few";
81
+ return "other";
82
+ },
83
+ lt: ({ n, v }) => {
84
+ if (v !== 0) return "many";
85
+ if (n % 10 === 1 && !between(n % 100, 11, 19)) return "one";
86
+ if (between(n % 10, 2, 9) && !between(n % 100, 11, 19)) return "few";
87
+ return "other";
88
+ },
89
+ lv: ({ n, v }) => {
90
+ if (v === 0 && (n % 10 === 0 || between(n % 100, 11, 19))) return "zero";
91
+ if (v === 0 && n % 10 === 1 && n % 100 !== 11) return "one";
92
+ return "other";
93
+ },
94
+ ro: ({ n, i, v }) => {
95
+ if (i === 1 && v === 0) return "one";
96
+ if (v !== 0 || n === 0 || n !== 1 && between(n % 100, 1, 19)) return "few";
97
+ return "other";
98
+ },
99
+ ar: ({ n, v }) => {
100
+ if (v !== 0) return "other";
101
+ if (n === 0) return "zero";
102
+ if (n === 1) return "one";
103
+ if (n === 2) return "two";
104
+ if (between(n % 100, 3, 10)) return "few";
105
+ if (between(n % 100, 11, 99)) return "many";
106
+ return "other";
107
+ },
108
+ he: ({ i, v }) => {
109
+ if (i === 1 && v === 0 || i === 0 && v !== 0) return "one";
110
+ if (i === 2 && v === 0) return "two";
111
+ return "other";
112
+ },
113
+ ga: ({ n, v }) => {
114
+ if (v !== 0) return "other";
115
+ if (n === 1) return "one";
116
+ if (n === 2) return "two";
117
+ if (between(n, 3, 6)) return "few";
118
+ if (between(n, 7, 10)) return "many";
119
+ return "other";
120
+ },
121
+ fr: oneIfZeroOrOne,
122
+ pt: oneIfZeroOrOne,
123
+ hi: ({ n, i }) => i === 0 || n === 1 ? "one" : "other",
124
+ bn: ({ n, i }) => i === 0 || n === 1 ? "one" : "other",
125
+ fa: ({ n, i }) => i === 0 || n === 1 ? "one" : "other",
126
+ es: oneIfOne,
127
+ tr: oneIfOne,
128
+ el: oneIfOne,
129
+ hu: oneIfOne,
130
+ ka: oneIfOne,
131
+ kk: oneIfOne,
132
+ az: oneIfOne,
133
+ uz: oneIfOne,
134
+ bg: oneIfOne,
135
+ sq: oneIfOne,
136
+ ja: onlyOther,
137
+ zh: onlyOther,
138
+ ko: onlyOther,
139
+ vi: onlyOther,
140
+ th: onlyOther,
141
+ id: onlyOther,
142
+ ms: onlyOther,
143
+ my: onlyOther,
144
+ km: onlyOther,
145
+ lo: onlyOther
146
+ };
147
+ function operands(count) {
148
+ const n = Math.abs(count);
149
+ const fraction = String(n).split(".")[1] ?? "";
150
+ return { n, i: Math.floor(n), v: fraction.length };
151
+ }
152
+ function fallbackCategory(count, tag) {
153
+ const language = tag.split("-")[0]?.toLowerCase() ?? "";
154
+ const rule = RULES[language] ?? oneIfIntegerOne;
155
+ return rule(operands(count));
156
+ }
157
+ function pluralCategory(count, locale) {
158
+ const tag = locale.replace(/_/g, "-");
159
+ if (typeof Intl !== "undefined" && typeof Intl.PluralRules === "function") {
160
+ try {
161
+ return new Intl.PluralRules(tag).select(count);
162
+ } catch {
163
+ }
164
+ }
165
+ return fallbackCategory(count, tag);
166
+ }
167
+
39
168
  // src/locon/utils/createTranslator.ts
40
169
  var reverseIndexCache = /* @__PURE__ */ new WeakMap();
41
170
  function getReverseIndex(assets, locale) {
@@ -71,13 +200,6 @@ function interpolate(value, params) {
71
200
  );
72
201
  }
73
202
  var PLURAL_SUFFIX = /_(zero|one|two|few|many|other)$/;
74
- function pluralCategory(count, locale) {
75
- try {
76
- return new Intl.PluralRules(locale.replace(/_/g, "-")).select(count);
77
- } catch {
78
- return count === 1 ? "one" : "other";
79
- }
80
- }
81
203
  function createTranslator({ assets, locale, defaultLocale = "en", projectLocale }) {
82
204
  const resolvedProjectLocale = projectLocale || defaultLocale;
83
205
  const read = (fromLocale, key) => {
@@ -0,0 +1,19 @@
1
+ import { a as LoconInstance, T as TranslateOptions } from './resources-BwFIzg3m.mjs';
2
+ export { R as ResourceLoader, b as ResourceOptions, c as createLocon, m as matchLocale } from './resources-BwFIzg3m.mjs';
3
+ import * as react from 'react';
4
+ import { PropsWithChildren } from 'react';
5
+
6
+ declare function LoconProvider({ instance, children }: PropsWithChildren<{
7
+ instance: LoconInstance;
8
+ }>): react.JSX.Element;
9
+ declare function useLocon(): {
10
+ locales: string[];
11
+ l: (key: string, translateOptions?: TranslateOptions) => string;
12
+ setLocale: (locale: string | null) => Promise<boolean>;
13
+ currentLocale: string;
14
+ loading: boolean;
15
+ ready: boolean;
16
+ error: Error | null;
17
+ };
18
+
19
+ export { LoconInstance, LoconProvider, useLocon };
@@ -0,0 +1,19 @@
1
+ import { a as LoconInstance, T as TranslateOptions } from './resources-BwFIzg3m.js';
2
+ export { R as ResourceLoader, b as ResourceOptions, c as createLocon, m as matchLocale } from './resources-BwFIzg3m.js';
3
+ import * as react from 'react';
4
+ import { PropsWithChildren } from 'react';
5
+
6
+ declare function LoconProvider({ instance, children }: PropsWithChildren<{
7
+ instance: LoconInstance;
8
+ }>): react.JSX.Element;
9
+ declare function useLocon(): {
10
+ locales: string[];
11
+ l: (key: string, translateOptions?: TranslateOptions) => string;
12
+ setLocale: (locale: string | null) => Promise<boolean>;
13
+ currentLocale: string;
14
+ loading: boolean;
15
+ ready: boolean;
16
+ error: Error | null;
17
+ };
18
+
19
+ export { LoconInstance, LoconProvider, useLocon };