inlang-plugin-rich-icu 0.1.0 → 0.1.3
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/README.md +3 -3
- package/dist/index.js +36 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,14 +27,14 @@ as a dependency. Add it to your `project.inlang/settings.json`:
|
|
|
27
27
|
Catalogs are JSON maps of `key → ICU string`, one file per locale:
|
|
28
28
|
|
|
29
29
|
```json
|
|
30
|
-
{ "
|
|
30
|
+
{ "messageCount": "{count, plural, one {Vous avez <strong># nouveau message</strong>.} other {Vous avez <strong># nouveaux messages</strong>.}}" }
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
## Render (Paraglide + React)
|
|
34
34
|
|
|
35
35
|
```tsx
|
|
36
36
|
<ParaglideMessage
|
|
37
|
-
message={m.
|
|
37
|
+
message={m.messageCount}
|
|
38
38
|
inputs={{ count }}
|
|
39
39
|
markup={{ strong: ({ children }) => <strong>{children}</strong> }}
|
|
40
40
|
/>
|
|
@@ -71,7 +71,7 @@ Other notes:
|
|
|
71
71
|
|
|
72
72
|
- `@inlang/sdk` 2.10.2
|
|
73
73
|
- `@formatjs/icu-messageformat-parser` ^2
|
|
74
|
-
-
|
|
74
|
+
- AST parity verified against `@inlang/plugin-icu1` 1.1.0 (markup-free corpus)
|
|
75
75
|
|
|
76
76
|
## Development
|
|
77
77
|
|
package/dist/index.js
CHANGED
|
@@ -4366,8 +4366,16 @@ function markupPartToTag(part) {
|
|
|
4366
4366
|
}
|
|
4367
4367
|
}
|
|
4368
4368
|
|
|
4369
|
+
// src/order-cases.ts
|
|
4370
|
+
function orderCaseKeys(keys, isExact2) {
|
|
4371
|
+
const exacts = keys.filter(isExact2);
|
|
4372
|
+
const cldr = keys.filter((k) => !isExact2(k) && k !== "other");
|
|
4373
|
+
const other = keys.includes("other") ? ["other"] : [];
|
|
4374
|
+
return [...exacts, ...cldr, ...other];
|
|
4375
|
+
}
|
|
4376
|
+
|
|
4369
4377
|
// src/parse.ts
|
|
4370
|
-
var SELF_CLOSE_MARK =
|
|
4378
|
+
var SELF_CLOSE_MARK = String.fromCodePoint(57344);
|
|
4371
4379
|
var SELF_CLOSE_RE = /<([\w][\w-]*)\s*\/>/y;
|
|
4372
4380
|
function normalizeSelfClosingTags(source) {
|
|
4373
4381
|
let out = "";
|
|
@@ -4466,7 +4474,7 @@ function extendVariant(v, el, ctx, poundArg) {
|
|
|
4466
4474
|
if (el.type === TYPE.plural || el.type === TYPE.select) {
|
|
4467
4475
|
const sel = registerSelector(el, ctx);
|
|
4468
4476
|
const out = [];
|
|
4469
|
-
for (const caseKey of
|
|
4477
|
+
for (const caseKey of orderCaseKeys(Object.keys(el.options), isExact)) {
|
|
4470
4478
|
const childPound = el.type === TYPE.plural ? el.value : poundArg;
|
|
4471
4479
|
const caseMatches = sel.matchesFor(caseKey);
|
|
4472
4480
|
for (const cv of expand(el.options[caseKey].value, ctx, childPound)) {
|
|
@@ -4499,13 +4507,6 @@ function isSelfClosing(children) {
|
|
|
4499
4507
|
return children.length === 1 && children[0].type === TYPE.literal && // biome-ignore lint/suspicious/noExplicitAny: FormatJS literal carries .value
|
|
4500
4508
|
children[0].value === SELF_CLOSE_MARK;
|
|
4501
4509
|
}
|
|
4502
|
-
function orderCases(el) {
|
|
4503
|
-
const keys = Object.keys(el.options);
|
|
4504
|
-
const exacts = keys.filter(isExact);
|
|
4505
|
-
const cldr = keys.filter((k) => !isExact(k) && k !== "other");
|
|
4506
|
-
const other = keys.includes("other") ? ["other"] : [];
|
|
4507
|
-
return [...exacts, ...cldr, ...other];
|
|
4508
|
-
}
|
|
4509
4510
|
function registerSelector(el, ctx) {
|
|
4510
4511
|
const arg = el.value;
|
|
4511
4512
|
if (el.type === TYPE.select) {
|
|
@@ -4586,7 +4587,7 @@ function mapSimple(el, ctx, poundArg) {
|
|
|
4586
4587
|
];
|
|
4587
4588
|
}
|
|
4588
4589
|
default:
|
|
4589
|
-
throw new Error(`
|
|
4590
|
+
throw new Error(`Unsupported ICU element type: ${el.type}`);
|
|
4590
4591
|
}
|
|
4591
4592
|
}
|
|
4592
4593
|
|
|
@@ -4596,7 +4597,7 @@ function bundleToMessageString(args) {
|
|
|
4596
4597
|
if (variants.length === 0) return "";
|
|
4597
4598
|
if (message.selectors.length === 0) return patternToString(variants[0].pattern, false);
|
|
4598
4599
|
const icuSelectors = resolveIcuSelectors(message, bundle);
|
|
4599
|
-
return reconstruct(icuSelectors, variants);
|
|
4600
|
+
return reconstruct(icuSelectors, variants, false);
|
|
4600
4601
|
}
|
|
4601
4602
|
function declMap(bundle) {
|
|
4602
4603
|
return new Map(bundle.declarations.map((d) => [d.name, d]));
|
|
@@ -4609,11 +4610,11 @@ function resolveIcuSelectors(message, bundle) {
|
|
|
4609
4610
|
function resolvePlural(pluralDecl, pluralKey, exactKey) {
|
|
4610
4611
|
const arg = pluralDecl.value.arg.name;
|
|
4611
4612
|
const ordinal = pluralDecl.value.annotation.options?.some(
|
|
4612
|
-
// biome-ignore lint/suspicious/noExplicitAny:
|
|
4613
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic inlang AST
|
|
4613
4614
|
(o) => o.name === "type" && o.value.value === "ordinal"
|
|
4614
4615
|
);
|
|
4615
4616
|
const offset = Number(
|
|
4616
|
-
// biome-ignore lint/suspicious/noExplicitAny:
|
|
4617
|
+
// biome-ignore lint/suspicious/noExplicitAny: dynamic inlang AST
|
|
4617
4618
|
pluralDecl.value.annotation.options?.find((o) => o.name === "offset")?.value.value ?? 0
|
|
4618
4619
|
);
|
|
4619
4620
|
out.push({ arg, kind: ordinal ? "selectordinal" : "plural", offset, pluralKey, exactKey });
|
|
@@ -4661,16 +4662,9 @@ function caseFor(sel, matches) {
|
|
|
4661
4662
|
const plural = matches.find((x) => x.key === sel.pluralKey);
|
|
4662
4663
|
return plural?.type === "literal-match" ? plural.value : "other";
|
|
4663
4664
|
}
|
|
4664
|
-
function
|
|
4665
|
-
const isEx = (k) => k.startsWith("=");
|
|
4666
|
-
const exacts = keys.filter(isEx);
|
|
4667
|
-
const cldr = keys.filter((k) => !isEx(k) && k !== "other");
|
|
4668
|
-
const other = keys.includes("other") ? ["other"] : [];
|
|
4669
|
-
return [...exacts, ...cldr, ...other];
|
|
4670
|
-
}
|
|
4671
|
-
function reconstruct(selectors, variants) {
|
|
4665
|
+
function reconstruct(selectors, variants, ancestorInPlural) {
|
|
4672
4666
|
const [sel, ...rest] = selectors;
|
|
4673
|
-
const inPlural = sel.kind !== "select";
|
|
4667
|
+
const inPlural = ancestorInPlural || sel.kind !== "select";
|
|
4674
4668
|
const groups = /* @__PURE__ */ new Map();
|
|
4675
4669
|
for (const v of variants) {
|
|
4676
4670
|
const c = caseFor(sel, v.matches);
|
|
@@ -4683,9 +4677,9 @@ function reconstruct(selectors, variants) {
|
|
|
4683
4677
|
}
|
|
4684
4678
|
const offsetStr = sel.offset ? `offset:${sel.offset} ` : "";
|
|
4685
4679
|
const head = `${sel.arg}, ${sel.kind}, ${offsetStr}`;
|
|
4686
|
-
const body =
|
|
4680
|
+
const body = orderCaseKeys([...groups.keys()], (k) => k.startsWith("=")).map((c) => {
|
|
4687
4681
|
const grp = groups.get(c) ?? [];
|
|
4688
|
-
const inner = rest.length === 0 ? patternToString(grp[0].pattern, inPlural) : reconstruct(rest, grp);
|
|
4682
|
+
const inner = rest.length === 0 ? patternToString(grp[0].pattern, inPlural) : reconstruct(rest, grp, inPlural);
|
|
4689
4683
|
return `${c} {${inner}}`;
|
|
4690
4684
|
}).join(" ");
|
|
4691
4685
|
return `{${head}${body}}`;
|
|
@@ -4703,7 +4697,7 @@ function patternToString(pattern, inPlural) {
|
|
|
4703
4697
|
case "markup-standalone":
|
|
4704
4698
|
return markupPartToTag(p);
|
|
4705
4699
|
default:
|
|
4706
|
-
throw new Error(`part
|
|
4700
|
+
throw new Error(`Unknown pattern part: ${p.type}`);
|
|
4707
4701
|
}
|
|
4708
4702
|
}).join("");
|
|
4709
4703
|
}
|
|
@@ -4758,7 +4752,7 @@ var richIcuPlugin = {
|
|
|
4758
4752
|
json = JSON.parse(decoder.decode(file.content));
|
|
4759
4753
|
} catch (err) {
|
|
4760
4754
|
console.warn(
|
|
4761
|
-
`${LOG_PREFIX} JSON
|
|
4755
|
+
`${LOG_PREFIX} invalid JSON for locale "${file.locale}", file skipped: ${err.message}`
|
|
4762
4756
|
);
|
|
4763
4757
|
continue;
|
|
4764
4758
|
}
|
|
@@ -4766,7 +4760,7 @@ var richIcuPlugin = {
|
|
|
4766
4760
|
if (key === "$schema") continue;
|
|
4767
4761
|
if (typeof value !== "string") {
|
|
4768
4762
|
console.warn(
|
|
4769
|
-
`${LOG_PREFIX}
|
|
4763
|
+
`${LOG_PREFIX} non-string value skipped for "${key}" (locale "${file.locale}")`
|
|
4770
4764
|
);
|
|
4771
4765
|
continue;
|
|
4772
4766
|
}
|
|
@@ -4775,7 +4769,7 @@ var richIcuPlugin = {
|
|
|
4775
4769
|
imported = messageToImport({ source: value, bundleId: key, locale: file.locale });
|
|
4776
4770
|
} catch (err) {
|
|
4777
4771
|
console.warn(
|
|
4778
|
-
`${LOG_PREFIX} ICU
|
|
4772
|
+
`${LOG_PREFIX} invalid ICU for "${key}" (locale "${file.locale}"), message skipped: ${err.message}`
|
|
4779
4773
|
);
|
|
4780
4774
|
continue;
|
|
4781
4775
|
}
|
|
@@ -4806,7 +4800,7 @@ var richIcuPlugin = {
|
|
|
4806
4800
|
const s = settings;
|
|
4807
4801
|
const bundleById = new Map(bundles.map((b) => [b.id, b]));
|
|
4808
4802
|
const variantsByMsg = /* @__PURE__ */ new Map();
|
|
4809
|
-
const keyOf = (v) => v.messageId != null ? `id:${v.messageId}` : `bl:${v.messageBundleId}
|
|
4803
|
+
const keyOf = (v) => v.messageId != null ? `id:${v.messageId}` : `bl:${v.messageBundleId} ${v.messageLocale}`;
|
|
4810
4804
|
for (const v of variants) {
|
|
4811
4805
|
const k = keyOf(v);
|
|
4812
4806
|
const arr = variantsByMsg.get(k);
|
|
@@ -4817,20 +4811,26 @@ var richIcuPlugin = {
|
|
|
4817
4811
|
for (const message of messages) {
|
|
4818
4812
|
const bundle = bundleById.get(message.bundleId);
|
|
4819
4813
|
if (!bundle) continue;
|
|
4820
|
-
const vs = (message.id != null ? variantsByMsg.get(`id:${message.id}`) : void 0) ?? variantsByMsg.get(`bl:${message.bundleId}
|
|
4814
|
+
const vs = (message.id != null ? variantsByMsg.get(`id:${message.id}`) : void 0) ?? variantsByMsg.get(`bl:${message.bundleId} ${message.locale}`) ?? [];
|
|
4821
4815
|
if (vs.length === 0) continue;
|
|
4816
|
+
let serialized;
|
|
4817
|
+
try {
|
|
4818
|
+
serialized = bundleToMessageString({ bundle, message, variants: vs });
|
|
4819
|
+
} catch (err) {
|
|
4820
|
+
console.warn(
|
|
4821
|
+
`${LOG_PREFIX} failed to export "${message.bundleId}" (locale "${message.locale}"), message skipped: ${err.message}`
|
|
4822
|
+
);
|
|
4823
|
+
continue;
|
|
4824
|
+
}
|
|
4822
4825
|
if (!byLocale[message.locale]) byLocale[message.locale] = {};
|
|
4823
|
-
byLocale[message.locale][message.bundleId] =
|
|
4824
|
-
bundle,
|
|
4825
|
-
message,
|
|
4826
|
-
variants: vs
|
|
4827
|
-
});
|
|
4826
|
+
byLocale[message.locale][message.bundleId] = serialized;
|
|
4828
4827
|
}
|
|
4829
4828
|
const pattern = pathPattern(s);
|
|
4829
|
+
const encoder = new TextEncoder();
|
|
4830
4830
|
return Object.entries(byLocale).map(([locale, map]) => ({
|
|
4831
4831
|
locale,
|
|
4832
4832
|
name: pattern.replace("{locale}", locale).split("/").at(-1) ?? locale,
|
|
4833
|
-
content:
|
|
4833
|
+
content: encoder.encode(`${JSON.stringify(map, null, " ")}
|
|
4834
4834
|
`)
|
|
4835
4835
|
}));
|
|
4836
4836
|
}
|
package/package.json
CHANGED