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